Defer Meshed creation

Refactored Mesh API to defer creation of
SkMesh instances until a GrDirectContext
can be obtained on the RenderThread.
This creates an SkMesh during the prepare
tree step when the UI thread is blocked
to ensure no concurrency issues.

Bug: b/265044322
Test: atest CtsUiRenderingTestCases:MeshTest
Change-Id: Ica8c364b99952e0ee71f7b95b312cf29c51ebc2a
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index bcbe706..536bb49 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -332,6 +332,7 @@
         "jni/android_graphics_Matrix.cpp",
         "jni/android_graphics_Picture.cpp",
         "jni/android_graphics_DisplayListCanvas.cpp",
+        "jni/android_graphics_Mesh.cpp",
         "jni/android_graphics_RenderNode.cpp",
         "jni/android_nio_utils.cpp",
         "jni/android_util_PathParser.cpp",
@@ -351,7 +352,6 @@
         "jni/ImageDecoder.cpp",
         "jni/Interpolator.cpp",
         "jni/MeshSpecification.cpp",
-        "jni/Mesh.cpp",
         "jni/MaskFilter.cpp",
         "jni/NinePatch.cpp",
         "jni/NinePatchPeeker.cpp",
@@ -538,6 +538,7 @@
         "Interpolator.cpp",
         "LightingInfo.cpp",
         "Matrix.cpp",
+        "Mesh.cpp",
         "MemoryPolicy.cpp",
         "PathParser.cpp",
         "Properties.cpp",
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index e2127ef..a18ba1c 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -52,4 +52,5 @@
 X(DrawVectorDrawable)
 X(DrawRippleDrawable)
 X(DrawWebView)
