diff options
author | 2023-02-21 17:00:51 -0800 | |
---|---|---|
committer | 2023-02-25 02:36:54 +0000 | |
commit | 5f0a800ad3d3bf24f2e5bb0c666a51c88819bf3e (patch) | |
tree | 59c2f440ed0d1e76821b29d9674468183709e5e1 | |
parent | bbf2ceda7571619c7e77cdfe2597afe130b58499 (diff) |
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
-rw-r--r-- | graphics/java/android/graphics/Mesh.java | 2 | ||||
-rw-r--r-- | libs/hwui/Android.bp | 3 | ||||
-rw-r--r-- | libs/hwui/DisplayListOps.in | 3 | ||||
-rw-r--r-- | libs/hwui/Mesh.cpp | 102 | ||||
-rw-r--r-- | libs/hwui/Mesh.h | 208 | ||||
-rw-r--r-- | libs/hwui/RecordingCanvas.cpp | 38 | ||||
-rw-r--r-- | libs/hwui/RecordingCanvas.h | 16 | ||||
-rw-r--r-- | libs/hwui/SafeMath.h | 107 | ||||
-rw-r--r-- | libs/hwui/SkiaCanvas.cpp | 15 | ||||
-rw-r--r-- | libs/hwui/SkiaCanvas.h | 3 | ||||
-rw-r--r-- | libs/hwui/hwui/Canvas.h | 14 | ||||
-rw-r--r-- | libs/hwui/jni/Mesh.cpp | 227 | ||||
-rw-r--r-- | libs/hwui/jni/Mesh.h | 265 | ||||
-rw-r--r-- | libs/hwui/jni/android_graphics_Canvas.cpp | 7 | ||||
-rw-r--r-- | libs/hwui/jni/android_graphics_Mesh.cpp | 350 | ||||
-rw-r--r-- | libs/hwui/pipeline/skia/SkiaDisplayList.cpp | 8 | ||||
-rw-r--r-- | libs/hwui/pipeline/skia/SkiaDisplayList.h | 2 | ||||
-rw-r--r-- | libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp | 5 | ||||
-rw-r--r-- | libs/hwui/pipeline/skia/SkiaRecordingCanvas.h | 1 |
19 files changed, 859 insertions, 517 deletions
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index 6a1313e16ce5..66fabec91924 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -341,7 +341,6 @@ public class Mesh { * @hide so only calls from module can utilize it */ long getNativeWrapperInstance() { - nativeUpdateMesh(mNativeMeshWrapper, mIsIndexed); return mNativeMeshWrapper; } @@ -383,5 +382,4 @@ public class Mesh { private static native void nativeUpdateUniforms(long builder, String uniformName, int[] values); - private static native void nativeUpdateMesh(long nativeMeshWrapper, boolean mIsIndexed); } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index bcbe706d71a3..536bb49675f1 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -332,6 +332,7 @@ cc_defaults { "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 @@ cc_defaults { "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 @@ cc_defaults { "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 e2127efca716..a18ba1c633b9 100644 --- a/libs/hwui/DisplayListOps.in +++ b/libs/hwui/DisplayListOps.in @@ -52,4 +52,5 @@ X(DrawShadowRec) 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 000000000000..e59bc9565a59 --- /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 000000000000..983681707415 --- /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 659aec0fdf58..0b58406516e3 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 @@ struct DrawVertices final : Op { 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 @@ struct DrawMesh final : Op { 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 @@ struct DrawMesh final : Op { 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::drawVertices(const SkVertices* vert, SkBlendMode mode, con } 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 @@ void RecordingCanvas::onDrawMesh(const SkMesh& mesh, sk_sp<SkBlender> blender, 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 @@ void RecordingCanvas::drawWebView(skiapipeline::FunctorDrawable* drawable) { 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 8409e136b57b..1f4ba5d6d557 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 @@ struct DisplayListOp { 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 @@ private: 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 @@ public: 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 000000000000..4d6adf55c0cb --- /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 d0124f5d4bad..7a1276982d0a 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 @@ void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, cons 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 f2c286a4fb46..b785989f35cb 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -129,8 +129,7 @@ public: 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 2a2019199bda..44ee31d34d23 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -16,25 +16,25 @@ #pragma once +#include <SaveFlags.h> +#include <SkBitmap.h> +#include <SkCanvas.h> +#include <SkMatrix.h> +#include <androidfw/ResourceTypes.h> #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> - class SkAnimatedImage; enum class SkBlendMode; class SkCanvasState; class SkRRect; class SkRuntimeShaderBuilder; class SkVertices; +class Mesh; namespace minikin { class Font; @@ -227,7 +227,7 @@ public: 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 b13d9bafb417..000000000000 --- 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 61c2260e3ad1..000000000000 --- 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 8a4d4e17edb1..8ba750372d18 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 drawVertices(JNIEnv* env, jobject, jlong canvasHandle, 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 000000000000..04339dc8b9a5 --- /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 fcfc4f82abed..af2d3b34bac7 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 @@ bool SkiaDisplayList::prepareListAndChildren( 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 @@ void SkiaDisplayList::reset() { 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 2a677344b7b2..7af31a4dc4c6 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 @@ public: 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 e1c8877a8b7a..3ca7eeb37a89 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -321,6 +321,11 @@ double SkiaRecordingCanvas::drawAnimatedImage(AnimatedImageDrawable* animatedIma 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 3fd8fa3aa9a9..a8e4580dc200 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -81,6 +81,7 @@ public: 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; |