-X(DrawMesh)
+X(DrawSkMesh)
+X(DrawMesh)
\ No newline at end of file
diff --git a/libs/hwui/Mesh.cpp b/libs/hwui/Mesh.cpp
new file mode 100644
index 0000000..e59bc95
--- /dev/null
+++ b/libs/hwui/Mesh.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 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 "Mesh.h"
+
+#include <GLES/gl.h>
+#include <SkMesh.h>
+
+#include "SafeMath.h"
+
+static size_t min_vcount_for_mode(SkMesh::Mode mode) {
+    switch (mode) {
+        case SkMesh::Mode::kTriangles:
+            return 3;
+        case SkMesh::Mode::kTriangleStrip:
+            return 3;
+    }
+}
+
+// Re-implementation of SkMesh::validate to validate user side that their mesh is valid.
+std::tuple<bool, SkString> Mesh::validate() {
+#define FAIL_MESH_VALIDATE(...) return std::make_tuple(false, SkStringPrintf(__VA_ARGS__))
+    if (!mMeshSpec) {
+        FAIL_MESH_VALIDATE("MeshSpecification is required.");
+    }
+    if (mVertexBufferData.empty()) {
+        FAIL_MESH_VALIDATE("VertexBuffer is required.");
+    }
+
+    auto meshStride = mMeshSpec->stride();
+    auto meshMode = SkMesh::Mode(mMode);
+    SafeMath sm;
+    size_t vsize = sm.mul(meshStride, mVertexCount);
+    if (sm.add(vsize, mVertexOffset) > mVertexBufferData.size()) {
+        FAIL_MESH_VALIDATE(
+                "The vertex buffer offset and vertex count reads beyond the end of the"
+                " vertex buffer.");
+    }
+
+    if (mVertexOffset % meshStride != 0) {
+        FAIL_MESH_VALIDATE("The vertex offset (%zu) must be a multiple of the vertex stride (%zu).",
+                           mVertexOffset, meshStride);
+    }
+
+    if (size_t uniformSize = mMeshSpec->uniformSize()) {
+        if (!mBuilder->fUniforms || mBuilder->fUniforms->size() < uniformSize) {
+            FAIL_MESH_VALIDATE("The uniform data is %zu bytes but must be at least %zu.",
+                               mBuilder->fUniforms->size(), uniformSize);
+        }
+    }
+
+    auto modeToStr = [](SkMesh::Mode m) {
+        switch (m) {
+            case SkMesh::Mode::kTriangles:
+                return "triangles";
+            case SkMesh::Mode::kTriangleStrip:
+                return "triangle-strip";
+        }
+    };
+    if (!mIndexBufferData.empty()) {
+        if (mIndexCount < min_vcount_for_mode(meshMode)) {
+            FAIL_MESH_VALIDATE("%s mode requires at least %zu indices but index count is %zu.",
+                               modeToStr(meshMode), min_vcount_for_mode(meshMode), mIndexCount);
+        }
+        size_t isize = sm.mul(sizeof(uint16_t), mIndexCount);
+        if (sm.add(isize, mIndexOffset) > mIndexBufferData.size()) {
+            FAIL_MESH_VALIDATE(
+                    "The index buffer offset and index count reads beyond the end of the"
+                    " index buffer.");
+        }
+        // If we allow 32 bit indices then this should enforce 4 byte alignment in that case.
+        if (!SkIsAlign2(mIndexOffset)) {
+            FAIL_MESH_VALIDATE("The index offset must be a multiple of 2.");
+        }
+    } else {
+        if (mVertexCount < min_vcount_for_mode(meshMode)) {
+            FAIL_MESH_VALIDATE("%s mode requires at least %zu vertices but vertex count is %zu.",
+                               modeToStr(meshMode), min_vcount_for_mode(meshMode), mVertexCount);
+        }
+        SkASSERT(!fICount);
+        SkASSERT(!fIOffset);
+    }
+
+    if (!sm.ok()) {
+        FAIL_MESH_VALIDATE("Overflow");
+    }
+#undef FAIL_MESH_VALIDATE
+    return {true, {}};
+}
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
new file mode 100644
index 0000000..9836817
--- /dev/null
+++ b/libs/hwui/Mesh.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022 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 MESH_H_
+#define MESH_H_
+
+#include <GrDirectContext.h>
+#include <SkMesh.h>
+#include <jni.h>
+#include <log/log.h>
+
+#include <utility>
+
+class MeshUniformBuilder {
+public:
+    struct MeshUniform {
+        template <typename T>
+        std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
+                const T& val) {
+            if (!fVar) {
+                LOG_FATAL("Assigning to missing variable");
+            } else if (sizeof(val) != fVar->sizeInBytes()) {
+                LOG_FATAL("Incorrect value size");
+            } else {
+                void* dst = reinterpret_cast<void*>(
+                        reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+                memcpy(dst, &val, sizeof(val));
+            }
+        }
+
+        MeshUniform& operator=(const SkMatrix& val) {
+            if (!fVar) {
+                LOG_FATAL("Assigning to missing variable");
+            } else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
+                LOG_FATAL("Incorrect value size");
+            } else {
+                float* data = reinterpret_cast<float*>(
+                        reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+                data[0] = val.get(0);
+                data[1] = val.get(3);
+                data[2] = val.get(6);
+                data[3] = val.get(1);
+                data[4] = val.get(4);
+                data[5] = val.get(7);
+                data[6] = val.get(2);
+                data[7] = val.get(5);
+                data[8] = val.get(8);
+            }
+            return *this;
+        }
+
+        template <typename T>
+        bool set(const T val[], const int count) {
+            static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
+            if (!fVar) {
+                LOG_FATAL("Assigning to missing variable");
+                return false;
+            } else if (sizeof(T) * count != fVar->sizeInBytes()) {
+                LOG_FATAL("Incorrect value size");
+                return false;
+            } else {
+                void* dst = reinterpret_cast<void*>(
+                        reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
+                memcpy(dst, val, sizeof(T) * count);
+            }
+            return true;
+        }
+
+        MeshUniformBuilder* fOwner;
+        const SkRuntimeEffect::Uniform* fVar;
+    };
+    MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; }
+
+    explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
+        fMeshSpec = sk_sp(meshSpec);
+        fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
+    }
+
+    sk_sp<SkData> fUniforms;
+
+private:
+    void* writableUniformData() {
+        if (!fUniforms->unique()) {
+            fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size());
+        }
+        return fUniforms->writable_data();
+    }
+
+    sk_sp<SkMeshSpecification> fMeshSpec;
+};
+
+class Mesh {
+public:
+    Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer,
+         size_t vertexBufferSize, jint vertexCount, jint vertexOffset,
+         std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
+            : mMeshSpec(meshSpec)
+            , mMode(mode)
+            , mVertexCount(vertexCount)
+            , mVertexOffset(vertexOffset)
+            , mBuilder(std::move(builder))
+            , mBounds(bounds) {
+        copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize);
+    }
+
+    Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer,
+         size_t vertexBufferSize, jint vertexCount, jint vertexOffset, const void* indexBuffer,
+         size_t indexBufferSize, jint indexCount, jint indexOffset,
+         std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
+            : mMeshSpec(meshSpec)
+            , mMode(mode)
+            , mVertexCount(vertexCount)
+            , mVertexOffset(vertexOffset)
+            , mIndexCount(indexCount)
+            , mIndexOffset(indexOffset)
+            , mBuilder(std::move(builder))
+            , mBounds(bounds) {
+        copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize);
+        copyToVector(mIndexBufferData, indexBuffer, indexBufferSize);
+    }
+
+    Mesh(Mesh&&) = default;
+
+    Mesh& operator=(Mesh&&) = default;
+
+    [[nodiscard]] std::tuple<bool, SkString> validate();
+
+    void updateSkMesh(GrDirectContext* context) const {
+        GrDirectContext::DirectContextID genId = GrDirectContext::DirectContextID();
+        if (context) {
+            genId = context->directContextID();
+        }
+
+        if (mIsDirty || genId != mGenerationId) {
+            auto vb = SkMesh::MakeVertexBuffer(
+                    context, reinterpret_cast<const void*>(mVertexBufferData.data()),
+                    mVertexBufferData.size());
+            auto meshMode = SkMesh::Mode(mMode);
+            if (!mIndexBufferData.empty()) {
+                auto ib = SkMesh::MakeIndexBuffer(
+                        context, reinterpret_cast<const void*>(mIndexBufferData.data()),
+                        mIndexBufferData.size());
+                mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
+                                            ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
+                                            mBounds)
+                                .mesh;
+            } else {
+                mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
+                                     mBuilder->fUniforms, mBounds)
+                                .mesh;
+            }
+            mIsDirty = false;
+            mGenerationId = genId;
+        }
+    }
+
+    SkMesh& getSkMesh() const {
+        LOG_FATAL_IF(mIsDirty,
+                     "Attempt to obtain SkMesh when Mesh is dirty, did you "
+                     "forget to call updateSkMesh with a GrDirectContext? "
+                     "Defensively creating a CPU mesh");
+        return mMesh;
+    }
+
+    void markDirty() { mIsDirty = true; }
+
+    MeshUniformBuilder* uniformBuilder() { return mBuilder.get(); }
+
+private:
+    void copyToVector(std::vector<uint8_t>& dst, const void* src, size_t srcSize) {
+        if (src) {
+            dst.resize(srcSize);
+            memcpy(dst.data(), src, srcSize);
+        }
+    }
+
+    sk_sp<SkMeshSpecification> mMeshSpec;
+    int mMode = 0;
+
+    std::vector<uint8_t> mVertexBufferData;
+    size_t mVertexCount = 0;
+    size_t mVertexOffset = 0;
+
+    std::vector<uint8_t> mIndexBufferData;
+    size_t mIndexCount = 0;
+    size_t mIndexOffset = 0;
+
+    std::unique_ptr<MeshUniformBuilder> mBuilder;
+    SkRect mBounds{};
+
+    mutable SkMesh mMesh{};
+    mutable bool mIsDirty = true;
+    mutable GrDirectContext::DirectContextID mGenerationId = GrDirectContext::DirectContextID();
+};
+#endif  // MESH_H_
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 659aec0..0b58406 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -24,6 +24,7 @@
 #include <experimental/type_traits>
 #include <utility>
 
+#include "Mesh.h"
 #include "SkAndroidFrameworkUtils.h"
 #include "SkBlendMode.h"
 #include "SkCanvas.h"
@@ -502,14 +503,14 @@
         c->drawVertices(vertices, mode, paint);
     }
 };
-struct DrawMesh final : Op {
-    static const auto kType = Type::DrawMesh;
-    DrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
+struct DrawSkMesh final : Op {
+    static const auto kType = Type::DrawSkMesh;
+    DrawSkMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
             : cpuMesh(mesh), blender(std::move(blender)), paint(paint) {
         isGpuBased = false;
     }
 
-    SkMesh cpuMesh;
+    const SkMesh& cpuMesh;
     mutable SkMesh gpuMesh;
     sk_sp<SkBlender> blender;
     SkPaint paint;
@@ -517,6 +518,7 @@
     mutable GrDirectContext::DirectContextID contextId;
     void draw(SkCanvas* c, const SkMatrix&) const {
         GrDirectContext* directContext = c->recordingContext()->asDirectContext();
+
         GrDirectContext::DirectContextID id = directContext->directContextID();
         if (!isGpuBased || contextId != id) {
             sk_sp<SkMesh::VertexBuffer> vb =
@@ -543,6 +545,18 @@
         c->drawMesh(gpuMesh, blender, paint);
     }
 };
+
+struct DrawMesh final : Op {
+    static const auto kType = Type::DrawMesh;
+    DrawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
+            : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+
+    const Mesh& mesh;
+    sk_sp<SkBlender> blender;
+    SkPaint paint;
+
+    void draw(SkCanvas* c, const SkMatrix&) const { c->drawMesh(mesh.getSkMesh(), blender, paint); }
+};
 struct DrawAtlas final : Op {
     static const auto kType = Type::DrawAtlas;
     DrawAtlas(const SkImage* atlas, int count, SkBlendMode mode, const SkSamplingOptions& sampling,
@@ -859,6 +873,10 @@
 }
 void DisplayListData::drawMesh(const SkMesh& mesh, const sk_sp<SkBlender>& blender,
                                const SkPaint& paint) {
+    this->push<DrawSkMesh>(0, mesh, blender, paint);
+}
+void DisplayListData::drawMesh(const Mesh& mesh, const sk_sp<SkBlender>& blender,
+                               const SkPaint& paint) {
     this->push<DrawMesh>(0, mesh, blender, paint);
 }
 void DisplayListData::drawAtlas(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[],
@@ -1205,6 +1223,9 @@
                                  const SkPaint& paint) {
     fDL->drawMesh(mesh, blender, paint);
 }
+void RecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) {
+    fDL->drawMesh(mesh, blender, paint);
+}
 void RecordingCanvas::onDrawAtlas2(const SkImage* atlas, const SkRSXform xforms[],
                                    const SkRect texs[], const SkColor colors[], int count,
                                    SkBlendMode bmode, const SkSamplingOptions& sampling,
@@ -1223,5 +1244,14 @@
     fDL->drawWebView(drawable);
 }
 
+[[nodiscard]] const SkMesh& DrawMeshPayload::getSkMesh() const {
+    LOG_FATAL_IF(!meshWrapper && !mesh, "One of Mesh or Mesh must be non-null");
+    if (meshWrapper) {
+        return meshWrapper->getSkMesh();
+    } else {
+        return *mesh;
+    }
+}
+
 }  // namespace uirenderer
 }  // namespace android
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 8409e13..1f4ba5d 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -28,6 +28,7 @@
 #include <log/log.h>
 
 #include <cstdlib>
+#include <utility>
 #include <vector>
 
 #include "CanvasTransform.h"
@@ -40,6 +41,7 @@
 
 enum class SkBlendMode;
 class SkRRect;
+class Mesh;
 
 namespace android {
 namespace uirenderer {
@@ -66,6 +68,18 @@
 
 static_assert(sizeof(DisplayListOp) == 4);
 
+class DrawMeshPayload {
+public:
+    explicit DrawMeshPayload(const SkMesh* mesh) : mesh(mesh) {}
+    explicit DrawMeshPayload(const Mesh* meshWrapper) : meshWrapper(meshWrapper) {}
+
+    [[nodiscard]] const SkMesh& getSkMesh() const;
+
+private:
+    const SkMesh* mesh = nullptr;
+    const Mesh* meshWrapper = nullptr;
+};
+
 struct DrawImagePayload {
     explicit DrawImagePayload(Bitmap& bitmap)
             : image(bitmap.makeImage()), palette(bitmap.palette()) {
@@ -143,6 +157,7 @@
     void drawDRRect(const SkRRect&, const SkRRect&, const SkPaint&);
 
     void drawMesh(const SkMesh&, const sk_sp<SkBlender>&, const SkPaint&);
+    void drawMesh(const Mesh&, const sk_sp<SkBlender>&, const SkPaint&);
 
     void drawAnnotation(const SkRect&, const char*, SkData*);
     void drawDrawable(SkDrawable*, const SkMatrix*);
@@ -247,6 +262,7 @@
                      SkBlendMode, const SkSamplingOptions&, const SkRect*, const SkPaint*) override;
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
 
+    void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint);
     void drawVectorDrawable(VectorDrawableRoot* tree);
     void drawWebView(skiapipeline::FunctorDrawable*);
 
diff --git a/libs/hwui/SafeMath.h b/libs/hwui/SafeMath.h
new file mode 100644
index 0000000..4d6adf5
--- /dev/null
+++ b/libs/hwui/SafeMath.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 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 SkSafeMath_DEFINED
+#define SkSafeMath_DEFINED
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+
+// Copy of Skia's SafeMath API used to validate Mesh parameters to support
+// deferred creation of SkMesh instances on RenderThread.
+// SafeMath always check that a series of operations do not overflow.
+// This must be correct for all platforms, because this is a check for safety at runtime.
+
+class SafeMath {
+public:
+    SafeMath() = default;
+
+    bool ok() const { return fOK; }
+    explicit operator bool() const { return fOK; }
+
+    size_t mul(size_t x, size_t y) {
+        return sizeof(size_t) == sizeof(uint64_t) ? mul64(x, y) : mul32(x, y);
+    }
+
+    size_t add(size_t x, size_t y) {
+        size_t result = x + y;
+        fOK &= result >= x;
+        return result;
+    }
+
+    /**
+     *  Return a + b, unless this result is an overflow/underflow. In those cases, fOK will
+     *  be set to false, and it is undefined what this returns.
+     */
+    int addInt(int a, int b) {
+        if (b < 0 && a < std::numeric_limits<int>::min() - b) {
+            fOK = false;
+            return a;
+        } else if (b > 0 && a > std::numeric_limits<int>::max() - b) {
+            fOK = false;
+            return a;
+        }
+        return a + b;
+    }
+
+    // These saturate to their results
+    static size_t Add(size_t x, size_t y) {
+        SafeMath tmp;
+        size_t sum = tmp.add(x, y);
+        return tmp.ok() ? sum : SIZE_MAX;
+    }
+
+    static size_t Mul(size_t x, size_t y) {
+        SafeMath tmp;
+        size_t prod = tmp.mul(x, y);
+        return tmp.ok() ? prod : SIZE_MAX;
+    }
+
+private:
+    uint32_t mul32(uint32_t x, uint32_t y) {
+        uint64_t bx = x;
+        uint64_t by = y;
+        uint64_t result = bx * by;
+        fOK &= result >> 32 == 0;
+        // Overflow information is capture in fOK. Return the result modulo 2^32.
+        return (uint32_t)result;
+    }
+
+    uint64_t mul64(uint64_t x, uint64_t y) {
+        if (x <= std::numeric_limits<uint64_t>::max() >> 32 &&
+            y <= std::numeric_limits<uint64_t>::max() >> 32) {
+            return x * y;
+        } else {
+            auto hi = [](uint64_t x) { return x >> 32; };
+            auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; };
+
+            uint64_t lx_ly = lo(x) * lo(y);
+            uint64_t hx_ly = hi(x) * lo(y);
+            uint64_t lx_hy = lo(x) * hi(y);
+            uint64_t hx_hy = hi(x) * hi(y);
+            uint64_t result = 0;
+            result = this->add(lx_ly, (hx_ly << 32));
+            result = this->add(result, (lx_hy << 32));
+            fOK &= (hx_hy + (hx_ly >> 32) + (lx_hy >> 32)) == 0;
+
+            return result;
+        }
+    }
+    bool fOK = true;
+};
+
+#endif  // SkSafeMath_DEFINED
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index d0124f5..7a12769 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -39,21 +39,22 @@
 #include <SkShader.h>
 #include <SkTextBlob.h>
 #include <SkVertices.h>
+#include <log/log.h>
+#include <ui/FatVector.h>
 
 #include <memory>
 #include <optional>
 #include <utility>
 
 #include "CanvasProperty.h"
+#include "Mesh.h"
 #include "NinePatchUtils.h"
 #include "VectorDrawable.h"
 #include "hwui/Bitmap.h"
 #include "hwui/MinikinUtils.h"
 #include "hwui/PaintFilter.h"
-#include <log/log.h>
 #include "pipeline/skia/AnimatedDrawables.h"
 #include "pipeline/skia/HolePunch.h"
-#include <ui/FatVector.h>
 
 namespace android {
 
@@ -572,8 +573,14 @@
     applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawVertices(vertices, mode, p); });
 }
 
-void SkiaCanvas::drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint) {
-    mCanvas->drawMesh(mesh, blender, paint);
+void SkiaCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
+    GrDirectContext* context = nullptr;
+    auto recordingContext = mCanvas->recordingContext();
+    if (recordingContext) {
+        context = recordingContext->asDirectContext();
+    }
+    mesh.updateSkMesh(context);
+    mCanvas->drawMesh(mesh.getSkMesh(), blender, paint);
 }
 
 // ----------------------------------------------------------------------------
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index f2c286a..b785989 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -129,8 +129,7 @@
                          float sweepAngle, bool useCenter, const Paint& paint) override;
     virtual void drawPath(const SkPath& path, const Paint& paint) override;
     virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) override;
-    virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender,
-                          const SkPaint& paint) override;
+    virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) override;
 
     virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) override;
     virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) override;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 2a20191..44ee31d 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -16,18 +16,17 @@
 
 #pragma once
 
-#include <cutils/compiler.h>
-#include <utils/Functor.h>
 #include <SaveFlags.h>
-
-#include <androidfw/ResourceTypes.h>
-#include "Properties.h"
-#include "pipeline/skia/AnimatedDrawables.h"
-#include "utils/Macros.h"
-
 #include <SkBitmap.h>
 #include <SkCanvas.h>
 #include <SkMatrix.h>
+#include <androidfw/ResourceTypes.h>
+#include <cutils/compiler.h>
+#include <utils/Functor.h>
+
+#include "Properties.h"
+#include "pipeline/skia/AnimatedDrawables.h"
+#include "utils/Macros.h"
 
 class SkAnimatedImage;
 enum class SkBlendMode;
@@ -35,6 +34,7 @@
 class SkRRect;
 class SkRuntimeShaderBuilder;
 class SkVertices;
+class Mesh;
 
 namespace minikin {
 class Font;
@@ -227,7 +227,7 @@
                          float sweepAngle, bool useCenter, const Paint& paint) = 0;
     virtual void drawPath(const SkPath& path, const Paint& paint) = 0;
     virtual void drawVertices(const SkVertices*, SkBlendMode, const Paint& paint) = 0;
-    virtual void drawMesh(const SkMesh& mesh, sk_sp<SkBlender>, const SkPaint& paint) = 0;
+    virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender>, const Paint& paint) = 0;
 
     // Bitmap-based
     virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) = 0;
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
deleted file mode 100644
index b13d9ba..0000000
--- a/libs/hwui/jni/Mesh.cpp
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2022 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 <GLES/gl.h>
-#include <Mesh.h>
-#include <SkMesh.h>
-
-#include "GraphicsJNI.h"
-#include "graphics_jni_helpers.h"
-
-namespace android {
-
-sk_sp<SkMesh::VertexBuffer> genVertexBuffer(JNIEnv* env, jobject buffer, int size,
-                                            jboolean isDirect) {
-    auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
-    auto vertexBuffer = SkMesh::MakeVertexBuffer(nullptr, buff.data(), size);
-    return vertexBuffer;
-}
-
-sk_sp<SkMesh::IndexBuffer> genIndexBuffer(JNIEnv* env, jobject buffer, int size,
-                                          jboolean isDirect) {
-    auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect);
-    auto indexBuffer = SkMesh::MakeIndexBuffer(nullptr, buff.data(), size);
-    return indexBuffer;
-}
-
-static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
-                  jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top,
-                  jfloat right, jfloat bottom) {
-    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
-    sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
-            genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect);
-    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto meshResult = SkMesh::Make(
-            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
-            SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
-
-    if (!meshResult.error.isEmpty()) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
-    }
-
-    auto meshPtr = std::make_unique<MeshWrapper>(
-            MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
-    return reinterpret_cast<jlong>(meshPtr.release());
-}
-
-static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
-                         jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
-                         jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
-                         jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) {
-    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
-    sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
-            genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isVertexDirect);
-    sk_sp<SkMesh::IndexBuffer> skIndexBuffer =
-            genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
-    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-
-    auto meshResult = SkMesh::MakeIndexed(
-            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
-            skIndexBuffer, indexCount, indexOffset,
-            SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
-
-    if (!meshResult.error.isEmpty()) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
-    }
-    auto meshPtr = std::make_unique<MeshWrapper>(
-            MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
-    return reinterpret_cast<jlong>(meshPtr.release());
-}
-
-static void updateMesh(JNIEnv* env, jobject, jlong meshWrapper, jboolean indexed) {
-    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    auto mesh = wrapper->mesh;
-    if (indexed) {
-        wrapper->mesh = SkMesh::MakeIndexed(sk_ref_sp(mesh.spec()), mesh.mode(),
-                                            sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(),
-                                            mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()),
-                                            mesh.indexCount(), mesh.indexOffset(),
-                                            wrapper->builder.fUniforms, mesh.bounds())
-                                .mesh;
-    } else {
-        wrapper->mesh = SkMesh::Make(sk_ref_sp(mesh.spec()), mesh.mode(),
-                                     sk_ref_sp(mesh.vertexBuffer()), mesh.vertexCount(),
-                                     mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds())
-                                .mesh;
-    }
-}
-
-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;
-}
-
-static 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;
-    }
-}
-
-static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
-                                      const char* uniformName, const float values[], int count,
-                                      bool isColor) {
-    MeshUniformBuilder::MeshUniform 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);
-    }
-}
-
-static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
-                                jfloat value1, jfloat value2, jfloat value3, jfloat value4,
-                                jint count) {
-    auto* wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    ScopedUtfChars name(env, uniformName);
-    const float values[4] = {value1, value2, value3, value4};
-    nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), values, count, false);
-}
-
-static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
-                                     jfloatArray jvalues, jboolean isColor) {
-    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    ScopedUtfChars name(env, jUniformName);
-    AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
-    nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
-                              autoValues.length(), isColor);
-}
-
-static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
-                                    const char* uniformName, const int values[], int count) {
-    MeshUniformBuilder::MeshUniform 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);
-    }
-}
-
-static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
-                              jint value1, jint value2, jint value3, jint value4, jint count) {
-    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    ScopedUtfChars name(env, uniformName);
-    const int values[4] = {value1, value2, value3, value4};
-    nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), values, count);
-}
-
-static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
-                                   jintArray values) {
-    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
-    ScopedUtfChars name(env, uniformName);
-    AutoJavaIntArray autoValues(env, values, 0);
-    nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
-                            autoValues.length());
-}
-
-static void MeshWrapper_destroy(MeshWrapper* wrapper) {
-    delete wrapper;
-}
-
-static jlong getMeshFinalizer(JNIEnv*, jobject) {
-    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
-}
-
-static const JNINativeMethod gMeshMethods[] = {
-        {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
-        {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make},
-        {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J",
-         (void*)makeIndexed},
-        {"nativeUpdateMesh", "(JZ)V", (void*)updateMesh},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
-
-int register_android_graphics_Mesh(JNIEnv* env) {
-    android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
-    return 0;
-}
-
-}  // namespace android
diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h
deleted file mode 100644
index 61c2260..0000000
--- a/libs/hwui/jni/Mesh.h
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2022 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 FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
-#define FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
-
-#include <SkMesh.h>
-#include <jni.h>
-
-#include <log/log.h>
-#include <utility>
-
-#include "graphics_jni_helpers.h"
-
-#define gIndexByteSize 2
-
-// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
-// direct and indrect buffers, allowing access to the underlying data in both
-// situations. If passed a null buffer, we will throw NullPointerException,
-// and c_data will return nullptr.
-//
-// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
-// conversion.
-class ScopedJavaNioBuffer {
-public:
-    ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, jint size, jboolean isDirect)
-            : mEnv(env), mBuffer(buffer) {
-        if (buffer == nullptr) {
-            mDataBase = nullptr;
-            mData = nullptr;
-            jniThrowNullPointerException(env);
-        } else {
-            mArray = (jarray) nullptr;
-            if (isDirect) {
-                mData = getDirectBufferPointer(mEnv, mBuffer);
-            } else {
-                mData = setIndirectData(size);
-            }
-        }
-    }
-
-    ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }
-
-    ~ScopedJavaNioBuffer() { reset(); }
-
-    void reset() {
-        if (mDataBase) {
-            releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
-            mDataBase = nullptr;
-        }
-    }
-
-    ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
-        if (this != &rhs) {
-            reset();
-
-            mEnv = rhs.mEnv;
-            mBuffer = rhs.mBuffer;
-            mDataBase = rhs.mDataBase;
-            mData = rhs.mData;
-            mArray = rhs.mArray;
-            rhs.mEnv = nullptr;
-            rhs.mData = nullptr;
-            rhs.mBuffer = nullptr;
-            rhs.mArray = nullptr;
-            rhs.mDataBase = nullptr;
-        }
-        return *this;
-    }
-
-    const void* data() const { return mData; }
-
-private:
-    /**
-     * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
-     * from a java.nio.Buffer.
-     */
-    void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
-        if (buffer == nullptr) {
-            return nullptr;
-        }
-
-        jint position;
-        jint limit;
-        jint elementSizeShift;
-        jlong pointer;
-        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
-        if (pointer == 0) {
-            jniThrowException(mEnv, "java/lang/IllegalArgumentException",
-                              "Must use a native order direct Buffer");
-            return nullptr;
-        }
-        pointer += position << elementSizeShift;
-        return reinterpret_cast<void*>(pointer);
-    }
-
-    static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
-        env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
-    }
-
-    static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
-                            jint* offset) {
-        jint position;
-        jint limit;
-        jint elementSizeShift;
-
-        jlong pointer;
-        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
-        *remaining = (limit - position) << elementSizeShift;
-        if (pointer != 0L) {
-            *array = nullptr;
-            pointer += position << elementSizeShift;
-            return reinterpret_cast<void*>(pointer);
-        }
-
-        *array = jniGetNioBufferBaseArray(env, buffer);
-        *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
-        return nullptr;
-    }
-
-    /**
-     * This is a copy of
-     * static void android_glBufferData__IILjava_nio_Buffer_2I
-     * from com_google_android_gles_jni_GLImpl.cpp
-     */
-    void* setIndirectData(jint size) {
-        jint exception;
-        const char* exceptionType;
-        const char* exceptionMessage;
-        jint bufferOffset = (jint)0;
-        jint remaining;
-        void* tempData;
-
-        if (mBuffer) {
-            tempData =
-                    (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
-            if (remaining < size) {
-                exception = 1;
-                exceptionType = "java/lang/IllegalArgumentException";
-                exceptionMessage = "remaining() < size < needed";
-                goto exit;
-            }
-        }
-        if (mBuffer && tempData == nullptr) {
-            mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
-            tempData = (void*)(mDataBase + bufferOffset);
-        }
-        return tempData;
-    exit:
-        if (mArray) {
-            releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
-        }
-        if (exception) {
-            jniThrowException(mEnv, exceptionType, exceptionMessage);
-        }
-        return nullptr;
-    }
-
-    JNIEnv* mEnv;
-
-    // Java Buffer data
-    void* mData;
-    jobject mBuffer;
-
-    // Indirect Buffer Data
-    jarray mArray;
-    char* mDataBase;
-};
-
-class MeshUniformBuilder {
-public:
-    struct MeshUniform {
-        template <typename T>
-        std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=(
-                const T& val) {
-            if (!fVar) {
-                LOG_FATAL("Assigning to missing variable");
-            } else if (sizeof(val) != fVar->sizeInBytes()) {
-                LOG_FATAL("Incorrect value size");
-            } else {
-                void* dst = reinterpret_cast<void*>(
-                    reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
-                memcpy(dst, &val, sizeof(val));
-            }
-        }
-
-        MeshUniform& operator=(const SkMatrix& val) {
-            if (!fVar) {
-                LOG_FATAL("Assigning to missing variable");
-            } else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
-                LOG_FATAL("Incorrect value size");
-            } else {
-                float* data = reinterpret_cast<float*>(
-                    reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
-                data[0] = val.get(0);
-                data[1] = val.get(3);
-                data[2] = val.get(6);
-                data[3] = val.get(1);
-                data[4] = val.get(4);
-                data[5] = val.get(7);
-                data[6] = val.get(2);
-                data[7] = val.get(5);
-                data[8] = val.get(8);
-            }
-            return *this;
-        }
-
-        template <typename T>
-        bool set(const T val[], const int count) {
-            static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable");
-            if (!fVar) {
-                LOG_FATAL("Assigning to missing variable");
-                return false;
-            } else if (sizeof(T) * count != fVar->sizeInBytes()) {
-                LOG_FATAL("Incorrect value size");
-                return false;
-            } else {
-                void* dst = reinterpret_cast<void*>(
-                    reinterpret_cast<uint8_t*>(fOwner->writableUniformData()) + fVar->offset);
-                memcpy(dst, val, sizeof(T) * count);
-            }
-            return true;
-        }
-
-        MeshUniformBuilder* fOwner;
-        const SkRuntimeEffect::Uniform* fVar;
-    };
-    MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; }
-
-    explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
-        fMeshSpec = sk_sp(meshSpec);
-        fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
-    }
-
-    sk_sp<SkData> fUniforms;
-
-private:
-    void* writableUniformData() {
-        if (!fUniforms->unique()) {
-            fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size());
-        }
-        return fUniforms->writable_data();
-    }
-
-    sk_sp<SkMeshSpecification> fMeshSpec;
-};
-
-struct MeshWrapper {
-    SkMesh mesh;
-    MeshUniformBuilder builder;
-};
-#endif  // FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 8a4d4e1..8ba7503 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -21,7 +21,6 @@
 #else
 #define __ANDROID_API_P__ 28
 #endif
-#include <Mesh.h>
 #include <androidfw/ResourceTypes.h>
 #include <hwui/Canvas.h>
 #include <hwui/Paint.h>
@@ -446,10 +445,10 @@
 
 static void drawMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong meshHandle, jint modeHandle,
                      jlong paintHandle) {
-    const SkMesh mesh = reinterpret_cast<MeshWrapper*>(meshHandle)->mesh;
+    const Mesh* mesh = reinterpret_cast<Mesh*>(meshHandle);
     SkBlendMode blendMode = static_cast<SkBlendMode>(modeHandle);
-    SkPaint* paint = reinterpret_cast<Paint*>(paintHandle);
-    get_canvas(canvasHandle)->drawMesh(mesh, SkBlender::Mode(blendMode), *paint);
+    Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+    get_canvas(canvasHandle)->drawMesh(*mesh, SkBlender::Mode(blendMode), *paint);
 }
 
 static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
diff --git a/libs/hwui/jni/android_graphics_Mesh.cpp b/libs/hwui/jni/android_graphics_Mesh.cpp
new file mode 100644
index 0000000..04339dc
--- /dev/null
+++ b/libs/hwui/jni/android_graphics_Mesh.cpp
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2023 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 <GrDirectContext.h>
+#include <Mesh.h>
+#include <SkMesh.h>
+#include <jni.h>
+#include <log/log.h>
+
+#include <utility>
+
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+#define gIndexByteSize 2
+
+// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
+// direct and indrect buffers, allowing access to the underlying data in both
+// situations. If passed a null buffer, we will throw NullPointerException,
+// and c_data will return nullptr.
+//
+// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
+// conversion.
+class ScopedJavaNioBuffer {
+public:
+    ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, size_t size, jboolean isDirect)
+            : mEnv(env), mBuffer(buffer) {
+        if (buffer == nullptr) {
+            mDataBase = nullptr;
+            mData = nullptr;
+            jniThrowNullPointerException(env);
+        } else {
+            mArray = (jarray) nullptr;
+            if (isDirect) {
+                mData = getDirectBufferPointer(mEnv, mBuffer);
+            } else {
+                mData = setIndirectData(size);
+            }
+        }
+    }
+
+    ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }
+
+    ~ScopedJavaNioBuffer() { reset(); }
+
+    void reset() {
+        if (mDataBase) {
+            releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
+            mDataBase = nullptr;
+        }
+    }
+
+    ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
+        if (this != &rhs) {
+            reset();
+
+            mEnv = rhs.mEnv;
+            mBuffer = rhs.mBuffer;
+            mDataBase = rhs.mDataBase;
+            mData = rhs.mData;
+            mArray = rhs.mArray;
+            rhs.mEnv = nullptr;
+            rhs.mData = nullptr;
+            rhs.mBuffer = nullptr;
+            rhs.mArray = nullptr;
+            rhs.mDataBase = nullptr;
+        }
+        return *this;
+    }
+
+    const void* data() const { return mData; }
+
+private:
+    /**
+     * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
+     * from a java.nio.Buffer.
+     */
+    void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
+        if (buffer == nullptr) {
+            return nullptr;
+        }
+
+        jint position;
+        jint limit;
+        jint elementSizeShift;
+        jlong pointer;
+        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+        if (pointer == 0) {
+            jniThrowException(mEnv, "java/lang/IllegalArgumentException",
+                              "Must use a native order direct Buffer");
+            return nullptr;
+        }
+        pointer += position << elementSizeShift;
+        return reinterpret_cast<void*>(pointer);
+    }
+
+    static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
+        env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
+    }
+
+    static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
+                            jint* offset) {
+        jint position;
+        jint limit;
+        jint elementSizeShift;
+
+        jlong pointer;
+        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+        *remaining = (limit - position) << elementSizeShift;
+        if (pointer != 0L) {
+            *array = nullptr;
+            pointer += position << elementSizeShift;
+            return reinterpret_cast<void*>(pointer);
+        }
+
+        *array = jniGetNioBufferBaseArray(env, buffer);
+        *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
+        return nullptr;
+    }
+
+    /**
+     * This is a copy of
+     * static void android_glBufferData__IILjava_nio_Buffer_2I
+     * from com_google_android_gles_jni_GLImpl.cpp
+     */
+    void* setIndirectData(size_t size) {
+        jint exception;
+        const char* exceptionType;
+        const char* exceptionMessage;
+        jint bufferOffset = (jint)0;
+        jint remaining;
+        void* tempData;
+
+        if (mBuffer) {
+            tempData =
+                    (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
+            if (remaining < size) {
+                exception = 1;
+                exceptionType = "java/lang/IllegalArgumentException";
+                exceptionMessage = "remaining() < size < needed";
+                goto exit;
+            }
+        }
+        if (mBuffer && tempData == nullptr) {
+            mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
+            tempData = (void*)(mDataBase + bufferOffset);
+        }
+        return tempData;
+    exit:
+        if (mArray) {
+            releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
+        }
+        if (exception) {
+            jniThrowException(mEnv, exceptionType, exceptionMessage);
+        }
+        return nullptr;
+    }
+
+    JNIEnv* mEnv;
+
+    // Java Buffer data
+    void* mData;
+    jobject mBuffer;
+
+    // Indirect Buffer Data
+    jarray mArray;
+    char* mDataBase;
+};
+
+namespace android {
+
+static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+                  jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top,
+                  jfloat right, jfloat bottom) {
+    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+    size_t bufferSize = vertexCount * skMeshSpec->stride();
+    auto buff = ScopedJavaNioBuffer(env, vertexBuffer, bufferSize, isDirect);
+    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+    auto meshPtr = new Mesh(skMeshSpec, mode, buff.data(), bufferSize, vertexCount, vertexOffset,
+                            std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+    auto [valid, msg] = meshPtr->validate();
+    if (!valid) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
+    }
+    return reinterpret_cast<jlong>(meshPtr);
+}
+
+static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
+                         jboolean isVertexDirect, jint vertexCount, jint vertexOffset,
+                         jobject indexBuffer, jboolean isIndexDirect, jint indexCount,
+                         jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) {
+    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
+    auto vertexBufferSize = vertexCount * skMeshSpec->stride();
+    auto indexBufferSize = indexCount * gIndexByteSize;
+    auto vBuf = ScopedJavaNioBuffer(env, vertexBuffer, vertexBufferSize, isVertexDirect);
+    auto iBuf = ScopedJavaNioBuffer(env, indexBuffer, indexBufferSize, isIndexDirect);
+    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
+    auto meshPtr = new Mesh(skMeshSpec, mode, vBuf.data(), vertexBufferSize, vertexCount,
+                            vertexOffset, iBuf.data(), indexBufferSize, indexCount, indexOffset,
+                            std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+    auto [valid, msg] = meshPtr->validate();
+    if (!valid) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
+    }
+
+    return reinterpret_cast<jlong>(meshPtr);
+}
+
+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;
+}
+
+static 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;
+    }
+}
+
+static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+                                      const char* uniformName, const float values[], int count,
+                                      bool isColor) {
+    MeshUniformBuilder::MeshUniform 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);
+    }
+}
+
+static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+                                jfloat value1, jfloat value2, jfloat value3, jfloat value4,
+                                jint count) {
+    auto* wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+    ScopedUtfChars name(env, uniformName);
+    const float values[4] = {value1, value2, value3, value4};
+    nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count, false);
+    wrapper->markDirty();
+}
+
+static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
+                                     jfloatArray jvalues, jboolean isColor) {
+    auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+    ScopedUtfChars name(env, jUniformName);
+    AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
+    nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
+                              autoValues.length(), isColor);
+    wrapper->markDirty();
+}
+
+static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
+                                    const char* uniformName, const int values[], int count) {
+    MeshUniformBuilder::MeshUniform 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);
+    }
+}
+
+static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+                              jint value1, jint value2, jint value3, jint value4, jint count) {
+    auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+    ScopedUtfChars name(env, uniformName);
+    const int values[4] = {value1, value2, value3, value4};
+    nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count);
+    wrapper->markDirty();
+}
+
+static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
+                                   jintArray values) {
+    auto wrapper = reinterpret_cast<Mesh*>(meshWrapper);
+    ScopedUtfChars name(env, uniformName);
+    AutoJavaIntArray autoValues(env, values, 0);
+    nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
+                            autoValues.length());
+    wrapper->markDirty();
+}
+
+static void MeshWrapper_destroy(Mesh* wrapper) {
+    delete wrapper;
+}
+
+static jlong getMeshFinalizer(JNIEnv*, jobject) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy));
+}
+
+static const JNINativeMethod gMeshMethods[] = {
+        {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer},
+        {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make},
+        {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J",
+         (void*)makeIndexed},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}};
+
+int register_android_graphics_Mesh(JNIEnv* env) {
+    android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods));
+    return 0;
+}
+
+}  // namespace android
\ No newline at end of file
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index fcfc4f8..af2d3b3 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -23,6 +23,7 @@
 #else
 #include "DamageAccumulator.h"
 #endif
+#include "TreeInfo.h"
 #include "VectorDrawable.h"
 #ifdef __ANDROID__
 #include "renderthread/CanvasContext.h"
@@ -102,6 +103,12 @@
         info.prepareTextures = false;
         info.canvasContext.unpinImages();
     }
+
+    auto grContext = info.canvasContext.getGrContext();
+    for (auto mesh : mMeshes) {
+        mesh->updateSkMesh(grContext);
+    }
+
 #endif
 
     bool hasBackwardProjectedNodesHere = false;
@@ -168,6 +175,7 @@
 
     mDisplayList.reset();
 
+    mMeshes.clear();
     mMutableImages.clear();
     mVectorDrawables.clear();
     mAnimatedImages.clear();
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 2a67734..7af31a4 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -18,6 +18,7 @@
 
 #include <deque>
 
+#include "Mesh.h"
 #include "RecordingCanvas.h"
 #include "RenderNodeDrawable.h"
 #include "TreeInfo.h"
@@ -167,6 +168,7 @@
     std::deque<RenderNodeDrawable> mChildNodes;
     std::deque<FunctorDrawable*> mChildFunctors;
     std::vector<SkImage*> mMutableImages;
+    std::vector<const Mesh*> mMeshes;
 
 private:
     std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index e1c8877..3ca7eeb 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -321,6 +321,11 @@
     return 0;
 }
 
+void SkiaRecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
+    mDisplayList->mMeshes.push_back(&mesh);
+    mRecorder.drawMesh(mesh, blender, paint);
+}
+
 }  // namespace skiapipeline
 }  // namespace uirenderer
 }  // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 3fd8fa3..a8e4580 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -81,6 +81,7 @@
     virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
 
     virtual void enableZ(bool enableZ) override;
+    virtual void drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) override;
     virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
     virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;