Merge Android 24Q1 Release (ab/11220357)
Bug: 319669529
Merged-In: I46c7859ff042ee7aa9193757e5df8269f4892362
Change-Id: I0c7b5036c0b0f5f2caad551edb063350f6eb87e7
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index e0ccf17..14e8653 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -28,6 +28,19 @@
],
}
+aconfig_declarations {
+ name: "hwui_flags",
+ package: "com.android.graphics.hwui.flags",
+ srcs: [
+ "aconfig/hwui_flags.aconfig",
+ ],
+}
+
+cc_aconfig_library {
+ name: "hwui_flags_cc_lib",
+ aconfig_declarations: "hwui_flags",
+}
+
cc_defaults {
name: "hwui_defaults",
defaults: [
@@ -44,7 +57,7 @@
"-DEGL_EGLEXT_PROTOTYPES",
"-DGL_GLEXT_PROTOTYPES",
"-DATRACE_TAG=ATRACE_TAG_VIEW",
- "-DLOG_TAG=\"OpenGLRenderer\"",
+ "-DLOG_TAG=\"HWUI\"",
"-Wall",
"-Wthread-safety",
"-Wno-unused-parameter",
@@ -122,6 +135,8 @@
"libcrypto",
"libsync",
"libui",
+ "aconfig_text_flags_c_lib",
+ "server_configurable_flags",
],
static_libs: [
"libEGL_blobCache",
@@ -131,6 +146,7 @@
"libstatspull_lazy",
"libstatssocket_lazy",
"libtonemap",
+ "hwui_flags_cc_lib",
],
},
host: {
@@ -520,6 +536,7 @@
"utils/Blur.cpp",
"utils/Color.cpp",
"utils/LinearAllocator.cpp",
+ "utils/TypefaceUtils.cpp",
"utils/VectorDrawableUtils.cpp",
"AnimationContext.cpp",
"Animator.cpp",
@@ -681,11 +698,13 @@
],
static_libs: [
+ "libflagtest",
"libgmock",
"libhwui_static",
],
shared_libs: [
"libmemunreachable",
+ "server_configurable_flags",
],
srcs: [
"tests/unit/main.cpp",
@@ -725,6 +744,7 @@
"tests/unit/TestUtilsTests.cpp",
"tests/unit/ThreadBaseTests.cpp",
"tests/unit/TypefaceTests.cpp",
+ "tests/unit/UnderlineTest.cpp",
"tests/unit/VectorDrawableTests.cpp",
"tests/unit/WebViewFunctorManagerTests.cpp",
],
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index b656b6a..4d020c5 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -16,6 +16,11 @@
#include "AutoBackendTextureRelease.h"
+#include <SkImage.h>
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/GrBackendSurface.h>
+#include <include/gpu/MutableTextureState.h>
#include "renderthread/RenderThread.h"
#include "utils/Color.h"
#include "utils/PaintUtils.h"
@@ -30,15 +35,47 @@
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(buffer, &desc);
bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
- GrBackendFormat backendFormat =
- GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+
+ GrBackendFormat backendFormat;
+ GrBackendApi backend = context->backend();
+ if (backend == GrBackendApi::kOpenGL) {
+ backendFormat =
+ GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeGLBackendTexture(context,
+ buffer,
+ desc.width,
+ desc.height,
+ &mDeleteProc,
+ &mUpdateProc,
+ &mImageCtx,
+ createProtectedImage,
+ backendFormat,
+ false);
+ } else if (backend == GrBackendApi::kVulkan) {
+ backendFormat =
+ GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
+ buffer,
+ desc.format,
+ false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
+ buffer,
+ desc.width,
+ desc.height,
+ &mDeleteProc,
+ &mUpdateProc,
+ &mImageCtx,
+ createProtectedImage,
+ backendFormat,
+ false);
+ } else {
+ LOG_ALWAYS_FATAL("Unexpected backend %d", backend);
+ }
LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(),
__FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32
", AHardwareBuffer_Format==%" PRIu32 ".",
static_cast<int>(context->backend()), desc.format);
- mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture(
- context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx,
- createProtectedImage, backendFormat, false);
LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(),
__FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32
", protected==%d",
@@ -70,7 +107,7 @@
// releaseProc is invoked by SkImage, when texture is no longer in use.
// "releaseContext" contains an "AutoBackendTextureRelease*".
-static void releaseProc(SkImage::ReleaseContext releaseContext) {
+static void releaseProc(SkImages::ReleaseContext releaseContext) {
AutoBackendTextureRelease* textureRelease =
reinterpret_cast<AutoBackendTextureRelease*>(releaseContext);
textureRelease->unref(false);
@@ -83,10 +120,10 @@
AHardwareBuffer_describe(buffer, &desc);
SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
// The following ref will be counteracted by Skia calling releaseProc, either during
- // MakeFromTexture if there is a failure, or later when SkImage is discarded. It must
- // be called before MakeFromTexture, otherwise Skia may remove HWUI's ref on failure.
+ // BorrowTextureFrom if there is a failure, or later when SkImage is discarded. It must
+ // be called before BorrowTextureFrom, otherwise Skia may remove HWUI's ref on failure.
ref();
- mImage = SkImage::MakeFromTexture(
+ mImage = SkImages::BorrowTextureFrom(
context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType,
uirenderer::DataSpaceToColorSpace(dataspace), releaseProc, this);
}
@@ -105,8 +142,8 @@
LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan);
if (mBackendTexture.isValid()) {
// Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout.
- GrBackendSurfaceMutableState newState(VK_IMAGE_LAYOUT_UNDEFINED,
- VK_QUEUE_FAMILY_FOREIGN_EXT);
+ skgpu::MutableTextureState newState(VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_QUEUE_FAMILY_FOREIGN_EXT);
// The unref for this ref happens in the releaseProc passed into setBackendTextureState. The
// releaseProc callback will be made when the work to set the new state has finished on the
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index cd4fae8..b667daf 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -80,6 +80,19 @@
static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
if (transform == ColorTransform::None) return;
+ if (transform == ColorTransform::Invert) {
+ auto filter = SkHighContrastFilter::Make(
+ {/* grayscale= */ false, SkHighContrastConfig::InvertStyle::kInvertLightness,
+ /* contrast= */ 0.0f});
+
+ if (paint.getColorFilter()) {
+ paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter()));
+ } else {
+ paint.setColorFilter(filter);
+ }
+ return;
+ }
+
SkColor newColor = transformColor(transform, paint.getColor());
paint.setColor(newColor);
diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h
index 291f4cf..288dca4 100644
--- a/libs/hwui/CanvasTransform.h
+++ b/libs/hwui/CanvasTransform.h
@@ -29,12 +29,15 @@
Unknown = 0,
Background = 1,
Foreground = 2,
+ // Contains foreground (usually text), like a button or chip
+ Container = 3
};
enum class ColorTransform {
None,
Light,
Dark,
+ Invert
};
// True if the paint was modified, false otherwise
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
new file mode 100644
index 0000000..1a5b938
--- /dev/null
+++ b/libs/hwui/ColorFilter.h
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+#ifndef COLORFILTER_H_
+#define COLORFILTER_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "GraphicsJNI.h"
+#include "SkColorFilter.h"
+#include "SkiaWrapper.h"
+
+namespace android {
+namespace uirenderer {
+
+class ColorFilter : public SkiaWrapper<SkColorFilter> {
+public:
+ static ColorFilter* fromJava(jlong handle) { return reinterpret_cast<ColorFilter*>(handle); }
+
+protected:
+ ColorFilter() = default;
+};
+
+class BlendModeColorFilter : public ColorFilter {
+public:
+ BlendModeColorFilter(SkColor color, SkBlendMode mode) : mColor(color), mMode(mode) {}
+
+private:
+ sk_sp<SkColorFilter> createInstance() override { return SkColorFilters::Blend(mColor, mMode); }
+
+private:
+ const SkColor mColor;
+ const SkBlendMode mMode;
+};
+
+class LightingFilter : public ColorFilter {
+public:
+ LightingFilter(SkColor mul, SkColor add) : mMul(mul), mAdd(add) {}
+
+ void setMul(SkColor mul) {
+ mMul = mul;
+ discardInstance();
+ }
+
+ void setAdd(SkColor add) {
+ mAdd = add;
+ discardInstance();
+ }
+
+private:
+ sk_sp<SkColorFilter> createInstance() override { return SkColorFilters::Lighting(mMul, mAdd); }
+
+private:
+ SkColor mMul;
+ SkColor mAdd;
+};
+
+class ColorMatrixColorFilter : public ColorFilter {
+public:
+ ColorMatrixColorFilter(std::vector<float>&& matrix) : mMatrix(std::move(matrix)) {}
+
+ void setMatrix(std::vector<float>&& matrix) {
+ mMatrix = std::move(matrix);
+ discardInstance();
+ }
+
+private:
+ sk_sp<SkColorFilter> createInstance() override {
+ return SkColorFilters::Matrix(mMatrix.data());
+ }
+
+private:
+ std::vector<float> mMatrix;
+};
+
+} // namespace uirenderer
+} // namespace android
+
+#endif // COLORFILTER_H_
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index a8d170d..fd27641 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -242,6 +242,47 @@
}
}
+SkRect DamageAccumulator::computeClipAndTransform(const SkRect& bounds, Matrix4* outMatrix) const {
+ const DirtyStack* frame = mHead;
+ Matrix4 transform;
+ SkRect pretransformResult = bounds;
+ while (true) {
+ SkRect currentBounds = pretransformResult;
+ pretransformResult.setEmpty();
+ switch (frame->type) {
+ case TransformRenderNode: {
+ const RenderProperties& props = frame->renderNode->properties();
+ // Perform clipping
+ if (props.getClipDamageToBounds() && !currentBounds.isEmpty()) {
+ if (!currentBounds.intersect(
+ SkRect::MakeIWH(props.getWidth(), props.getHeight()))) {
+ currentBounds.setEmpty();
+ }
+ }
+
+ // apply all transforms
+ mapRect(props, currentBounds, &pretransformResult);
+ frame->renderNode->applyViewPropertyTransforms(transform);
+ } break;
+ case TransformMatrix4:
+ mapRect(frame->matrix4, currentBounds, &pretransformResult);
+ transform.multiply(*frame->matrix4);
+ break;
+ default:
+ pretransformResult = currentBounds;
+ break;
+ }
+ if (frame->prev == frame) break;
+ frame = frame->prev;
+ }
+ SkRect result;
+ Matrix4 globalToLocal;
+ globalToLocal.loadInverse(transform);
+ mapRect(&globalToLocal, pretransformResult, &result);
+ *outMatrix = transform;
+ return result;
+}
+
void DamageAccumulator::dirty(float left, float top, float right, float bottom) {
mHead->pendingDirty.join({left, top, right, bottom});
}
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index c4249af..30bf706 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -61,6 +61,8 @@
void computeCurrentTransform(Matrix4* outMatrix) const;
+ SkRect computeClipAndTransform(const SkRect& bounds, Matrix4* outMatrix) const;
+
void finish(SkRect* totalDirty);
struct StretchResult {
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index eb5878d..b1c5bf4 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -54,6 +54,8 @@
mImpl->updateChildren(std::move(updateFn));
}
+ void visit(std::function<void(const RenderNode&)> func) const { mImpl->visit(std::move(func)); }
+
[[nodiscard]] explicit operator bool() const {
return mImpl.get() != nullptr;
}
@@ -143,6 +145,8 @@
return mImpl && mImpl->hasText();
}
+ [[nodiscard]] bool hasFill() const { return mImpl && mImpl->hasFill(); }
+
void applyColorTransform(ColorTransform transform) {
if (mImpl) {
mImpl->applyColorTransform(transform);
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index a18ba1c..d21f07ef 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-X(Flush)
X(Save)
X(Restore)
X(SaveLayer)
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
new file mode 100644
index 0000000..00d049c
--- /dev/null
+++ b/libs/hwui/FeatureFlags.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HWUI_FEATURE_FLAGS_H
+#define ANDROID_HWUI_FEATURE_FLAGS_H
+
+#ifdef __ANDROID__
+#include <com_android_text_flags.h>
+#endif // __ANDROID__
+
+namespace android {
+
+namespace text_feature {
+
+inline bool fix_double_underline() {
+#ifdef __ANDROID__
+ return com_android_text_flags_fix_double_underline();
+#else
+ return true;
+#endif // __ANDROID__
+}
+
+inline bool deprecate_ui_fonts() {
+#ifdef __ANDROID__
+ return com_android_text_flags_deprecate_ui_fonts();
+#else
+ return true;
+#endif // __ANDROID__
+}
+
+} // namespace text_feature
+
+} // namespace android
+
+#endif // ANDROID_HWUI_FEATURE_FLAGS_H
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index 8191f5e..a958a09 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -15,6 +15,8 @@
*/
#include "FrameInfo.h"
+#include <gui/TraceUtils.h>
+
#include <cstring>
namespace android {
@@ -51,6 +53,30 @@
void FrameInfo::importUiThreadInfo(int64_t* info) {
memcpy(mFrameInfo, info, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
+ mSkippedFrameReason.reset();
+}
+
+const char* toString(SkippedFrameReason reason) {
+ switch (reason) {
+ case SkippedFrameReason::DrawingOff:
+ return "DrawingOff";
+ case SkippedFrameReason::ContextIsStopped:
+ return "ContextIsStopped";
+ case SkippedFrameReason::NothingToDraw:
+ return "NothingToDraw";
+ case SkippedFrameReason::NoOutputTarget:
+ return "NoOutputTarget";
+ case SkippedFrameReason::NoBuffer:
+ return "NoBuffer";
+ case SkippedFrameReason::AlreadyDrawn:
+ return "AlreadyDrawn";
+ }
+}
+
+void FrameInfo::setSkippedFrameReason(android::uirenderer::SkippedFrameReason reason) {
+ ATRACE_FORMAT_INSTANT("Frame skipped: %s", toString(reason));
+ addFlag(FrameInfoFlags::SkippedFrame);
+ mSkippedFrameReason = reason;
}
} /* namespace uirenderer */
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index b15b6cb..f7ad139 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -16,15 +16,17 @@
#ifndef FRAMEINFO_H_
#define FRAMEINFO_H_
-#include "utils/Macros.h"
-
#include <cutils/compiler.h>
+#include <memory.h>
#include <utils/Timers.h>
#include <array>
-#include <memory.h>
+#include <optional>
#include <string>
+#include "SkippedFrameInfo.h"
+#include "utils/Macros.h"
+
namespace android {
namespace uirenderer {
@@ -186,8 +188,14 @@
return mFrameInfo[static_cast<int>(index)];
}
+ void setSkippedFrameReason(SkippedFrameReason reason);
+ inline std::optional<SkippedFrameReason> getSkippedFrameReason() const {
+ return mSkippedFrameReason;
+ }
+
private:
int64_t mFrameInfo[static_cast<int>(FrameInfoIndex::NumIndexes)];
+ std::optional<SkippedFrameReason> mSkippedFrameReason;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 687e4dd..59f2169 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -148,7 +148,7 @@
int fast_i = 0, janky_i = 0;
// Set the bottom of all the shapes to the baseline
for (int fi = mFrameSource.size() - 1; fi >= 0; fi--) {
- if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
+ if (mFrameSource[fi].getSkippedFrameReason()) {
continue;
}
float lineWidth = baseLineWidth;
@@ -181,7 +181,7 @@
int janky_i = (mNumJankyRects - 1) * 4;
for (size_t fi = 0; fi < mFrameSource.size(); fi++) {
- if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
+ if (mFrameSource[fi].getSkippedFrameReason()) {
continue;
}
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index b7e9999..16de21d 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -22,9 +22,11 @@
#include <GLES2/gl2ext.h>
#include <GLES3/gl3.h>
#include <GrDirectContext.h>
+#include <GrTypes.h>
#include <SkBitmap.h>
#include <SkCanvas.h>
#include <SkImage.h>
+#include <SkImageAndroid.h>
#include <SkImageInfo.h>
#include <SkRefCnt.h>
#include <gui/TraceUtils.h>
@@ -262,8 +264,9 @@
}
sk_sp<SkImage> image =
- SkImage::MakeFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(), ahb);
- mGrContext->submit(true);
+ SkImages::TextureFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(),
+ ahb);
+ mGrContext->submit(GrSyncCpu::kYes);
uploadSucceeded = (image.get() != nullptr);
});
diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp
index ca1312e7..21f4ca7 100644
--- a/libs/hwui/MemoryPolicy.cpp
+++ b/libs/hwui/MemoryPolicy.cpp
@@ -28,7 +28,10 @@
constexpr static MemoryPolicy sDefaultMemoryPolicy;
constexpr static MemoryPolicy sPersistentOrSystemPolicy{
.contextTimeout = 10_s,
+ .minimumResourceRetention = 1_s,
+ .maximumResourceRetention = 10_s,
.useAlternativeUiHidden = true,
+ .purgeScratchOnly = false,
};
constexpr static MemoryPolicy sLowRamPolicy{
.useAlternativeUiHidden = true,
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index 347daf34..e10dda9 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -53,6 +53,8 @@
// The minimum amount of time to hold onto items in the resource cache
// The actual time used will be the max of this & when frames were actually rendered
nsecs_t minimumResourceRetention = 10_s;
+ // The maximum amount of time to hold onto items in the resource cache
+ nsecs_t maximumResourceRetention = 100000_s;
// If false, use only TRIM_UI_HIDDEN to drive background cache limits;
// If true, use all signals (such as all contexts are stopped) to drive the limits
bool useAlternativeUiHidden = true;
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
index 13e3c8e..69fda34 100644
--- a/libs/hwui/Mesh.h
+++ b/libs/hwui/Mesh.h
@@ -19,6 +19,7 @@
#include <GrDirectContext.h>
#include <SkMesh.h>
+#include <include/gpu/ganesh/SkMeshGanesh.h>
#include <jni.h>
#include <log/log.h>
@@ -143,21 +144,34 @@
}
if (mIsDirty || genId != mGenerationId) {
- auto vb = SkMesh::MakeVertexBuffer(
- context, reinterpret_cast<const void*>(mVertexBufferData.data()),
- mVertexBufferData.size());
+ auto vertexData = reinterpret_cast<const void*>(mVertexBufferData.data());
+#ifdef __ANDROID__
+ auto vb = SkMeshes::MakeVertexBuffer(context,
+ vertexData,
+ mVertexBufferData.size());
+#else
+ auto vb = SkMeshes::MakeVertexBuffer(vertexData,
+ mVertexBufferData.size());
+#endif
auto meshMode = SkMesh::Mode(mMode);
if (!mIndexBufferData.empty()) {
- auto ib = SkMesh::MakeIndexBuffer(
- context, reinterpret_cast<const void*>(mIndexBufferData.data()),
- mIndexBufferData.size());
+ auto indexData = reinterpret_cast<const void*>(mIndexBufferData.data());
+#ifdef __ANDROID__
+ auto ib = SkMeshes::MakeIndexBuffer(context,
+ indexData,
+ mIndexBufferData.size());
+#else
+ auto ib = SkMeshes::MakeIndexBuffer(indexData,
+ mIndexBufferData.size());
+#endif
mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
- mBounds)
+ SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
.mesh;
} else {
mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
- mBuilder->fUniforms, mBounds)
+ mBuilder->fUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
+ mBounds)
.mesh;
}
mIsDirty = false;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 09d3f05..6c3172a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -20,15 +20,26 @@
#ifdef __ANDROID__
#include "HWUIProperties.sysprop.h"
#endif
-#include "SkTraceEventCommon.h"
+#include <android-base/properties.h>
+#include <cutils/compiler.h>
+#include <log/log.h>
#include <algorithm>
#include <cstdlib>
#include <optional>
-#include <android-base/properties.h>
-#include <cutils/compiler.h>
-#include <log/log.h>
+#include "src/core/SkTraceEventCommon.h"
+
+#ifdef __ANDROID__
+#include <com_android_graphics_hwui_flags.h>
+namespace hwui_flags = com::android::graphics::hwui::flags;
+#else
+namespace hwui_flags {
+constexpr bool clip_surfaceviews() {
+ return false;
+}
+} // namespace hwui_flags
+#endif
namespace android {
namespace uirenderer {
@@ -92,6 +103,8 @@
float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number
+bool Properties::clipSurfaceViews = false;
+
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
@@ -159,6 +172,9 @@
// call isDrawingEnabled to force loading of the property
isDrawingEnabled();
+ clipSurfaceViews =
+ base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
+
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index bb47744..bca57e9 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -325,6 +325,8 @@
static float maxHdrHeadroomOn8bit;
+ static bool clipSurfaceViews;
+
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
}
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 045de35..afe4c38 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -21,6 +21,7 @@
#include <SkCanvas.h>
#include <SkColorSpace.h>
#include <SkImage.h>
+#include <SkImageAndroid.h>
#include <SkImageInfo.h>
#include <SkMatrix.h>
#include <SkPaint.h>
@@ -29,6 +30,7 @@
#include <SkSamplingOptions.h>
#include <SkSurface.h>
#include "include/gpu/GpuTypes.h" // from Skia
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <gui/TraceUtils.h>
#include <private/android/AHardwareBufferHelpers.h>
#include <shaders/shaders.h>
@@ -108,7 +110,8 @@
sk_sp<SkColorSpace> colorSpace =
DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace));
sk_sp<SkImage> image =
- SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
+ SkImages::DeferredFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType,
+ colorSpace);
if (!image.get()) {
return request->onCopyFinished(CopyResult::UnknownError);
@@ -171,16 +174,16 @@
SkBitmap skBitmap = request->getDestinationBitmap(srcRect.width(), srcRect.height());
SkBitmap* bitmap = &skBitmap;
sk_sp<SkSurface> tmpSurface =
- SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes,
- bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
+ SkSurfaces::RenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes,
+ bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
// if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we
// attempt to do the intermediate rendering step in 8888
if (!tmpSurface.get()) {
SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType);
- tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes,
- tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
+ tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes,
+ tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
if (!tmpSurface.get()) {
ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
return request->onCopyFinished(CopyResult::UnknownError);
@@ -346,19 +349,19 @@
* a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a
* software buffer.
*/
- sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes,
- bitmap->info(),
- 0,
- kTopLeft_GrSurfaceOrigin, nullptr);
+ sk_sp<SkSurface> tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes,
+ bitmap->info(),
+ 0,
+ kTopLeft_GrSurfaceOrigin, nullptr);
// if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we
// attempt to do the intermediate rendering step in 8888
if (!tmpSurface.get()) {
SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType);
- tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes,
- tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
+ tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes,
+ tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
if (!tmpSurface.get()) {
ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
return false;
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 924fbd6..3b694c5 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -36,6 +36,7 @@
#include "SkImageFilter.h"
#include "SkImageInfo.h"
#include "SkLatticeIter.h"
+#include "SkMesh.h"
#include "SkPaint.h"
#include "SkPicture.h"
#include "SkRRect.h"
@@ -49,6 +50,7 @@
#include "effects/GainmapRenderer.h"
#include "include/gpu/GpuTypes.h" // from Skia
#include "include/gpu/GrDirectContext.h"
+#include "include/gpu/ganesh/SkMeshGanesh.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include "pipeline/skia/FunctorDrawable.h"
#ifdef __ANDROID__
@@ -107,11 +109,6 @@
};
static_assert(sizeof(Op) == 4, "");
-struct Flush final : Op {
- static const auto kType = Type::Flush;
- void draw(SkCanvas* c, const SkMatrix&) const { c->flush(); }
-};
-
struct Save final : Op {
static const auto kType = Type::Save;
void draw(SkCanvas* c, const SkMatrix&) const { c->save(); }
@@ -532,24 +529,26 @@
mutable bool isGpuBased;
mutable GrDirectContext::DirectContextID contextId;
void draw(SkCanvas* c, const SkMatrix&) const {
+#ifdef __ANDROID__
GrDirectContext* directContext = c->recordingContext()->asDirectContext();
GrDirectContext::DirectContextID id = directContext->directContextID();
if (!isGpuBased || contextId != id) {
sk_sp<SkMesh::VertexBuffer> vb =
- SkMesh::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer());
+ SkMeshes::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer());
if (!cpuMesh.indexBuffer()) {
gpuMesh = SkMesh::Make(cpuMesh.refSpec(), cpuMesh.mode(), vb, cpuMesh.vertexCount(),
cpuMesh.vertexOffset(), cpuMesh.refUniforms(),
- cpuMesh.bounds())
+ SkSpan<SkRuntimeEffect::ChildPtr>(), cpuMesh.bounds())
.mesh;
} else {
sk_sp<SkMesh::IndexBuffer> ib =
- SkMesh::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer());
+ SkMeshes::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer());
gpuMesh = SkMesh::MakeIndexed(cpuMesh.refSpec(), cpuMesh.mode(), vb,
cpuMesh.vertexCount(), cpuMesh.vertexOffset(), ib,
cpuMesh.indexCount(), cpuMesh.indexOffset(),
- cpuMesh.refUniforms(), cpuMesh.bounds())
+ cpuMesh.refUniforms(),
+ SkSpan<SkRuntimeEffect::ChildPtr>(), cpuMesh.bounds())
.mesh;
}
@@ -558,6 +557,9 @@
}
c->drawMesh(gpuMesh, blender, paint);
+#else
+ c->drawMesh(cpuMesh, blender, paint);
+#endif
}
};
@@ -675,12 +677,11 @@
// because the webview functor still doesn't respect the canvas clip stack.
const SkIRect deviceBounds = c->getDeviceClipBounds();
if (mLayerSurface == nullptr || c->imageInfo() != mLayerImageInfo) {
- GrRecordingContext* directContext = c->recordingContext();
mLayerImageInfo =
c->imageInfo().makeWH(deviceBounds.width(), deviceBounds.height());
- mLayerSurface = SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes,
- mLayerImageInfo, 0,
- kTopLeft_GrSurfaceOrigin, nullptr);
+ // SkCanvas::makeSurface returns a new surface that will be GPU-backed if
+ // canvas was also.
+ mLayerSurface = c->makeSurface(mLayerImageInfo);
}
SkCanvas* layerCanvas = mLayerSurface->getCanvas();
@@ -717,6 +718,27 @@
return (value & (value - 1)) == 0;
}
+template <typename T>
+constexpr bool doesPaintHaveFill(T& paint) {
+ using T1 = std::remove_cv_t<T>;
+ if constexpr (std::is_same_v<T1, SkPaint>) {
+ return paint.getStyle() != SkPaint::Style::kStroke_Style;
+ } else if constexpr (std::is_same_v<T1, SkPaint&>) {
+ return paint.getStyle() != SkPaint::Style::kStroke_Style;
+ } else if constexpr (std::is_same_v<T1, SkPaint*>) {
+ return paint && paint->getStyle() != SkPaint::Style::kStroke_Style;
+ } else if constexpr (std::is_same_v<T1, const SkPaint*>) {
+ return paint && paint->getStyle() != SkPaint::Style::kStroke_Style;
+ }
+
+ return false;
+}
+
+template <typename... Args>
+constexpr bool hasPaintWithFill(Args&&... args) {
+ return (... || doesPaintHaveFill(args));
+}
+
template <typename T, typename... Args>
void* DisplayListData::push(size_t pod, Args&&... args) {
size_t skip = SkAlignPtr(sizeof(T) + pod);
@@ -735,6 +757,14 @@
new (op) T{std::forward<Args>(args)...};
op->type = (uint32_t)T::kType;
op->skip = skip;
+
+ // check if this is a fill op or not, in case we need to avoid messing with it with force invert
+ if constexpr (!std::is_same_v<T, DrawTextBlob>) {
+ if (hasPaintWithFill(args...)) {
+ mHasFill = true;
+ }
+ }
+
return op + 1;
}
@@ -752,10 +782,6 @@
}
}
-void DisplayListData::flush() {
- this->push<Flush>(0);
-}
-
void DisplayListData::save() {
this->push<Save>(0);
}
@@ -1047,10 +1073,6 @@
return nullptr;
}
-void RecordingCanvas::onFlush() {
- fDL->flush();
-}
-
void RecordingCanvas::willSave() {
mSaveCount++;
fDL->save();
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 1f4ba5d..afadbfd 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -110,7 +110,7 @@
class DisplayListData final {
public:
- DisplayListData() : mHasText(false) {}
+ DisplayListData() : mHasText(false), mHasFill(false) {}
~DisplayListData();
void draw(SkCanvas* canvas) const;
@@ -121,14 +121,13 @@
void applyColorTransform(ColorTransform transform);
bool hasText() const { return mHasText; }
+ bool hasFill() const { return mHasFill; }
size_t usedSize() const { return fUsed; }
size_t allocatedSize() const { return fReserved; }
private:
friend class RecordingCanvas;
- void flush();
-
void save();
void saveLayer(const SkRect*, const SkPaint*, const SkImageFilter*, SkCanvas::SaveLayerFlags);
void saveBehind(const SkRect*);
@@ -194,6 +193,7 @@
size_t fReserved = 0;
bool mHasText : 1;
+ bool mHasFill : 1;
};
class RecordingCanvas final : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> {
@@ -208,8 +208,6 @@
void willRestore() override;
bool onDoSaveBehind(const SkRect*) override;
- void onFlush() override;
-
void didConcat44(const SkM44&) override;
void didSetM44(const SkM44&) override;
void didScale(SkScalar, SkScalar) override;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index a733d17..f526a28 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -28,16 +28,21 @@
#include "DamageAccumulator.h"
#include "pipeline/skia/SkiaDisplayList.h"
#endif
-#include <gui/TraceUtils.h>
-#include "utils/MathUtils.h"
-#include "utils/StringUtils.h"
-
#include <SkPathOps.h>
+#include <gui/TraceUtils.h>
+#include <ui/FatVector.h>
+
#include <algorithm>
#include <atomic>
#include <sstream>
#include <string>
-#include <ui/FatVector.h>
+
+#ifdef __ANDROID__
+#include "include/gpu/ganesh/SkImageGanesh.h"
+#endif
+#include "utils/ForceDark.h"
+#include "utils/MathUtils.h"
+#include "utils/StringUtils.h"
namespace android {
namespace uirenderer {
@@ -109,6 +114,13 @@
output << std::endl;
}
+void RenderNode::visit(std::function<void(const RenderNode&)> func) const {
+ func(*this);
+ if (mDisplayList) {
+ mDisplayList.visit(func);
+ }
+}
+
int RenderNode::getUsageSize() {
int size = sizeof(RenderNode);
size += mStagingDisplayList.getUsedSize();
@@ -363,13 +375,18 @@
mImageFilterClipBounds != clipBounds ||
mTargetImageFilterLayerSurfaceGenerationId != layerSurfaceGenerationId) {
// Otherwise create a new snapshot with the given filter and snapshot
- mSnapshotResult.snapshot =
- snapshot->makeWithFilter(context,
- imageFilter,
- subset,
- clipBounds,
- &mSnapshotResult.outSubset,
- &mSnapshotResult.outOffset);
+#ifdef __ANDROID__
+ if (context) {
+ mSnapshotResult.snapshot = SkImages::MakeWithFilter(
+ context, snapshot, imageFilter, subset, clipBounds, &mSnapshotResult.outSubset,
+ &mSnapshotResult.outOffset);
+ } else
+#endif
+ {
+ mSnapshotResult.snapshot = SkImages::MakeWithFilter(
+ snapshot, imageFilter, subset, clipBounds, &mSnapshotResult.outSubset,
+ &mSnapshotResult.outOffset);
+ }
mTargetImageFilter = sk_ref_sp(imageFilter);
mImageFilterClipBounds = clipBounds;
mTargetImageFilterLayerSurfaceGenerationId = layerSurfaceGenerationId;
@@ -387,16 +404,21 @@
deleteDisplayList(observer, info);
mDisplayList = std::move(mStagingDisplayList);
if (mDisplayList) {
- WebViewSyncData syncData {
- .applyForceDark = info && !info->disableForceDark
- };
+ WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info)};
mDisplayList.syncContents(syncData);
handleForceDark(info);
}
}
+inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) {
+ return CC_UNLIKELY(
+ info &&
+ (!info->disableForceDark ||
+ info->forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK));
+}
+
void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
- if (CC_LIKELY(!info || info->disableForceDark)) {
+ if (!shouldEnableForceDark(info)) {
return;
}
auto usage = usageHint();
@@ -405,7 +427,13 @@
children.push_back(node);
});
if (mDisplayList.hasText()) {
- usage = UsageHint::Foreground;
+ if (mDisplayList.hasFill()) {
+ // Handle a special case for custom views that draw both text and background in the
+ // same RenderNode, which would otherwise be altered to white-on-white text.
+ usage = UsageHint::Container;
+ } else {
+ usage = UsageHint::Foreground;
+ }
}
if (usage == UsageHint::Unknown) {
if (children.size() > 1) {
@@ -431,8 +459,13 @@
drawn.join(bounds);
}
}
- mDisplayList.applyColorTransform(
- usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light);
+
+ if (usage == UsageHint::Container) {
+ mDisplayList.applyColorTransform(ColorTransform::Invert);
+ } else {
+ mDisplayList.applyColorTransform(usage == UsageHint::Background ? ColorTransform::Dark
+ : ColorTransform::Light);
+ }
}
void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 4d03bf1..c904542 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -129,10 +129,6 @@
StretchMask& getStretchMask() { return mStretchMask; }
- VirtualLightRefBase* getUserContext() const { return mUserContext.get(); }
-
- void setUserContext(VirtualLightRefBase* context) { mUserContext = context; }
-
bool isPropertyFieldDirty(DirtyPropertyMask field) const {
return mDirtyPropertyFields & field;
}
@@ -215,6 +211,8 @@
void output(std::ostream& output, uint32_t level);
+ void visit(std::function<void(const RenderNode&)>) const;
+
void setUsageHint(UsageHint usageHint) { mUsageHint = usageHint; }
UsageHint usageHint() const { return mUsageHint; }
@@ -222,6 +220,7 @@
int64_t uniqueId() const { return mUniqueId; }
void setIsTextureView() { mIsTextureView = true; }
+ bool isTextureView() const { return mIsTextureView; }
void markDrawStart(SkCanvas& canvas);
void markDrawEnd(SkCanvas& canvas);
@@ -234,6 +233,7 @@
void syncProperties();
void syncDisplayList(TreeObserver& observer, TreeInfo* info);
void handleForceDark(TreeInfo* info);
+ bool shouldEnableForceDark(TreeInfo* info);
void prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer);
void pushStagingPropertiesChanges(TreeInfo& info);
@@ -248,7 +248,6 @@
const int64_t mUniqueId;
String8 mName;
- sp<VirtualLightRefBase> mUserContext;
uint32_t mDirtyPropertyFields;
RenderProperties mProperties;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index ea9b6c9..008ea3a 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -47,6 +47,7 @@
#include <utility>
#include "CanvasProperty.h"
+#include "FeatureFlags.h"
#include "Mesh.h"
#include "NinePatchUtils.h"
#include "VectorDrawable.h"
@@ -825,7 +826,9 @@
sk_sp<SkTextBlob> textBlob(builder.make());
applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); });
- drawTextDecorations(x, y, totalAdvance, paintCopy);
+ if (!text_feature::fix_double_underline()) {
+ drawTextDecorations(x, y, totalAdvance, paintCopy);
+ }
}
void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 9cb50ed..4bf1790 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -175,7 +175,7 @@
const Paint& paint, const SkPath& path, size_t start,
size_t end) override;
- void onFilterPaint(Paint& paint);
+ virtual void onFilterPaint(Paint& paint);
Paint filterPaint(const Paint& src) {
Paint dst(src);
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index b58f517..c67b135 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -18,9 +18,8 @@
#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
-#include "include/private/SkFixed.h"
-#include "src/core/SkTSearch.h"
+#include <cstdlib>
#include <log/log.h>
typedef int Dot14;
@@ -41,18 +40,18 @@
if (x <= 0) {
return 0;
}
- if (x >= SK_Scalar1) {
+ if (x >= 1.0f) {
return Dot14_ONE;
}
- return SkScalarToFixed(x) >> 2;
+ return static_cast<Dot14>(x * Dot14_ONE);
}
static float SkUnitCubicInterp(float value, float bx, float by, float cx, float cy) {
// pin to the unit-square, and convert to 2.14
Dot14 x = pin_and_convert(value);
- if (x == 0) return 0;
- if (x == Dot14_ONE) return SK_Scalar1;
+ if (x == 0) return 0.0f;
+ if (x == Dot14_ONE) return 1.0f;
Dot14 b = pin_and_convert(bx);
Dot14 c = pin_and_convert(cx);
@@ -84,7 +83,7 @@
A = 3 * b;
B = 3 * (c - 2 * b);
C = 3 * (b - c) + Dot14_ONE;
- return SkFixedToScalar(eval_cubic(t, A, B, C) << 2);
+ return Dot14ToFloat(eval_cubic(t, A, B, C));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -104,7 +103,7 @@
fFlags = 0;
fElemCount = static_cast<uint8_t>(elemCount);
fFrameCount = static_cast<int16_t>(frameCount);
- fRepeat = SK_Scalar1;
+ fRepeat = 1.0f;
if (fStorage) {
free(fStorage);
fStorage = nullptr;
@@ -136,17 +135,46 @@
float SkiaInterpolatorBase::ComputeRelativeT(SkMSec time, SkMSec prevTime, SkMSec nextTime,
const float blend[4]) {
- SkASSERT(time > prevTime && time < nextTime);
+ LOG_FATAL_IF(time < prevTime || time > nextTime);
float t = (float)(time - prevTime) / (float)(nextTime - prevTime);
return blend ? SkUnitCubicInterp(t, blend[0], blend[1], blend[2], blend[3]) : t;
}
+// Returns the index of where the item is or the bit not of the index
+// where the item should go in order to keep arr sorted in ascending order.
+int SkiaInterpolatorBase::binarySearch(const SkTimeCode* arr, int count, SkMSec target) {
+ if (count <= 0) {
+ return ~0;
+ }
+
+ int lo = 0;
+ int hi = count - 1;
+
+ while (lo < hi) {
+ int mid = (hi + lo) / 2;
+ SkMSec elem = arr[mid].fTime;
+ if (elem == target) {
+ return mid;
+ } else if (elem < target) {
+ lo = mid + 1;
+ } else {
+ hi = mid;
+ }
+ }
+ // Check to see if target is greater or less than where we stopped
+ if (target < arr[lo].fTime) {
+ return ~lo;
+ }
+ // e.g. it should go at the end.
+ return ~(lo + 1);
+}
+
SkiaInterpolatorBase::Result SkiaInterpolatorBase::timeToT(SkMSec time, float* T, int* indexPtr,
bool* exactPtr) const {
- SkASSERT(fFrameCount > 0);
+ LOG_FATAL_IF(fFrameCount <= 0);
Result result = kNormal_Result;
- if (fRepeat != SK_Scalar1) {
+ if (fRepeat != 1.0f) {
SkMSec startTime = 0, endTime = 0; // initialize to avoid warning
this->getDuration(&startTime, &endTime);
SkMSec totalTime = endTime - startTime;
@@ -168,10 +196,8 @@
time = offsetTime + startTime;
}
- int index = SkTSearch<SkMSec>(&fTimes[0].fTime, fFrameCount, time, sizeof(SkTimeCode));
-
+ int index = SkiaInterpolatorBase::binarySearch(fTimes, fFrameCount, time);
bool exact = true;
-
if (index < 0) {
index = ~index;
if (index == 0) {
@@ -184,10 +210,11 @@
}
result = kFreezeEnd_Result;
} else {
+ // Need to interpolate between two frames.
exact = false;
}
}
- SkASSERT(index < fFrameCount);
+ LOG_FATAL_IF(index >= fFrameCount);
const SkTimeCode* nextTime = &fTimes[index];
SkMSec nextT = nextTime[0].fTime;
if (exact) {
@@ -207,7 +234,7 @@
}
SkiaInterpolator::SkiaInterpolator(int elemCount, int frameCount) {
- SkASSERT(elemCount > 0);
+ LOG_FATAL_IF(elemCount <= 0);
this->reset(elemCount, frameCount);
}
@@ -221,21 +248,19 @@
fValues = (float*)((char*)fStorage + sizeof(SkTimeCode) * frameCount);
}
-#define SK_Fixed1Third (SK_Fixed1 / 3)
-#define SK_Fixed2Third (SK_Fixed1 * 2 / 3)
-
static const float gIdentityBlend[4] = {0.33333333f, 0.33333333f, 0.66666667f, 0.66666667f};
bool SkiaInterpolator::setKeyFrame(int index, SkMSec time, const float values[],
const float blend[4]) {
- SkASSERT(values != nullptr);
+ LOG_FATAL_IF(values == nullptr);
if (blend == nullptr) {
blend = gIdentityBlend;
}
- bool success = ~index == SkTSearch<SkMSec>(&fTimes->fTime, index, time, sizeof(SkTimeCode));
- SkASSERT(success);
+ // Verify the time should go after all the frames before index
+ bool success = ~index == SkiaInterpolatorBase::binarySearch(fTimes, index, time);
+ LOG_FATAL_IF(!success);
if (success) {
SkTimeCode* timeCode = &fTimes[index];
timeCode->fTime = time;
@@ -257,7 +282,7 @@
if (exact) {
memcpy(values, nextSrc, fElemCount * sizeof(float));
} else {
- SkASSERT(index > 0);
+ LOG_FATAL_IF(index <= 0);
const float* prevSrc = nextSrc - fElemCount;
diff --git a/libs/hwui/SkiaInterpolator.h b/libs/hwui/SkiaInterpolator.h
index 9422cb5..62e6c1e 100644
--- a/libs/hwui/SkiaInterpolator.h
+++ b/libs/hwui/SkiaInterpolator.h
@@ -68,14 +68,16 @@
enum Flags { kMirror = 1, kReset = 2, kHasBlend = 4 };
static float ComputeRelativeT(uint32_t time, uint32_t prevTime, uint32_t nextTime,
const float blend[4] = nullptr);
- int16_t fFrameCount;
- uint8_t fElemCount;
- uint8_t fFlags;
- float fRepeat;
struct SkTimeCode {
uint32_t fTime;
float fBlend[4];
};
+ static int binarySearch(const SkTimeCode* arr, int count, uint32_t target);
+
+ int16_t fFrameCount;
+ uint8_t fElemCount;
+ uint8_t fFlags;
+ float fRepeat;
SkTimeCode* fTimes; // pointer into fStorage
void* fStorage;
};
diff --git a/libs/hwui/SkiaWrapper.h b/libs/hwui/SkiaWrapper.h
new file mode 100644
index 0000000..bd0e35a
--- /dev/null
+++ b/libs/hwui/SkiaWrapper.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#ifndef SKIA_WRAPPER_H_
+#define SKIA_WRAPPER_H_
+
+#include <SkRefCnt.h>
+#include <utils/RefBase.h>
+
+namespace android::uirenderer {
+
+template <typename T>
+class SkiaWrapper : public VirtualLightRefBase {
+public:
+ sk_sp<T> getInstance() {
+ if (mInstance != nullptr && shouldDiscardInstance()) {
+ mInstance = nullptr;
+ }
+
+ if (mInstance == nullptr) {
+ mInstance = createInstance();
+ mGenerationId++;
+ }
+ return mInstance;
+ }
+
+ virtual bool shouldDiscardInstance() const { return false; }
+
+ void discardInstance() { mInstance = nullptr; }
+
+ [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; }
+
+protected:
+ virtual sk_sp<T> createInstance() = 0;
+
+private:
+ sk_sp<T> mInstance = nullptr;
+ int32_t mGenerationId = 0;
+};
+
+} // namespace android::uirenderer
+
+#endif // SKIA_WRAPPER_H_
diff --git a/libs/hwui/SkippedFrameInfo.h b/libs/hwui/SkippedFrameInfo.h
new file mode 100644
index 0000000..de56d9a
--- /dev/null
+++ b/libs/hwui/SkippedFrameInfo.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+namespace android::uirenderer {
+
+enum class SkippedFrameReason {
+ DrawingOff,
+ ContextIsStopped,
+ NothingToDraw,
+ NoOutputTarget,
+ NoBuffer,
+ AlreadyDrawn,
+};
+
+} /* namespace android::uirenderer */
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
index 974a5d0..ae29edf 100644
--- a/libs/hwui/Tonemapper.cpp
+++ b/libs/hwui/Tonemapper.cpp
@@ -20,6 +20,7 @@
#include <log/log.h>
// libshaders only exists on Android devices
#ifdef __ANDROID__
+#include <renderthread/CanvasContext.h>
#include <shaders/shaders.h>
#endif
@@ -53,8 +54,17 @@
ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect));
+ auto colorTransform = android::mat4();
+ const auto* context = renderthread::CanvasContext::getActiveContext();
+ if (context) {
+ const auto ratio = context->targetSdrHdrRatio();
+ if (ratio > 1.0f) {
+ colorTransform = android::mat4::scale(vec4(ratio, ratio, ratio, 1.f));
+ }
+ }
+
const auto uniforms =
- shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance,
+ shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance,
currentDisplayLuminanceNits, maxLuminance);
for (const auto& uniform : uniforms) {
diff --git a/libs/hwui/TreeInfo.cpp b/libs/hwui/TreeInfo.cpp
index 750f869..717157c 100644
--- a/libs/hwui/TreeInfo.cpp
+++ b/libs/hwui/TreeInfo.cpp
@@ -24,7 +24,8 @@
: mode(mode)
, prepareTextures(mode == MODE_FULL)
, canvasContext(canvasContext)
- , disableForceDark(canvasContext.useForceDark() ? 0 : 1)
+ , disableForceDark(canvasContext.getForceDarkType() == ForceDarkType::NONE ? 1 : 0)
+ , forceDarkType(canvasContext.getForceDarkType())
, screenSize(canvasContext.getNextFrameSize()) {}
} // namespace android::uirenderer
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 2bff9cb..88449f3 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -16,14 +16,17 @@
#pragma once
-#include "Properties.h"
-#include "utils/Macros.h"
-
#include <utils/Timers.h>
-#include "SkSize.h"
+#include <optional>
#include <string>
+#include "Properties.h"
+#include "SkSize.h"
+#include "SkippedFrameInfo.h"
+#include "utils/ForceDark.h"
+#include "utils/Macros.h"
+
namespace android {
namespace uirenderer {
@@ -95,6 +98,7 @@
bool updateWindowPositions = false;
int disableForceDark;
+ ForceDarkType forceDarkType = ForceDarkType::NONE;
const SkISize screenSize;
@@ -110,13 +114,13 @@
// animate itself, such as if hasFunctors is true
// This is only set if hasAnimations is true
bool requiresUiRedraw = false;
- // This is set to true if draw() can be called this frame
- // false means that we must delay until the next vsync pulse as frame
+ // This is set to nullopt if draw() can be called this frame
+ // A value means that we must delay until the next vsync pulse as frame
// production is outrunning consumption
- // NOTE that if this is false CanvasContext will set either requiresUiRedraw
+ // NOTE that if this has a value CanvasContext will set either requiresUiRedraw
// *OR* will post itself for the next vsync automatically, use this
// only to avoid calling draw()
- bool canDrawThisFrame = true;
+ std::optional<SkippedFrameReason> skippedFrameReason;
// Sentinel for animatedImageDelay meaning there is no need to post such
// a message.
static constexpr nsecs_t kNoAnimatedImageDelay = -1;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
new file mode 100644
index 0000000..ca11975
--- /dev/null
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -0,0 +1,57 @@
+package: "com.android.graphics.hwui.flags"
+
+flag {
+ name: "matrix_44"
+ namespace: "core_graphics"
+ description: "API for 4x4 matrix and related canvas functions"
+ bug: "280116960"
+}
+
+flag {
+ name: "limited_hdr"
+ namespace: "core_graphics"
+ description: "API to enable apps to restrict the amount of HDR headroom that is used"
+ bug: "234181960"
+}
+
+flag {
+ name: "hdr_10bit_plus"
+ namespace: "core_graphics"
+ description: "Use 10101010 and FP16 formats for HDR-UI when available"
+ bug: "284159488"
+}
+
+flag {
+ name: "gainmap_animations"
+ namespace: "core_graphics"
+ description: "APIs to help enable animations involving gainmaps"
+ bug: "296482289"
+}
+
+flag {
+ name: "gainmap_constructor_with_metadata"
+ namespace: "core_graphics"
+ description: "APIs to create a new gainmap with a bitmap for metadata."
+ bug: "304478551"
+}
+
+flag {
+ name: "clip_surfaceviews"
+ namespace: "core_graphics"
+ description: "Clip z-above surfaceviews to global clip rect"
+ bug: "298621623"
+}
+
+flag {
+ name: "requested_formats_v"
+ namespace: "core_graphics"
+ description: "Enable r_8, r_16_uint, rg_1616_uint, and rgba_10101010 in the SDK"
+ bug: "292545615"
+}
+
+flag {
+ name: "animate_hdr_transitions"
+ namespace: "core_graphics"
+ description: "Automatically animate all changes in HDR headroom"
+ bug: "314810174"
+}
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index c442a7b..c80a9b4 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
#include <log/log.h>
#include "android/graphics/bitmap.h"
diff --git a/libs/hwui/apex/android_canvas.cpp b/libs/hwui/apex/android_canvas.cpp
index 905b123..19f726a 100644
--- a/libs/hwui/apex/android_canvas.cpp
+++ b/libs/hwui/apex/android_canvas.cpp
@@ -45,9 +45,9 @@
SkImageInfo imageInfo = uirenderer::ANativeWindowToImageInfo(*buffer, cs);
size_t rowBytes = buffer->stride * imageInfo.bytesPerPixel();
- // If SkSurface::MakeRasterDirect fails then we should as well as we will not be able to
+ // If SkSurfaces::WrapPixels fails then we should as well as we will not be able to
// draw into the canvas.
- sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(imageInfo, buffer->bits, rowBytes);
+ sk_sp<SkSurface> surface = SkSurfaces::WrapPixels(imageInfo, buffer->bits, rowBytes);
if (surface.get() != nullptr) {
if (outBitmap) {
outBitmap->setInfo(imageInfo, rowBytes);
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 09ae7e7..883f273 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -25,9 +25,6 @@
#include <sys/cdefs.h>
#include <vulkan/vulkan.h>
-#undef LOG_TAG
-#define LOG_TAG "AndroidGraphicsJNI"
-
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index 651d526..3ebf7d1 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -244,11 +244,18 @@
// This can happen if a BitmapShader is used on multiple canvas', such as a
// software + hardware canvas, which is otherwise valid as SkShader is "immutable"
std::lock_guard _lock(mUniformGuard);
- const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
- sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
- (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
- sk_float_log(mGainmapInfo.fDisplayRatioSdr));
- const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
+ // Compute the weight parameter that will be used to blend between the images.
+ float W = 0.f;
+ if (targetHdrSdrRatio > mGainmapInfo.fDisplayRatioSdr) {
+ if (targetHdrSdrRatio < mGainmapInfo.fDisplayRatioHdr) {
+ W = (sk_float_log(targetHdrSdrRatio) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
+ (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr));
+ } else {
+ W = 1.f;
+ }
+ }
mBuilder.uniform("W") = W;
uniforms = mBuilder.uniforms();
}
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 8049dc9..27773a6 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -111,7 +111,7 @@
{
std::unique_lock lock{mImageLock};
snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
- snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
+ snap.mPic = mSkAnimatedImage->makePictureSnapshot();
}
return snap;
@@ -123,7 +123,7 @@
{
std::unique_lock lock{mImageLock};
mSkAnimatedImage->reset();
- snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
+ snap.mPic = mSkAnimatedImage->makePictureSnapshot();
snap.mDurationMS = currentFrameDuration();
}
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 92d875b..8344a86 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -43,12 +43,15 @@
#include <SkColor.h>
#include <SkEncodedImageFormat.h>
#include <SkHighContrastFilter.h>
-#include <SkImageEncoder.h>
+#include <SkImage.h>
+#include <SkImageAndroid.h>
#include <SkImagePriv.h>
#include <SkJpegGainmapEncoder.h>
#include <SkPixmap.h>
#include <SkRect.h>
#include <SkStream.h>
+#include <SkJpegEncoder.h>
+#include <SkPngEncoder.h>
#include <SkWebpEncoder.h>
#include <limits>
@@ -296,7 +299,8 @@
mPixelStorage.hardware.size = AHardwareBuffer_getAllocationSize(buffer);
AHardwareBuffer_acquire(buffer);
setImmutable(); // HW bitmaps are always immutable
- mImage = SkImage::MakeFromAHardwareBuffer(buffer, mInfo.alphaType(), mInfo.refColorSpace());
+ mImage = SkImages::DeferredFromAHardwareBuffer(buffer, mInfo.alphaType(),
+ mInfo.refColorSpace());
}
#endif
@@ -407,7 +411,12 @@
// Note we don't cache in this case, because the raster image holds a pointer to this Bitmap
// internally and ~Bitmap won't be invoked.
// TODO: refactor Bitmap to not derive from SkPixelRef, which would allow caching here.
+#ifdef __ANDROID__
+ // pinnable images are only supported with the Ganesh GPU backend compiled in.
+ image = SkImages::PinnableRasterFromBitmap(skiaBitmap);
+#else
image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode);
+#endif
}
return image;
}
@@ -528,17 +537,25 @@
return false;
}
- SkEncodedImageFormat fm;
switch (format) {
- case JavaCompressFormat::Jpeg:
- fm = SkEncodedImageFormat::kJPEG;
- break;
+ case JavaCompressFormat::Jpeg: {
+ SkJpegEncoder::Options options;
+ options.fQuality = quality;
+ return SkJpegEncoder::Encode(stream, bitmap.pixmap(), options);
+ }
case JavaCompressFormat::Png:
- fm = SkEncodedImageFormat::kPNG;
- break;
- case JavaCompressFormat::Webp:
- fm = SkEncodedImageFormat::kWEBP;
- break;
+ return SkPngEncoder::Encode(stream, bitmap.pixmap(), {});
+ case JavaCompressFormat::Webp: {
+ SkWebpEncoder::Options options;
+ if (quality >= 100) {
+ options.fCompression = SkWebpEncoder::Compression::kLossless;
+ options.fQuality = 75; // This is effort to compress
+ } else {
+ options.fCompression = SkWebpEncoder::Compression::kLossy;
+ options.fQuality = quality;
+ }
+ return SkWebpEncoder::Encode(stream, bitmap.pixmap(), options);
+ }
case JavaCompressFormat::WebpLossy:
case JavaCompressFormat::WebpLossless: {
SkWebpEncoder::Options options;
@@ -548,8 +565,6 @@
return SkWebpEncoder::Encode(stream, bitmap.pixmap(), options);
}
}
-
- return SkEncodeImage(stream, bitmap, fm, quality);
}
sp<uirenderer::Gainmap> Bitmap::gainmap() const {
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index cd8af3d..80b6c03 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -16,17 +16,18 @@
#include "Canvas.h"
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "FeatureFlags.h"
#include "MinikinUtils.h"
#include "Paint.h"
#include "Properties.h"
#include "RenderNode.h"
#include "Typeface.h"
-#include "pipeline/skia/SkiaRecordingCanvas.h"
-
+#include "hwui/DrawTextFunctor.h"
#include "hwui/PaintFilter.h"
-
-#include <SkFontMetrics.h>
-#include <SkRRect.h>
+#include "pipeline/skia/SkiaRecordingCanvas.h"
namespace android {
@@ -34,13 +35,6 @@
return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
}
-static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
- const Paint& paint, Canvas* canvas) {
- const SkScalar strokeWidth = fmax(thickness, 1.0f);
- const SkScalar bottom = top + strokeWidth;
- canvas->drawRect(left, top, right, bottom, paint);
-}
-
void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) {
// paint has already been filtered by our caller, so we can ignore any filter
const bool strikeThru = paint.isStrikeThru();
@@ -72,73 +66,6 @@
}
}
-static void simplifyPaint(int color, Paint* paint) {
- paint->setColor(color);
- paint->setShader(nullptr);
- paint->setColorFilter(nullptr);
- paint->setLooper(nullptr);
- paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
- paint->setStrokeJoin(SkPaint::kRound_Join);
- paint->setLooper(nullptr);
-}
-
-class DrawTextFunctor {
-public:
- DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
- float y, float totalAdvance)
- : layout(layout)
- , canvas(canvas)
- , paint(paint)
- , x(x)
- , y(y)
- , totalAdvance(totalAdvance) {}
-
- void operator()(size_t start, size_t end) {
- auto glyphFunc = [&](uint16_t* text, float* positions) {
- for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
- text[textIndex++] = layout.getGlyphId(i);
- positions[posIndex++] = x + layout.getX(i);
- positions[posIndex++] = y + layout.getY(i);
- }
- };
-
- size_t glyphCount = end - start;
-
- if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
- // high contrast draw path
- int color = paint.getColor();
- int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
- bool darken = channelSum < (128 * 3);
-
- // outline
- gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
- Paint outlinePaint(paint);
- simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
- outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
-
- // inner
- gDrawTextBlobMode = DrawTextBlobMode::HctInner;
- Paint innerPaint(paint);
- simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
- innerPaint.setStyle(SkPaint::kFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
- gDrawTextBlobMode = DrawTextBlobMode::Normal;
- } else {
- // standard draw path
- canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
- }
- }
-
-private:
- const minikin::Layout& layout;
- Canvas* canvas;
- const Paint& paint;
- float x;
- float y;
- float totalAdvance;
-};
-
void Canvas::drawGlyphs(const minikin::Font& font, const int* glyphIds, const float* positions,
int glyphCount, const Paint& paint) {
// Minikin modify skFont for auto-fakebold/auto-fakeitalic.
@@ -151,7 +78,7 @@
memcpy(outPositions, positions, sizeof(float) * 2 * glyphCount);
};
- const minikin::MinikinFont* minikinFont = font.typeface().get();
+ const minikin::MinikinFont* minikinFont = font.baseTypeface().get();
SkFont* skfont = &copied.getSkFont();
MinikinFontSkia::populateSkFont(skfont, minikinFont, minikin::FontFakery());
@@ -182,6 +109,31 @@
DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance());
MinikinUtils::forFontRun(layout, &paint, f);
+
+ if (text_feature::fix_double_underline()) {
+ Paint copied(paint);
+ PaintFilter* filter = getPaintFilter();
+ if (filter != nullptr) {
+ filter->filterFullPaint(&copied);
+ }
+ const bool isUnderline = copied.isUnderline();
+ const bool isStrikeThru = copied.isStrikeThru();
+ if (isUnderline || isStrikeThru) {
+ const SkScalar left = x;
+ const SkScalar right = x + layout.getAdvance();
+ if (isUnderline) {
+ const SkScalar top = y + f.getUnderlinePosition();
+ drawStroke(left, right, top, f.getUnderlineThickness(), copied, this);
+ }
+ if (isStrikeThru) {
+ float textSize = paint.getSkFont().getSize();
+ const float position = textSize * Paint::kStdStrikeThru_Top;
+ const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness;
+ const SkScalar top = y + position;
+ drawStroke(left, right, top, thickness, copied, this);
+ }
+ }
+ }
}
void Canvas::drawDoubleRoundRectXY(float outerLeft, float outerTop, float outerRight,
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 44ee31d..9ec023b 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -285,7 +285,7 @@
* totalAdvance: used to define width of text decorations (underlines, strikethroughs).
*/
virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
- float y,float totalAdvance) = 0;
+ float y, float totalAdvance) = 0;
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
const Paint& paint, const SkPath& path, size_t start,
size_t end) = 0;
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
new file mode 100644
index 0000000..2e6e976
--- /dev/null
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -0,0 +1,141 @@
+/*
+ * 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 <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "Canvas.h"
+#include "FeatureFlags.h"
+#include "MinikinUtils.h"
+#include "Paint.h"
+#include "Properties.h"
+#include "RenderNode.h"
+#include "Typeface.h"
+#include "hwui/PaintFilter.h"
+#include "pipeline/skia/SkiaRecordingCanvas.h"
+
+namespace android {
+
+static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
+ const Paint& paint, Canvas* canvas) {
+ const SkScalar strokeWidth = fmax(thickness, 1.0f);
+ const SkScalar bottom = top + strokeWidth;
+ canvas->drawRect(left, top, right, bottom, paint);
+}
+
+static void simplifyPaint(int color, Paint* paint) {
+ paint->setColor(color);
+ paint->setShader(nullptr);
+ paint->setColorFilter(nullptr);
+ paint->setLooper(nullptr);
+ paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
+ paint->setStrokeJoin(SkPaint::kRound_Join);
+ paint->setLooper(nullptr);
+}
+
+class DrawTextFunctor {
+public:
+ DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
+ float y, float totalAdvance)
+ : layout(layout)
+ , canvas(canvas)
+ , paint(paint)
+ , x(x)
+ , y(y)
+ , totalAdvance(totalAdvance)
+ , underlinePosition(0)
+ , underlineThickness(0) {}
+
+ void operator()(size_t start, size_t end) {
+ auto glyphFunc = [&](uint16_t* text, float* positions) {
+ for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
+ text[textIndex++] = layout.getGlyphId(i);
+ positions[posIndex++] = x + layout.getX(i);
+ positions[posIndex++] = y + layout.getY(i);
+ }
+ };
+
+ size_t glyphCount = end - start;
+
+ if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
+ // high contrast draw path
+ int color = paint.getColor();
+ int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
+ bool darken = channelSum < (128 * 3);
+
+ // outline
+ gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
+ Paint outlinePaint(paint);
+ simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
+ outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
+ canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+
+ // inner
+ gDrawTextBlobMode = DrawTextBlobMode::HctInner;
+ Paint innerPaint(paint);
+ simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
+ innerPaint.setStyle(SkPaint::kFill_Style);
+ canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
+ gDrawTextBlobMode = DrawTextBlobMode::Normal;
+ } else {
+ // standard draw path
+ canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
+ }
+
+ if (text_feature::fix_double_underline()) {
+ // Extract underline position and thickness.
+ if (paint.isUnderline()) {
+ SkFontMetrics metrics;
+ paint.getSkFont().getMetrics(&metrics);
+ const float textSize = paint.getSkFont().getSize();
+ SkScalar position;
+ if (!metrics.hasUnderlinePosition(&position)) {
+ position = textSize * Paint::kStdUnderline_Top;
+ }
+ SkScalar thickness;
+ if (!metrics.hasUnderlineThickness(&thickness)) {
+ thickness = textSize * Paint::kStdUnderline_Thickness;
+ }
+
+ // If multiple fonts are used, use the most bottom position and most thick stroke
+ // width as the underline position. This follows the CSS standard:
+ // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property
+ // <quote>
+ // The exact position and thickness of line decorations is UA-defined in this level.
+ // However, for underlines and overlines the UA must use a single thickness and
+ // position on each line for the decorations deriving from a single decorating box.
+ // </quote>
+ underlinePosition = std::max(underlinePosition, position);
+ underlineThickness = std::max(underlineThickness, thickness);
+ }
+ }
+ }
+
+ float getUnderlinePosition() const { return underlinePosition; }
+ float getUnderlineThickness() const { return underlineThickness; }
+
+private:
+ const minikin::Layout& layout;
+ Canvas* canvas;
+ const Paint& paint;
+ float x;
+ float y;
+ float totalAdvance;
+ float underlinePosition;
+ float underlineThickness;
+};
+
+} // namespace android
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 701a87f..588463c 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -43,9 +43,6 @@
#include <memory>
-#undef LOG_TAG
-#define LOG_TAG "ImageDecoder"
-
using namespace android;
sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 34cb4ae..f4ee36ec 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -30,6 +30,7 @@
#include <minikin/MinikinExtent.h>
#include <minikin/MinikinPaint.h>
#include <minikin/MinikinRect.h>
+#include <utils/TypefaceUtils.h>
namespace android {
@@ -142,7 +143,7 @@
skVariation[i].value = SkFloatToScalar(variations[i].value);
}
args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args));
return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize,
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index e359145..7552b56d 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -16,12 +16,15 @@
#include "MinikinUtils.h"
-#include <string>
-
#include <log/log.h>
-
+#include <minikin/FamilyVariant.h>
#include <minikin/MeasuredText.h>
#include <minikin/Measurement.h>
+
+#include <optional>
+#include <string>
+
+#include "FeatureFlags.h"
#include "Paint.h"
#include "SkPathMeasure.h"
#include "Typeface.h"
@@ -43,9 +46,17 @@
minikinPaint.wordSpacing = paint->getWordSpacing();
minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font);
minikinPaint.localeListId = paint->getMinikinLocaleListId();
- minikinPaint.familyVariant = paint->getFamilyVariant();
minikinPaint.fontStyle = resolvedFace->fStyle;
minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
+
+ const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant();
+ if (familyVariant.has_value()) {
+ minikinPaint.familyVariant = familyVariant.value();
+ } else {
+ minikinPaint.familyVariant = text_feature::deprecate_ui_fonts()
+ ? minikin::FamilyVariant::ELEGANT
+ : minikin::FamilyVariant::DEFAULT;
+ }
return minikinPaint;
}
@@ -84,7 +95,8 @@
float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf, size_t start,
- size_t count, size_t bufSize, float* advances) {
+ size_t count, size_t bufSize, float* advances,
+ minikin::MinikinRect* bounds) {
minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
const minikin::U16StringPiece textBuf(buf, bufSize);
const minikin::Range range(start, start + count);
@@ -92,7 +104,7 @@
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen,
- endHyphen, advances);
+ endHyphen, advances, bounds);
}
minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 009b84b..61bc881 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -51,10 +51,9 @@
static void getBounds(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
const uint16_t* buf, size_t bufSize, minikin::MinikinRect* out);
- static float measureText(const Paint* paint, minikin::Bidi bidiFlags,
- const Typeface* typeface, const uint16_t* buf,
- size_t start, size_t count, size_t bufSize,
- float* advances);
+ static float measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
+ const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+ float* advances, minikin::MinikinRect* bounds);
static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf,
@@ -76,7 +75,7 @@
size_t start = 0;
size_t nGlyphs = layout.nGlyphs();
for (size_t i = 0; i < nGlyphs; i++) {
- const minikin::MinikinFont* nextFont = layout.getFont(i)->typeface().get();
+ const minikin::MinikinFont* nextFont = layout.typeface(i).get();
if (i > 0 && nextFont != curFont) {
SkFont* skfont = &paint->getSkFont();
MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 4a8f3e1..ef4dce5 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -17,18 +17,18 @@
#ifndef ANDROID_GRAPHICS_PAINT_H_
#define ANDROID_GRAPHICS_PAINT_H_
-#include "Typeface.h"
-
-#include <cutils/compiler.h>
-
#include <SkFont.h>
#include <SkPaint.h>
#include <SkSamplingOptions.h>
+#include <cutils/compiler.h>
+#include <minikin/FamilyVariant.h>
+#include <minikin/FontFamily.h>
+#include <minikin/FontFeature.h>
+#include <minikin/Hyphenator.h>
+
#include <string>
-#include <minikin/FontFamily.h>
-#include <minikin/FamilyVariant.h>
-#include <minikin/Hyphenator.h>
+#include "Typeface.h"
namespace android {
@@ -82,11 +82,15 @@
float getWordSpacing() const { return mWordSpacing; }
- void setFontFeatureSettings(const std::string& fontFeatureSettings) {
- mFontFeatureSettings = fontFeatureSettings;
+ void setFontFeatureSettings(std::string_view fontFeatures) {
+ mFontFeatureSettings = minikin::FontFeature::parse(fontFeatures);
}
- std::string getFontFeatureSettings() const { return mFontFeatureSettings; }
+ void resetFontFeatures() { mFontFeatureSettings.clear(); }
+
+ const std::vector<minikin::FontFeature>& getFontFeatureSettings() const {
+ return mFontFeatureSettings;
+ }
void setMinikinLocaleListId(uint32_t minikinLocaleListId) {
mMinikinLocaleListId = minikinLocaleListId;
@@ -94,9 +98,10 @@
uint32_t getMinikinLocaleListId() const { return mMinikinLocaleListId; }
+ void resetFamilyVariant() { mFamilyVariant.reset(); }
void setFamilyVariant(minikin::FamilyVariant variant) { mFamilyVariant = variant; }
- minikin::FamilyVariant getFamilyVariant() const { return mFamilyVariant; }
+ std::optional<minikin::FamilyVariant> getFamilyVariant() const { return mFamilyVariant; }
void setStartHyphenEdit(uint32_t startHyphen) {
mHyphenEdit = minikin::packHyphenEdit(
@@ -169,9 +174,9 @@
float mLetterSpacing = 0;
float mWordSpacing = 0;
- std::string mFontFeatureSettings;
+ std::vector<minikin::FontFeature> mFontFeatureSettings;
uint32_t mMinikinLocaleListId;
- minikin::FamilyVariant mFamilyVariant;
+ std::optional<minikin::FamilyVariant> mFamilyVariant;
uint32_t mHyphenEdit = 0;
// The native Typeface object has the same lifetime of the Java Typeface
// object. The Java Paint object holds a strong reference to the Java Typeface
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 3c67edc..a9d1a2a 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -25,8 +25,9 @@
#include "MinikinSkia.h"
#include "SkPaint.h"
-#include "SkStream.h" // Fot tests.
+#include "SkStream.h" // For tests.
#include "SkTypeface.h"
+#include "utils/TypefaceUtils.h"
#include <minikin/FontCollection.h>
#include <minikin/FontFamily.h>
@@ -140,9 +141,8 @@
const minikin::FontStyle defaultStyle;
const minikin::MinikinFont* mf =
- families.empty()
- ? nullptr
- : families[0]->getClosestMatch(defaultStyle).font->typeface().get();
+ families.empty() ? nullptr
+ : families[0]->getClosestMatch(defaultStyle).typeface().get();
if (mf != nullptr) {
SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface();
const SkFontStyle& style = skTypeface->fontStyle();
@@ -187,7 +187,9 @@
LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", kRobotoFont);
void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(data, st.st_size));
- sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData));
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
+ LOG_ALWAYS_FATAL_IF(fm == nullptr, "Could not load FreeType SkFontMgr");
+ sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData));
LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
std::shared_ptr<minikin::MinikinFont> font =
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index a7f5aa83..90b1da8 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-#include "GraphicsJNI.h"
-#include "ImageDecoder.h"
-#include "Utils.h"
-
#include <SkAndroidCodec.h>
#include <SkAnimatedImage.h>
#include <SkColorFilter.h>
@@ -27,10 +23,15 @@
#include <SkRect.h>
#include <SkRefCnt.h>
#include <hwui/AnimatedImageDrawable.h>
-#include <hwui/ImageDecoder.h>
#include <hwui/Canvas.h>
+#include <hwui/ImageDecoder.h>
#include <utils/Looper.h>
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
+#include "ImageDecoder.h"
+#include "Utils.h"
+
using namespace android;
static jclass gAnimatedImageDrawableClass;
@@ -145,8 +146,9 @@
static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jlong nativeFilter) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter);
- drawable->setStagingColorFilter(sk_ref_sp(filter));
+ auto filter = uirenderer::ColorFilter::fromJava(nativeFilter);
+ auto skColorFilter = filter != nullptr ? filter->getInstance() : sk_sp<SkColorFilter>();
+ drawable->setStagingColorFilter(skColorFilter);
}
static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 6ee7576..9e21f86 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1,5 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
// #define LOG_NDEBUG 0
#include "Bitmap.h"
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 8abcd9a..3d0a534 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "BitmapFactory"
-
#include "BitmapFactory.h"
#include <Gainmap.h>
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 740988f..ea5c144 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "BitmapRegionDecoder"
-
#include "BitmapRegionDecoder.h"
#include <HardwareBitmapUploader.h>
diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp
index 4bd7ef4..0b95148 100644
--- a/libs/hwui/jni/ColorFilter.cpp
+++ b/libs/hwui/jni/ColorFilter.cpp
@@ -15,20 +15,21 @@
** limitations under the License.
*/
-#include "GraphicsJNI.h"
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
#include "SkBlendMode.h"
-#include "SkColorFilter.h"
-#include "SkColorMatrixFilter.h"
namespace android {
using namespace uirenderer;
-class SkColorFilterGlue {
+class ColorFilterGlue {
public:
- static void SafeUnref(SkColorFilter* filter) {
- SkSafeUnref(filter);
+ static void SafeUnref(ColorFilter* filter) {
+ if (filter) {
+ filter->decStrong(nullptr);
+ }
}
static jlong GetNativeFinalizer(JNIEnv*, jobject) {
@@ -36,41 +37,75 @@
}
static jlong CreateBlendModeFilter(JNIEnv* env, jobject, jint srcColor, jint modeHandle) {
- SkBlendMode mode = static_cast<SkBlendMode>(modeHandle);
- return reinterpret_cast<jlong>(SkColorFilters::Blend(srcColor, mode).release());
+ auto mode = static_cast<SkBlendMode>(modeHandle);
+ auto* blendModeFilter = new BlendModeColorFilter(srcColor, mode);
+ blendModeFilter->incStrong(nullptr);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(blendModeFilter));
}
static jlong CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
- return reinterpret_cast<jlong>(SkColorMatrixFilter::MakeLightingFilter(mul, add).release());
+ auto* lightingFilter = new LightingFilter(mul, add);
+ lightingFilter->incStrong(nullptr);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(lightingFilter));
}
- static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
- float matrix[20];
- env->GetFloatArrayRegion(jarray, 0, 20, matrix);
+ static void SetLightingFilterMul(JNIEnv* env, jobject, jlong lightingFilterPtr, jint mul) {
+ auto* filter = reinterpret_cast<LightingFilter*>(lightingFilterPtr);
+ if (filter) {
+ filter->setMul(mul);
+ }
+ }
+
+ static void SetLightingFilterAdd(JNIEnv* env, jobject, jlong lightingFilterPtr, jint add) {
+ auto* filter = reinterpret_cast<LightingFilter*>(lightingFilterPtr);
+ if (filter) {
+ filter->setAdd(add);
+ }
+ }
+
+ static std::vector<float> getMatrixFromJFloatArray(JNIEnv* env, jfloatArray jarray) {
+ std::vector<float> matrix(20);
+ // float matrix[20];
+ env->GetFloatArrayRegion(jarray, 0, 20, matrix.data());
// java biases the translates by 255, so undo that before calling skia
matrix[ 4] *= (1.0f/255);
matrix[ 9] *= (1.0f/255);
matrix[14] *= (1.0f/255);
matrix[19] *= (1.0f/255);
- return reinterpret_cast<jlong>(SkColorFilters::Matrix(matrix).release());
+ return matrix;
+ }
+
+ static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
+ std::vector<float> matrix = getMatrixFromJFloatArray(env, jarray);
+ auto* colorMatrixColorFilter = new ColorMatrixColorFilter(std::move(matrix));
+ colorMatrixColorFilter->incStrong(nullptr);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(colorMatrixColorFilter));
+ }
+
+ static void SetColorMatrix(JNIEnv* env, jobject, jlong colorMatrixColorFilterPtr,
+ jfloatArray jarray) {
+ auto* filter = reinterpret_cast<ColorMatrixColorFilter*>(colorMatrixColorFilterPtr);
+ if (filter) {
+ filter->setMatrix(getMatrixFromJFloatArray(env, jarray));
+ }
}
};
static const JNINativeMethod colorfilter_methods[] = {
- {"nativeGetFinalizer", "()J", (void*) SkColorFilterGlue::GetNativeFinalizer }
-};
+ {"nativeGetFinalizer", "()J", (void*)ColorFilterGlue::GetNativeFinalizer}};
static const JNINativeMethod blendmode_methods[] = {
- { "native_CreateBlendModeFilter", "(II)J", (void*) SkColorFilterGlue::CreateBlendModeFilter },
+ {"native_CreateBlendModeFilter", "(II)J", (void*)ColorFilterGlue::CreateBlendModeFilter},
};
static const JNINativeMethod lighting_methods[] = {
- { "native_CreateLightingFilter", "(II)J", (void*) SkColorFilterGlue::CreateLightingFilter },
-};
+ {"native_CreateLightingFilter", "(II)J", (void*)ColorFilterGlue::CreateLightingFilter},
+ {"native_SetLightingFilterAdd", "(JI)V", (void*)ColorFilterGlue::SetLightingFilterAdd},
+ {"native_SetLightingFilterMul", "(JI)V", (void*)ColorFilterGlue::SetLightingFilterMul}};
static const JNINativeMethod colormatrix_methods[] = {
- { "nativeColorMatrixFilter", "([F)J", (void*) SkColorFilterGlue::CreateColorMatrixFilter },
-};
+ {"nativeColorMatrixFilter", "([F)J", (void*)ColorFilterGlue::CreateColorMatrixFilter},
+ {"nativeSetColorMatrix", "(J[F)V", (void*)ColorFilterGlue::SetColorMatrix}};
int register_android_graphics_ColorFilter(JNIEnv* env) {
android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods,
diff --git a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
index 15e529e..a66d3b8 100644
--- a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
+++ b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
@@ -1,11 +1,11 @@
#include "CreateJavaOutputStreamAdaptor.h"
#include "SkData.h"
-#include "SkMalloc.h"
#include "SkRefCnt.h"
#include "SkStream.h"
#include "SkTypes.h"
#include "Utils.h"
+#include <cstdlib>
#include <nativehelper/JNIHelp.h>
#include <log/log.h>
#include <memory>
@@ -177,6 +177,10 @@
return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions);
}
+static void free_pointer_skproc(const void* ptr, void*) {
+ free((void*)ptr);
+}
+
sk_sp<SkData> CopyJavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray storage) {
std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, inputStream, storage));
if (!stream) {
@@ -186,19 +190,31 @@
size_t bufferSize = 4096;
size_t streamLen = 0;
size_t len;
- char* data = (char*)sk_malloc_throw(bufferSize);
+ char* data = (char*)malloc(bufferSize);
+ LOG_ALWAYS_FATAL_IF(!data);
while ((len = stream->read(data + streamLen,
bufferSize - streamLen)) != 0) {
streamLen += len;
if (streamLen == bufferSize) {
bufferSize *= 2;
- data = (char*)sk_realloc_throw(data, bufferSize);
+ data = (char*)realloc(data, bufferSize);
+ LOG_ALWAYS_FATAL_IF(!data);
}
}
- data = (char*)sk_realloc_throw(data, streamLen);
-
- return SkData::MakeFromMalloc(data, streamLen);
+ if (streamLen == 0) {
+ // realloc with size 0 is unspecified behavior in C++11
+ free(data);
+ data = nullptr;
+ } else {
+ // Trim down the buffer to the actual size of the data.
+ LOG_FATAL_IF(streamLen > bufferSize);
+ data = (char*)realloc(data, streamLen);
+ LOG_ALWAYS_FATAL_IF(!data);
+ }
+ // Just in case sk_free differs from free, we ask Skia to use
+ // free to cleanup the buffer that SkData wraps.
+ return SkData::MakeWithProc(data, streamLen, free_pointer_skproc, nullptr);
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index 69774cc..e6d790f 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
#include "FontUtils.h"
@@ -34,6 +31,7 @@
#include <minikin/FontFamily.h>
#include <minikin/LocaleList.h>
#include <ui/FatVector.h>
+#include <utils/TypefaceUtils.h>
#include <memory>
@@ -89,7 +87,8 @@
}
std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
builder->langId, builder->variant, std::move(builder->fonts),
- true /* isCustomFallback */, false /* isDefaultFallback */);
+ true /* isCustomFallback */, false /* isDefaultFallback */,
+ minikin::VariationFamilyType::None);
if (family->getCoverage().length() == 0) {
return 0;
}
@@ -127,7 +126,7 @@
args.setCollectionIndex(ttcIndex);
args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args));
if (face == NULL) {
ALOGE("addFont failed to create font, invalid request");
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index cec0ee7..0fffee7 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -208,8 +208,6 @@
p.writeFloat(info.fDisplayRatioHdr);
// base image type
p.writeInt32(static_cast<int32_t>(info.fBaseImageType));
- // type
- p.writeInt32(static_cast<int32_t>(info.fType));
#else
doThrowRE(env, "Cannot use parcels outside of Android!");
#endif
@@ -232,7 +230,6 @@
info.fDisplayRatioSdr = p.readFloat();
info.fDisplayRatioHdr = p.readFloat();
info.fBaseImageType = static_cast<SkGainmapInfo::BaseImageType>(p.readInt32());
- info.fType = static_cast<SkGainmapInfo::Type>(p.readInt32());
fromJava(nativeObject)->info = info;
#else
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 78b4f7b..7cc4866 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "GraphicsJNI"
-
#include <assert.h>
#include <unistd.h>
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index 23ab5dd..b9fff36 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -125,14 +125,6 @@
static jobject createBitmapRegionDecoder(JNIEnv* env,
android::BitmapRegionDecoderWrapper* bitmap);
- /**
- * Given a bitmap we natively allocate a memory block to store the contents
- * of that bitmap. The memory is then attached to the bitmap via an
- * SkPixelRef, which ensures that upon deletion the appropriate caches
- * are notified.
- */
- static bool allocatePixels(JNIEnv* env, SkBitmap* bitmap);
-
/** Copy the colors in colors[] to the bitmap, convert to the correct
format along the way.
Whether to use premultiplied pixels is determined by dstBitmap's alphaType.
diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp
index e32c911..54369b9 100644
--- a/libs/hwui/jni/GraphicsStatsService.cpp
+++ b/libs/hwui/jni/GraphicsStatsService.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "GraphicsStatsService"
-
#include <JankTracker.h>
#include <log/log.h>
#include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/jni/MaskFilter.cpp b/libs/hwui/jni/MaskFilter.cpp
index 048ce02..cbd4520 100644
--- a/libs/hwui/jni/MaskFilter.cpp
+++ b/libs/hwui/jni/MaskFilter.cpp
@@ -1,6 +1,5 @@
#include "GraphicsJNI.h"
#include "SkMaskFilter.h"
-#include "SkBlurMask.h"
#include "SkBlurMaskFilter.h"
#include "SkBlurTypes.h"
#include "SkTableMaskFilter.h"
@@ -11,6 +10,13 @@
}
}
+// From https://skia.googlesource.com/skia/+/d74c99a3cd5eef5f16b2eb226e6b45fe523c8552/src/core/SkBlurMask.cpp#28
+static constexpr float kBLUR_SIGMA_SCALE = 0.57735f;
+
+static float convertRadiusToSigma(float radius) {
+ return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
+}
+
class SkMaskFilterGlue {
public:
static void destructor(JNIEnv* env, jobject, jlong filterHandle) {
@@ -19,7 +25,7 @@
}
static jlong createBlur(JNIEnv* env, jobject, jfloat radius, jint blurStyle) {
- SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
+ SkScalar sigma = convertRadiusToSigma(radius);
SkMaskFilter* filter = SkMaskFilter::MakeBlur((SkBlurStyle)blurStyle, sigma).release();
ThrowIAE_IfNull(env, filter);
return reinterpret_cast<jlong>(filter);
@@ -34,7 +40,7 @@
direction[i] = values[i];
}
- SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
+ SkScalar sigma = convertRadiusToSigma(radius);
SkMaskFilter* filter = SkBlurMaskFilter::MakeEmboss(sigma,
direction, ambient, specular).release();
ThrowIAE_IfNull(env, filter);
diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp
index d50a8a2..67ef143 100644
--- a/libs/hwui/jni/NinePatch.cpp
+++ b/libs/hwui/jni/NinePatch.cpp
@@ -15,8 +15,6 @@
** limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "9patch"
#define LOG_NDEBUG 1
#include <androidfw/ResourceTypes.h>
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 13357fa..d84b73d 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -15,16 +15,30 @@
** limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Paint"
-
-#include <utils/Log.h>
-
-#include "GraphicsJNI.h"
+#include <hwui/BlurDrawLooper.h>
+#include <hwui/MinikinSkia.h>
+#include <hwui/MinikinUtils.h>
+#include <hwui/Paint.h>
+#include <hwui/Typeface.h>
+#include <minikin/GraphemeBreak.h>
+#include <minikin/LocaleList.h>
+#include <minikin/Measurement.h>
+#include <minikin/MinikinPaint.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedStringChars.h>
#include <nativehelper/ScopedUtfChars.h>
-#include <nativehelper/ScopedPrimitiveArray.h>
+#include <unicode/utf16.h>
+#include <utils/Log.h>
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
+#include "SkBlendMode.h"
#include "SkColorFilter.h"
#include "SkColorSpace.h"
#include "SkFont.h"
@@ -35,28 +49,22 @@
#include "SkPathEffect.h"
#include "SkPathUtils.h"
#include "SkShader.h"
-#include "SkBlendMode.h"
#include "unicode/uloc.h"
#include "utils/Blur.h"
-#include <hwui/BlurDrawLooper.h>
-#include <hwui/MinikinSkia.h>
-#include <hwui/MinikinUtils.h>
-#include <hwui/Paint.h>
-#include <hwui/Typeface.h>
-#include <minikin/GraphemeBreak.h>
-#include <minikin/LocaleList.h>
-#include <minikin/Measurement.h>
-#include <minikin/MinikinPaint.h>
-#include <unicode/utf16.h>
-
-#include <cassert>
-#include <cstring>
-#include <memory>
-#include <vector>
-
namespace android {
+namespace {
+
+void copyMinikinRectToSkRect(const minikin::MinikinRect& minikinRect, SkRect* skRect) {
+ skRect->fLeft = minikinRect.mLeft;
+ skRect->fTop = minikinRect.mTop;
+ skRect->fRight = minikinRect.mRight;
+ skRect->fBottom = minikinRect.mBottom;
+}
+
+} // namespace
+
static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count,
const SkPoint pos[], SkPath* dst) {
dst->reset();
@@ -105,8 +113,8 @@
float measured = 0;
std::unique_ptr<float[]> advancesArray(new float[count]);
- MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text,
- 0, count, count, advancesArray.get());
+ MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0,
+ count, count, advancesArray.get(), nullptr);
for (int i = 0; i < count; i++) {
// traverse in the given direction
@@ -196,9 +204,9 @@
if (advances) {
advancesArray.reset(new jfloat[count]);
}
- const float advance = MinikinUtils::measureText(paint,
- static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count, contextCount,
- advancesArray.get());
+ const float advance = MinikinUtils::measureText(
+ paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count,
+ contextCount, advancesArray.get(), nullptr);
if (advances) {
env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
}
@@ -236,7 +244,7 @@
minikin::Bidi bidiFlags = dir == 1 ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count,
- advancesArray.get());
+ advancesArray.get(), nullptr);
size_t result = minikin::GraphemeBreak::getTextRunCursor(advancesArray.get(), text,
start, count, offset, moveOpt);
return static_cast<jint>(result);
@@ -500,7 +508,7 @@
static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface,
const jchar buf[], jint start, jint count, jint bufSize,
jboolean isRtl, jint offset, jfloatArray advances,
- jint advancesIndex) {
+ jint advancesIndex, SkRect* drawBounds) {
if (advances) {
size_t advancesLength = env->GetArrayLength(advances);
if ((size_t)(count + advancesIndex) > advancesLength) {
@@ -509,14 +517,23 @@
}
}
minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
+ minikin::MinikinRect bounds;
if (offset == start + count && advances == nullptr) {
- return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
- bufSize, nullptr);
+ float result =
+ MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
+ bufSize, nullptr, drawBounds ? &bounds : nullptr);
+ if (drawBounds) {
+ copyMinikinRectToSkRect(bounds, drawBounds);
+ }
+ return result;
}
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
- advancesArray.get());
+ advancesArray.get(), drawBounds ? &bounds : nullptr);
+ if (drawBounds) {
+ copyMinikinRectToSkRect(bounds, drawBounds);
+ }
float result = minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset);
if (advances) {
minikin::distributeAdvances(advancesArray.get(), buf, start, count);
@@ -532,7 +549,7 @@
ScopedCharArrayRO textArray(env, text);
jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
start - contextStart, end - start, contextEnd - contextStart,
- isRtl, offset - contextStart, nullptr, 0);
+ isRtl, offset - contextStart, nullptr, 0, nullptr);
return result;
}
@@ -540,13 +557,19 @@
jcharArray text, jint start, jint end,
jint contextStart, jint contextEnd,
jboolean isRtl, jint offset,
- jfloatArray advances, jint advancesIndex) {
+ jfloatArray advances, jint advancesIndex,
+ jobject drawBounds) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
ScopedCharArrayRO textArray(env, text);
+ SkRect skDrawBounds;
jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
start - contextStart, end - start, contextEnd - contextStart,
- isRtl, offset - contextStart, advances, advancesIndex);
+ isRtl, offset - contextStart, advances, advancesIndex,
+ drawBounds ? &skDrawBounds : nullptr);
+ if (drawBounds != nullptr) {
+ GraphicsJNI::rect_to_jrectf(skDrawBounds, env, drawBounds);
+ }
return result;
}
@@ -555,7 +578,7 @@
minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
std::unique_ptr<float[]> advancesArray(new float[count]);
MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
- advancesArray.get());
+ advancesArray.get(), nullptr);
return minikin::getOffsetForAdvance(advancesArray.get(), buf, start, count, advance);
}
@@ -571,7 +594,7 @@
return result;
}
- static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) {
+ static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics* metrics, bool useLocale) {
const int kElegantTop = 2500;
const int kElegantBottom = -1000;
const int kElegantAscent = 1900;
@@ -584,7 +607,7 @@
minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
float saveSkewX = font->getSkewX();
bool savefakeBold = font->isEmbolden();
- MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery);
+ MinikinFontSkia::populateSkFont(font, baseFont.typeface().get(), baseFont.fakery);
SkScalar spacing = font->getMetrics(metrics);
// The populateSkPaint call may have changed fake bold / text skew
// because we want to measure with those effects applied, so now
@@ -600,6 +623,17 @@
metrics->fLeading = size * kElegantLeading / 2048;
spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading;
}
+
+ if (useLocale) {
+ minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
+ minikin::MinikinExtent extent =
+ typeface->fFontCollection->getReferenceExtentForLocale(minikinPaint);
+ metrics->fAscent = std::min(extent.ascent, metrics->fAscent);
+ metrics->fDescent = std::max(extent.descent, metrics->fDescent);
+ metrics->fTop = std::min(metrics->fAscent, metrics->fTop);
+ metrics->fBottom = std::max(metrics->fDescent, metrics->fBottom);
+ }
+
return spacing;
}
@@ -612,7 +646,7 @@
MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize);
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
metrics.fAscent = extent.ascent;
metrics.fDescent = extent.descent;
@@ -657,27 +691,29 @@
jstring settings) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
if (!settings) {
- paint->setFontFeatureSettings(std::string());
+ paint->resetFontFeatures();
} else {
ScopedUtfChars settingsChars(env, settings);
- paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size()));
+ paint->setFontFeatureSettings(
+ std::string_view(settingsChars.c_str(), settingsChars.size()));
}
}
- static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
+ static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj,
+ jboolean useLocale) {
SkFontMetrics metrics;
- SkScalar spacing = getMetricsInternal(paintHandle, &metrics);
+ SkScalar spacing = getMetricsInternal(paintHandle, &metrics, useLocale);
GraphicsJNI::set_metrics(env, metricsObj, metrics);
return SkScalarToFloat(spacing);
}
- static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
+ static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj,
+ jboolean useLocale) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, useLocale);
return GraphicsJNI::set_metrics_int(env, metricsObj, metrics);
}
-
// ------------------ @CriticalNative ---------------------------
static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) {
@@ -821,9 +857,11 @@
static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) {
Paint* obj = reinterpret_cast<Paint *>(objHandle);
- SkColorFilter* filter = reinterpret_cast<SkColorFilter *>(filterHandle);
- obj->setColorFilter(sk_ref_sp(filter));
- return reinterpret_cast<jlong>(obj->getColorFilter());
+ auto colorFilter = uirenderer::ColorFilter::fromJava(filterHandle);
+ auto skColorFilter =
+ colorFilter != nullptr ? colorFilter->getInstance() : sk_sp<SkColorFilter>();
+ obj->setColorFilter(skColorFilter);
+ return filterHandle;
}
static void setXfermode(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint xfermodeHandle) {
@@ -899,15 +937,39 @@
obj->setMinikinLocaleListId(minikinLocaleListId);
}
- static jboolean isElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
+ // Note: Following three values must be equal to the ones in Java file: Paint.java.
+ constexpr jint ELEGANT_TEXT_HEIGHT_UNSET = -1;
+ constexpr jint ELEGANT_TEXT_HEIGHT_ENABLED = 0;
+ constexpr jint ELEGANT_TEXT_HEIGHT_DISABLED = 1;
+
+ static jint getElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
Paint* obj = reinterpret_cast<Paint*>(paintHandle);
- return obj->getFamilyVariant() == minikin::FamilyVariant::ELEGANT;
+ const std::optional<minikin::FamilyVariant>& familyVariant = obj->getFamilyVariant();
+ if (familyVariant.has_value()) {
+ if (familyVariant.value() == minikin::FamilyVariant::ELEGANT) {
+ return ELEGANT_TEXT_HEIGHT_ENABLED;
+ } else {
+ return ELEGANT_TEXT_HEIGHT_DISABLED;
+ }
+ } else {
+ return ELEGANT_TEXT_HEIGHT_UNSET;
+ }
}
- static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean aa) {
+ static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint value) {
Paint* obj = reinterpret_cast<Paint*>(paintHandle);
- obj->setFamilyVariant(
- aa ? minikin::FamilyVariant::ELEGANT : minikin::FamilyVariant::DEFAULT);
+ switch (value) {
+ case ELEGANT_TEXT_HEIGHT_ENABLED:
+ obj->setFamilyVariant(minikin::FamilyVariant::ELEGANT);
+ return;
+ case ELEGANT_TEXT_HEIGHT_DISABLED:
+ obj->setFamilyVariant(minikin::FamilyVariant::DEFAULT);
+ return;
+ case ELEGANT_TEXT_HEIGHT_UNSET:
+ default:
+ obj->resetFamilyVariant();
+ return;
+ }
}
static jfloat getTextSize(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
@@ -978,19 +1040,19 @@
static jfloat ascent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
return SkScalarToFloat(metrics.fAscent);
}
static jfloat descent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
return SkScalarToFloat(metrics.fDescent);
}
static jfloat getUnderlinePosition(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
SkScalar position;
if (metrics.hasUnderlinePosition(&position)) {
return SkScalarToFloat(position);
@@ -1002,7 +1064,7 @@
static jfloat getUnderlineThickness(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
SkFontMetrics metrics;
- getMetricsInternal(paintHandle, &metrics);
+ getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
SkScalar thickness;
if (metrics.hasUnderlineThickness(&thickness)) {
return SkScalarToFloat(thickness);
@@ -1083,7 +1145,7 @@
(void*)PaintGlue::getCharArrayBounds},
{"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph},
{"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F},
- {"nGetRunCharacterAdvance", "(J[CIIIIZI[FI)F",
+ {"nGetRunCharacterAdvance", "(J[CIIIIZI[FILandroid/graphics/RectF;)F",
(void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F},
{"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I},
{"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
@@ -1097,9 +1159,9 @@
{"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales},
{"nSetFontFeatureSettings", "(JLjava/lang/String;)V",
(void*)PaintGlue::setFontFeatureSettings},
- {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F",
+ {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;Z)F",
(void*)PaintGlue::getFontMetrics},
- {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I",
+ {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;Z)I",
(void*)PaintGlue::getFontMetricsInt},
// --------------- @CriticalNative ------------------
@@ -1142,8 +1204,8 @@
{"nSetTextAlign", "(JI)V", (void*)PaintGlue::setTextAlign},
{"nSetTextLocalesByMinikinLocaleListId", "(JI)V",
(void*)PaintGlue::setTextLocalesByMinikinLocaleListId},
- {"nIsElegantTextHeight", "(J)Z", (void*)PaintGlue::isElegantTextHeight},
- {"nSetElegantTextHeight", "(JZ)V", (void*)PaintGlue::setElegantTextHeight},
+ {"nGetElegantTextHeight", "(J)I", (void*)PaintGlue::getElegantTextHeight},
+ {"nSetElegantTextHeight", "(JI)V", (void*)PaintGlue::setElegantTextHeight},
{"nGetTextSize", "(J)F", (void*)PaintGlue::getTextSize},
{"nSetTextSize", "(JF)V", (void*)PaintGlue::setTextSize},
{"nGetTextScaleX", "(J)F", (void*)PaintGlue::getTextScaleX},
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index f3db170..dcd3fa4 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -14,13 +14,13 @@
* limitations under the License.
*/
#include "Bitmap.h"
+#include "ColorFilter.h"
#include "GraphicsJNI.h"
#include "SkBlendMode.h"
#include "SkImageFilter.h"
#include "SkImageFilters.h"
#include "graphics_jni_helpers.h"
#include "utils/Blur.h"
-#include <utils/Log.h>
using namespace android::uirenderer;
@@ -76,11 +76,13 @@
jlong colorFilterHandle,
jlong inputFilterHandle
) {
- auto* colorFilter = reinterpret_cast<const SkColorFilter*>(colorFilterHandle);
+ auto colorFilter = android::uirenderer::ColorFilter::fromJava(colorFilterHandle);
+ auto skColorFilter =
+ colorFilter != nullptr ? colorFilter->getInstance() : sk_sp<SkColorFilter>();
auto* inputFilter = reinterpret_cast<const SkImageFilter*>(inputFilterHandle);
- sk_sp<SkImageFilter> colorFilterImageFilter = SkImageFilters::ColorFilter(
- sk_ref_sp(colorFilter), sk_ref_sp(inputFilter), nullptr);
- return reinterpret_cast<jlong>(colorFilterImageFilter.release());
+ sk_sp<SkImageFilter> colorFilterImageFilter =
+ SkImageFilters::ColorFilter(skColorFilter, sk_ref_sp(inputFilter), nullptr);
+ return reinterpret_cast<jlong>(colorFilterImageFilter.release());
}
static jlong createBlendModeEffect(
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 7eb79be..a952be0 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "ShaderJNI"
-
#include <vector>
#include "Gainmap.h"
@@ -68,21 +65,41 @@
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref));
}
-static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
- jint tileModeX, jint tileModeY, bool isDirectSampled,
- const SkSamplingOptions& sampling) {
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static SkGainmapInfo sNoOpGainmap = {
+ .fGainmapRatioMin = {1.f, 1.f, 1.f, 1.0},
+ .fGainmapRatioMax = {1.f, 1.f, 1.f, 1.0},
+ .fGainmapGamma = {1.f, 1.f, 1.f, 1.f},
+ .fEpsilonSdr = {0.f, 0.f, 0.f, 1.0},
+ .fEpsilonHdr = {0.f, 0.f, 0.f, 1.0},
+ .fDisplayRatioSdr = 1.f,
+ .fDisplayRatioHdr = 1.f,
+};
+
+static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
+ jint tileModeX, jint tileModeY, jint maxAniso, bool filter,
+ bool isDirectSampled, jlong overrideGainmapPtr) {
+ SkSamplingOptions sampling = maxAniso > 0 ? SkSamplingOptions::Aniso(static_cast<int>(maxAniso))
+ : SkSamplingOptions(filter ? SkFilterMode::kLinear
+ : SkFilterMode::kNearest,
+ SkMipmapMode::kNone);
const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ const Gainmap* gainmap = reinterpret_cast<Gainmap*>(overrideGainmapPtr);
sk_sp<SkImage> image;
if (bitmapHandle) {
// Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
// we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
image = bitmap.makeImage();
+ if (!gainmap && bitmap.hasGainmap()) {
+ gainmap = bitmap.gainmap().get();
+ }
- if (!isDirectSampled && bitmap.hasGainmap()) {
- sk_sp<SkShader> gainmapShader = MakeGainmapShader(
- image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
- (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+ if (!isDirectSampled && gainmap && gainmap->info != sNoOpGainmap) {
+ sk_sp<SkShader> gainmapShader =
+ MakeGainmapShader(image, gainmap->bitmap->makeImage(), gainmap->info,
+ (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
if (gainmapShader) {
if (matrix) {
gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
@@ -114,26 +131,6 @@
///////////////////////////////////////////////////////////////////////////////////////////////
-static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
- jint tileModeX, jint tileModeY, bool filter,
- bool isDirectSampled) {
- SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest,
- SkMipmapMode::kNone);
- return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY,
- isDirectSampled, sampling);
-}
-
-static jlong BitmapShader_constructorWithMaxAniso(JNIEnv* env, jobject o, jlong matrixPtr,
- jlong bitmapHandle, jint tileModeX,
- jint tileModeY, jint maxAniso,
- bool isDirectSampled) {
- auto sampling = SkSamplingOptions::Aniso(static_cast<int>(maxAniso));
- return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY,
- isDirectSampled, sampling);
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////
-
static std::vector<SkColor4f> convertColorLongs(JNIEnv* env, jlongArray colorArray) {
const size_t count = env->GetArrayLength(colorArray);
const jlong* colorValues = env->GetLongArrayElements(colorArray, nullptr);
@@ -422,8 +419,7 @@
};
static const JNINativeMethod gBitmapShaderMethods[] = {
- {"nativeCreate", "(JJIIZZ)J", (void*)BitmapShader_constructor},
- {"nativeCreateWithMaxAniso", "(JJIIIZ)J", (void*)BitmapShader_constructorWithMaxAniso},
+ {"nativeCreate", "(JJIIIZZJ)J", (void*)BitmapShader_constructor},
};
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 7298906..3533001 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "YuvToJpegEncoder"
-
#include "CreateJavaOutputStreamAdaptor.h"
#include "SkStream.h"
#include "YuvToJpegEncoder.h"
@@ -336,7 +333,8 @@
bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env,
SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
- int width, int height, int jpegQuality) {
+ int width, int height, int jpegQuality, ScopedByteArrayRO* jExif,
+ ScopedIntArrayRO* jHdrStrides, ScopedIntArrayRO* jSdrStrides) {
// Check SDR color space. Now we only support SRGB transfer function
if ((sdrColorSpace & ADataSpace::TRANSFER_MASK) != ADataSpace::TRANSFER_SRGB) {
jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
@@ -344,6 +342,19 @@
"The requested SDR color space is not supported. Transfer function must be SRGB");
return false;
}
+ // Check HDR and SDR strides length.
+ // HDR is YCBCR_P010 color format, and its strides length must be 2 (Y, chroma (Cb, Cr)).
+ // SDR is YUV_420_888 color format, and its strides length must be 3 (Y, Cb, Cr).
+ if (jHdrStrides->size() != 2) {
+ jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+ env->ThrowNew(IllegalArgumentException, "HDR stride length must be 2.");
+ return false;
+ }
+ if (jSdrStrides->size() != 3) {
+ jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+ env->ThrowNew(IllegalArgumentException, "SDR stride length must be 3.");
+ return false;
+ }
ultrahdr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace);
ultrahdr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace);
@@ -355,20 +366,32 @@
return false;
}
+ const int* hdrStrides = reinterpret_cast<const int*>(jHdrStrides->get());
+ const int* sdrStrides = reinterpret_cast<const int*>(jSdrStrides->get());
+
JpegR jpegREncoder;
jpegr_uncompressed_struct p010;
p010.data = hdr;
p010.width = width;
p010.height = height;
+ // Divided by 2 because unit in libultrader is pixel and in YuvImage it is byte.
+ p010.luma_stride = (hdrStrides[0] + 1) / 2;
+ p010.chroma_stride = (hdrStrides[1] + 1) / 2;
p010.colorGamut = hdrColorGamut;
jpegr_uncompressed_struct yuv420;
yuv420.data = sdr;
yuv420.width = width;
yuv420.height = height;
+ yuv420.luma_stride = sdrStrides[0];
+ yuv420.chroma_stride = sdrStrides[1];
yuv420.colorGamut = sdrColorGamut;
+ jpegr_exif_struct exif;
+ exif.data = const_cast<void*>(reinterpret_cast<const void*>(jExif->get()));
+ exif.length = jExif->size();
+
jpegr_compressed_struct jpegR;
jpegR.maxLength = width * height * sizeof(uint8_t);
@@ -377,7 +400,8 @@
if (int success = jpegREncoder.encodeJPEGR(&p010, &yuv420,
hdrTransferFunction,
- &jpegR, jpegQuality, nullptr); success != android::OK) {
+ &jpegR, jpegQuality,
+ exif.length > 0 ? &exif : NULL); success != JPEGR_NO_ERROR) {
ALOGW("Encode JPEG/R failed, error code: %d.", success);
return false;
}
@@ -419,20 +443,27 @@
static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr,
jint hdrColorSpace, jbyteArray inSdr, jint sdrColorSpace,
jint width, jint height, jint quality, jobject jstream,
- jbyteArray jstorage) {
+ jbyteArray jstorage, jbyteArray jExif,
+ jintArray jHdrStrides, jintArray jSdrStrides) {
jbyte* hdr = env->GetByteArrayElements(inHdr, NULL);
jbyte* sdr = env->GetByteArrayElements(inSdr, NULL);
+ ScopedByteArrayRO exif(env, jExif);
+ ScopedIntArrayRO hdrStrides(env, jHdrStrides);
+ ScopedIntArrayRO sdrStrides(env, jSdrStrides);
+
SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
P010Yuv420ToJpegREncoder encoder;
jboolean result = JNI_FALSE;
if (encoder.encode(env, strm, hdr, hdrColorSpace, sdr, sdrColorSpace,
- width, height, quality)) {
+ width, height, quality, &exif,
+ &hdrStrides, &sdrStrides)) {
result = JNI_TRUE;
}
env->ReleaseByteArrayElements(inHdr, hdr, 0);
env->ReleaseByteArrayElements(inSdr, sdr, 0);
+
delete strm;
return result;
}
@@ -441,7 +472,7 @@
static const JNINativeMethod gYuvImageMethods[] = {
{ "nativeCompressToJpeg", "([BIII[I[IILjava/io/OutputStream;[B)Z",
(void*)YuvImage_compressToJpeg },
- { "nativeCompressToJpegR", "([BI[BIIIILjava/io/OutputStream;[B)Z",
+ { "nativeCompressToJpegR", "([BI[BIIIILjava/io/OutputStream;[B[B[I[I)Z",
(void*)YuvImage_compressToJpegR }
};
diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h
index 0e711ef..629f1e6 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.h
+++ b/libs/hwui/jni/YuvToJpegEncoder.h
@@ -2,6 +2,7 @@
#define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
#include <android/data_space.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
#include <ultrahdr/jpegr.h>
extern "C" {
@@ -90,11 +91,15 @@
* @param width Width of the Yuv data in terms of pixels.
* @param height Height of the Yuv data in terms of pixels.
* @param jpegQuality Picture quality in [0, 100].
+ * @param exif Buffer holds EXIF package.
+ * @param hdrStrides The number of row bytes in each image plane of the HDR input.
+ * @param sdrStrides The number of row bytes in each image plane of the SDR input.
* @return true if successfully compressed the stream.
*/
bool encode(JNIEnv* env,
SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
- int width, int height, int jpegQuality);
+ int width, int height, int jpegQuality, ScopedByteArrayRO* exif,
+ ScopedIntArrayRO* hdrStrides, ScopedIntArrayRO* sdrStrides);
/** Map data space (defined in DataSpace.java and data_space.h) to the color gamut
* used in JPEG/R
diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
index f060bb3..426644e 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -84,7 +84,7 @@
canvas->resetRecording(width, height, renderNode);
}
-static jint android_view_DisplayListCanvas_getMaxTextureSize(CRITICAL_JNI_PARAMS) {
+static jint android_view_DisplayListCanvas_getMaxTextureSize(JNIEnv*, jobject) {
#ifdef __ANDROID__ // Layoutlib does not support RenderProxy (RenderThread)
return android::uirenderer::renderthread::RenderProxy::maxTextureSize();
#else
@@ -175,14 +175,14 @@
const char* const kClassPathName = "android/graphics/RecordingCanvas";
static JNINativeMethod gMethods[] = {
+ {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize},
+ {"nGetMaximumTextureHeight", "()I",
+ (void*)android_view_DisplayListCanvas_getMaxTextureSize},
// ------------ @CriticalNative --------------
{"nCreateDisplayListCanvas", "(JII)J",
(void*)android_view_DisplayListCanvas_createDisplayListCanvas},
{"nResetDisplayListCanvas", "(JJII)V",
(void*)android_view_DisplayListCanvas_resetDisplayListCanvas},
- {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize},
- {"nGetMaximumTextureHeight", "()I",
- (void*)android_view_DisplayListCanvas_getMaxTextureSize},
{"nEnableZ", "(JZ)V", (void*)android_view_DisplayListCanvas_enableZ},
{"nFinishRecording", "(JJ)V", (void*)android_view_DisplayListCanvas_finishRecording},
{"nDrawRenderNode", "(JJ)V", (void*)android_view_DisplayListCanvas_drawRenderNode},
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index 706f18c..e3cdee6 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "HardwareBufferRenderer"
#define ATRACE_TAG ATRACE_TAG_VIEW
#include <GraphicsJNI.h>
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d04de37..d15b1680 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "ThreadedRenderer"
#define ATRACE_TAG ATRACE_TAG_VIEW
#include <FrameInfo.h>
@@ -27,7 +25,7 @@
#include <SkColorSpace.h>
#include <SkData.h>
#include <SkImage.h>
-#include <SkImagePriv.h>
+#include <SkImageAndroid.h>
#include <SkPicture.h>
#include <SkPixmap.h>
#include <SkSerialProcs.h>
@@ -35,7 +33,9 @@
#include <SkTypeface.h>
#include <dlfcn.h>
#include <gui/TraceUtils.h>
+#include <include/encode/SkPngEncoder.h>
#include <inttypes.h>
+#include <log/log.h>
#include <media/NdkImage.h>
#include <media/NdkImageReader.h>
#include <nativehelper/JNIPlatformHelp.h>
@@ -58,6 +58,7 @@
#include "JvmErrorReporter.h"
#include "android_graphics_HardwareRendererObserver.h"
+#include "utils/ForceDark.h"
namespace android {
@@ -477,7 +478,7 @@
// actually cross thread boundaries here, make a copy so it's immutable proper
if (bitmap && !bitmap->isImmutable()) {
ATRACE_NAME("Copying mutable bitmap");
- return SkImage::MakeFromBitmap(*bitmap);
+ return SkImages::RasterFromBitmap(*bitmap);
}
if (img->isTextureBacked()) {
ATRACE_NAME("Readback of texture image");
@@ -497,7 +498,7 @@
return sk_ref_sp(img);
}
bm.setImmutable();
- return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode);
+ return SkImages::PinnableRasterFromBitmap(bm);
}
return sk_ref_sp(img);
}
@@ -524,7 +525,16 @@
if (iter != context->mTextureMap.end()) {
img = iter->second.get();
}
- return img->encodeToData();
+ if (!img) {
+ return nullptr;
+ }
+ // The following encode (specifically the pixel readback) will fail on a
+ // texture-backed image. They should already be raster images, but on
+ // the off-chance they aren't, we will just serialize it as nothing.
+ if (img->isTextureBacked()) {
+ return SkData::MakeEmpty();
+ }
+ return SkPngEncoder::Encode(nullptr, img, {});
}
void serialize(SkWStream* stream) const override {
@@ -815,10 +825,10 @@
proxy->allocateBuffers();
}
-static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jboolean enable) {
+static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz, jlong proxyPtr,
+ jint type) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setForceDark(enable);
+ proxy->setForceDark(static_cast<ForceDarkType>(type));
}
static void android_view_ThreadedRenderer_preload(JNIEnv*, jclass) {
@@ -1007,7 +1017,7 @@
{"nSetIsolatedProcess", "(Z)V", (void*)android_view_ThreadedRenderer_setIsolatedProcess},
{"nSetContextPriority", "(I)V", (void*)android_view_ThreadedRenderer_setContextPriority},
{"nAllocateBuffers", "(J)V", (void*)android_view_ThreadedRenderer_allocateBuffers},
- {"nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark},
+ {"nSetForceDark", "(JI)V", (void*)android_view_ThreadedRenderer_setForceDark},
{"nSetDisplayDensityDpi", "(I)V",
(void*)android_view_ThreadedRenderer_setDisplayDensityDpi},
{"nInitDisplayInfo", "(IIFIJJZZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 2a218a2..a7d6423 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -568,6 +568,7 @@
struct {
jclass clazz;
jmethodID callPositionChanged;
+ jmethodID callPositionChanged2;
jmethodID callApplyStretch;
jmethodID callPositionLost;
} gPositionListener;
@@ -589,14 +590,31 @@
virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) override {
if (CC_UNLIKELY(!mListener || !info.updateWindowPositions)) return;
- Matrix4 transform;
- info.damageAccumulator->computeCurrentTransform(&transform);
const RenderProperties& props = node.properties();
+ const bool enableClip = Properties::clipSurfaceViews;
- uirenderer::Rect bounds(props.getWidth(), props.getHeight());
+ Matrix4 transform;
+ SkIRect clipBounds;
+ if (enableClip) {
+ uirenderer::Rect initialClipBounds;
+ const auto clipFlags = props.getClippingFlags();
+ if (clipFlags) {
+ props.getClippingRectForFlags(clipFlags, &initialClipBounds);
+ } else {
+ // Works for RenderNode::damageSelf()
+ initialClipBounds.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
+ }
+ clipBounds =
+ info.damageAccumulator
+ ->computeClipAndTransform(initialClipBounds.toSkRect(), &transform)
+ .roundOut();
+ } else {
+ info.damageAccumulator->computeCurrentTransform(&transform);
+ }
bool useStretchShader =
Properties::getStretchEffectBehavior() != StretchEffectBehavior::UniformScale;
// Compute the transform bounds first before calculating the stretch
+ uirenderer::Rect bounds(props.getWidth(), props.getHeight());
transform.mapRect(bounds);
bool hasStretch = useStretchShader && info.stretchEffectCount;
@@ -614,10 +632,11 @@
bounds.roundOut();
}
- if (mPreviousPosition == bounds) {
+ if (mPreviousPosition == bounds && mPreviousClip == clipBounds) {
return;
}
mPreviousPosition = bounds;
+ mPreviousClip = clipBounds;
ATRACE_NAME("Update SurfaceView position");
@@ -629,11 +648,23 @@
// In particular if the app removes a view from the view tree before
// this callback is dispatched, then we lose the position
// information for this frame.
- jboolean keepListening = env->CallStaticBooleanMethod(
- gPositionListener.clazz, gPositionListener.callPositionChanged, mListener,
- static_cast<jlong>(info.canvasContext.getFrameNumber()),
- static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
- static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom));
+ jboolean keepListening;
+ if (!enableClip) {
+ keepListening = env->CallStaticBooleanMethod(
+ gPositionListener.clazz, gPositionListener.callPositionChanged, mListener,
+ static_cast<jlong>(info.canvasContext.getFrameNumber()),
+ static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
+ static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom));
+ } else {
+ keepListening = env->CallStaticBooleanMethod(
+ gPositionListener.clazz, gPositionListener.callPositionChanged2, mListener,
+ static_cast<jlong>(info.canvasContext.getFrameNumber()),
+ static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
+ static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom),
+ static_cast<jint>(clipBounds.fLeft), static_cast<jint>(clipBounds.fTop),
+ static_cast<jint>(clipBounds.fRight),
+ static_cast<jint>(clipBounds.fBottom));
+ }
if (!keepListening) {
env->DeleteGlobalRef(mListener);
mListener = nullptr;
@@ -738,6 +769,7 @@
JavaVM* mVm;
jobject mListener;
uirenderer::Rect mPreviousPosition;
+ uirenderer::Rect mPreviousClip;
};
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
@@ -866,6 +898,8 @@
gPositionListener.clazz = MakeGlobalRefOrDie(env, clazz);
gPositionListener.callPositionChanged = GetStaticMethodIDOrDie(
env, clazz, "callPositionChanged", "(Ljava/lang/ref/WeakReference;JIIII)Z");
+ gPositionListener.callPositionChanged2 = GetStaticMethodIDOrDie(
+ env, clazz, "callPositionChanged2", "(Ljava/lang/ref/WeakReference;JIIIIIIII)Z");
gPositionListener.callApplyStretch = GetStaticMethodIDOrDie(
env, clazz, "callApplyStretch", "(Ljava/lang/ref/WeakReference;JFFFFFFFFFF)Z");
gPositionListener.callPositionLost = GetStaticMethodIDOrDie(
diff --git a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
index 764eff9..b86c74fe 100644
--- a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
+++ b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <Interpolator.h>
#include <cutils/log.h>
diff --git a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
index c6d26f8..40be924 100644
--- a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
+++ b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <Animator.h>
#include <Interpolator.h>
#include <RenderProperties.h>
diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
index 9cffceb..ade48f2 100644
--- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-#include "GraphicsJNI.h"
+#include <hwui/Paint.h>
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
#include "PathParser.h"
#include "VectorDrawable.h"
-#include <hwui/Paint.h>
-
namespace android {
using namespace uirenderer;
using namespace uirenderer::VectorDrawable;
@@ -108,8 +108,9 @@
Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
SkRect rect;
GraphicsJNI::jrect_to_rect(env, jrect, &rect);
- SkColorFilter* colorFilter = reinterpret_cast<SkColorFilter*>(colorFilterPtr);
- return tree->draw(canvas, colorFilter, rect, needsMirroring, canReuseCache);
+ auto colorFilter = ColorFilter::fromJava(colorFilterPtr);
+ auto skColorFilter = colorFilter != nullptr ? colorFilter->getInstance() : nullptr;
+ return tree->draw(canvas, skColorFilter.get(), rect, needsMirroring, canReuseCache);
}
/**
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 1af60b2..f405aba 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
#include "Font.h"
#include "SkData.h"
#include "SkFont.h"
@@ -40,6 +37,7 @@
#include <minikin/LocaleList.h>
#include <minikin/SystemFonts.h>
#include <ui/FatVector.h>
+#include <utils/TypefaceUtils.h>
#include <memory>
@@ -127,7 +125,7 @@
static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong builderPtr,
jint weight, jboolean italic, jint ttcIndex) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr));
// Reconstruct SkTypeface with different arguments from existing SkTypeface.
@@ -159,7 +157,7 @@
static jfloat Font_getGlyphBounds(JNIEnv* env, jobject, jlong fontHandle, jint glyphId,
jlong paintHandle, jobject rect) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
SkFont* skFont = &paint->getSkFont();
@@ -179,7 +177,7 @@
static jfloat Font_getFontMetrics(JNIEnv* env, jobject, jlong fontHandle, jlong paintHandle,
jobject metricsObj) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
- MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
SkFont* skFont = &paint->getSkFont();
@@ -209,7 +207,7 @@
// Fast Native
static jobject Font_newByteBuffer(JNIEnv* env, jobject, jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
return env->NewDirectByteBuffer(const_cast<void*>(minikinFont->GetFontData()),
minikinFont->GetFontSize());
}
@@ -217,7 +215,7 @@
// Critical Native
static jlong Font_getBufferAddress(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
- return reinterpret_cast<jlong>(font->font->typeface()->GetFontData());
+ return reinterpret_cast<jlong>(font->font->baseTypeface()->GetFontData());
}
// Critical Native
@@ -236,7 +234,7 @@
}
return env->NewStringUTF(path.c_str());
} else {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
const std::string& path = minikinFont->GetFontPath();
if (path.empty()) {
return nullptr;
@@ -275,7 +273,7 @@
reader.skipString(); // fontPath
return reader.read<int>();
} else {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
return minikinFont->GetFontIndex();
}
}
@@ -289,7 +287,7 @@
reader.skip<int>(); // fontIndex
return reader.readArray<minikin::FontVariation>().second;
} else {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
return minikinFont->GetAxes().size();
}
}
@@ -304,7 +302,7 @@
reader.skip<int>(); // fontIndex
var = reader.readArray<minikin::FontVariation>().first[index];
} else {
- const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
var = minikinFont->GetAxes().at(index);
}
uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
@@ -314,7 +312,7 @@
// Critical Native
static jint Font_getSourceId(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
- return font->font->typeface()->GetSourceId();
+ return font->font->baseTypeface()->GetSourceId();
}
static jlongArray Font_getAvailableFontSet(JNIEnv* env, jobject) {
@@ -462,7 +460,7 @@
args.setCollectionIndex(ttcIndex);
args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args));
if (face == nullptr) {
return nullptr;
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index ee158ee..462c8c8 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
#include "graphics_jni_helpers.h"
#include <nativehelper/ScopedUtfChars.h>
@@ -60,7 +57,7 @@
// Regular JNI
static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr,
jstring langTags, jint variant, jboolean isCustomFallback,
- jboolean isDefaultFallback) {
+ jboolean isDefaultFallback, jint variationFamilyType) {
std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr));
uint32_t localeId;
if (langTags == nullptr) {
@@ -71,7 +68,8 @@
}
std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts),
- isCustomFallback, isDefaultFallback);
+ isCustomFallback, isDefaultFallback,
+ static_cast<minikin::VariationFamilyType>(variationFamilyType));
if (family->getCoverage().length() == 0) {
// No coverage means minikin rejected given font for some reasons.
jniThrowException(env, "java/lang/IllegalArgumentException",
@@ -121,7 +119,7 @@
static const JNINativeMethod gFontFamilyBuilderMethods[] = {
{"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder},
{"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont},
- {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build},
+ {"nBuild", "(JLjava/lang/String;IZZI)J", (void*)FontFamily_Builder_build},
{"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc},
};
diff --git a/libs/hwui/jni/pdf/PdfEditor.cpp b/libs/hwui/jni/pdf/PdfEditor.cpp
index 427bafa..3b18f5f 100644
--- a/libs/hwui/jni/pdf/PdfEditor.cpp
+++ b/libs/hwui/jni/pdf/PdfEditor.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "PdfEditor"
-
#include <sys/types.h>
#include <unistd.h>
diff --git a/libs/hwui/jni/pdf/PdfUtils.cpp b/libs/hwui/jni/pdf/PdfUtils.cpp
index 06d2028..6887fda 100644
--- a/libs/hwui/jni/pdf/PdfUtils.cpp
+++ b/libs/hwui/jni/pdf/PdfUtils.cpp
@@ -16,14 +16,11 @@
#include "PdfUtils.h"
-#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
#include "fpdfview.h"
-
-#undef LOG_TAG
-#define LOG_TAG "PdfUtils"
-#include <utils/Log.h>
+#include "jni.h"
namespace android {
diff --git a/libs/hwui/jni/text/GraphemeBreak.cpp b/libs/hwui/jni/text/GraphemeBreak.cpp
index 55f03bd..322af7e 100644
--- a/libs/hwui/jni/text/GraphemeBreak.cpp
+++ b/libs/hwui/jni/text/GraphemeBreak.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "GraphemeBreaker"
-
#include <minikin/GraphemeBreak.h>
#include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/jni/text/LineBreaker.cpp b/libs/hwui/jni/text/LineBreaker.cpp
index 6986517..c512256 100644
--- a/libs/hwui/jni/text/LineBreaker.cpp
+++ b/libs/hwui/jni/text/LineBreaker.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "LineBreaker"
-
#include "utils/misc.h"
#include "utils/Log.h"
#include "graphics_jni_helpers.h"
@@ -54,13 +51,12 @@
// set text and set a number of parameters for creating a layout (width, tabstops, strategy,
// hyphenFrequency)
-static jlong nInit(JNIEnv* env, jclass /* unused */,
- jint breakStrategy, jint hyphenationFrequency, jboolean isJustified, jintArray indents) {
+static jlong nInit(JNIEnv* env, jclass /* unused */, jint breakStrategy, jint hyphenationFrequency,
+ jboolean isJustified, jintArray indents, jboolean useBoundsForWidth) {
return reinterpret_cast<jlong>(new minikin::android::StaticLayoutNative(
static_cast<minikin::BreakStrategy>(breakStrategy),
- static_cast<minikin::HyphenationFrequency>(hyphenationFrequency),
- isJustified,
- jintArrayToFloatVector(env, indents)));
+ static_cast<minikin::HyphenationFrequency>(hyphenationFrequency), isJustified,
+ jintArrayToFloatVector(env, indents), useBoundsForWidth));
}
static void nFinish(jlong nativePtr) {
@@ -131,39 +127,44 @@
}
static const JNINativeMethod gMethods[] = {
- // Fast Natives
- {"nInit", "("
- "I" // breakStrategy
- "I" // hyphenationFrequency
- "Z" // isJustified
- "[I" // indents
- ")J", (void*) nInit},
+ // Fast Natives
+ {"nInit",
+ "("
+ "I" // breakStrategy
+ "I" // hyphenationFrequency
+ "Z" // isJustified
+ "[I" // indents
+ "Z" // useBoundsForWidth
+ ")J",
+ (void*)nInit},
- // Critical Natives
- {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc},
+ // Critical Natives
+ {"nGetReleaseFunc", "()J", (void*)nGetReleaseFunc},
- // Regular JNI
- {"nComputeLineBreaks", "("
- "J" // nativePtr
- "[C" // text
- "J" // MeasuredParagraph ptr.
- "I" // length
- "F" // firstWidth
- "I" // firstWidthLineCount
- "F" // restWidth
- "[F" // variableTabStops
- "F" // defaultTabStop
- "I" // indentsOffset
- ")J", (void*) nComputeLineBreaks},
+ // Regular JNI
+ {"nComputeLineBreaks",
+ "("
+ "J" // nativePtr
+ "[C" // text
+ "J" // MeasuredParagraph ptr.
+ "I" // length
+ "F" // firstWidth
+ "I" // firstWidthLineCount
+ "F" // restWidth
+ "[F" // variableTabStops
+ "F" // defaultTabStop
+ "I" // indentsOffset
+ ")J",
+ (void*)nComputeLineBreaks},
- // Result accessors, CriticalNatives
- {"nGetLineCount", "(J)I", (void*)nGetLineCount},
- {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset},
- {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth},
- {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent},
- {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent},
- {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag},
- {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc},
+ // Result accessors, CriticalNatives
+ {"nGetLineCount", "(J)I", (void*)nGetLineCount},
+ {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset},
+ {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth},
+ {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent},
+ {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent},
+ {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag},
+ {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc},
};
int register_android_graphics_text_LineBreaker(JNIEnv* env) {
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index c13c800..746745a 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "MeasuredText"
-
#include "GraphicsJNI.h"
#include "utils/misc.h"
#include "utils/Log.h"
@@ -65,13 +62,14 @@
// Regular JNI
static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
- jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end,
- jboolean isRtl) {
+ jlong paintPtr, jint lbStyle, jint lbWordStyle, jboolean hyphenation,
+ jint start, jint end, jboolean isRtl) {
Paint* paint = toPaint(paintPtr);
const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
toBuilder(builderPtr)
- ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl);
+ ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, hyphenation,
+ isRtl);
}
// Regular JNI
@@ -84,13 +82,14 @@
// Regular JNI
static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, jlong hintPtr,
jcharArray javaText, jboolean computeHyphenation,
- jboolean computeLayout, jboolean fastHyphenationMode) {
+ jboolean computeLayout, jboolean computeBounds,
+ jboolean fastHyphenationMode) {
ScopedCharArrayRO text(env, javaText);
const minikin::U16StringPiece textBuffer(text.get(), text.size());
// Pass the ownership to Java.
return toJLong(toBuilder(builderPtr)
- ->build(textBuffer, computeHyphenation, computeLayout,
+ ->build(textBuffer, computeHyphenation, computeLayout, computeBounds,
fastHyphenationMode, toMeasuredParagraph(hintPtr))
.release());
}
@@ -161,9 +160,9 @@
static const JNINativeMethod gMTBuilderMethods[] = {
// MeasuredParagraphBuilder native functions.
{"nInitBuilder", "()J", (void*)nInitBuilder},
- {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun},
+ {"nAddStyleRun", "(JJIIZIIZ)V", (void*)nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
- {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText},
+ {"nBuildMeasuredText", "(JJ[CZZZZ)J", (void*)nBuildMeasuredText},
{"nFreeBuilder", "(J)V", (void*)nFreeBuilder},
};
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 8e4dd53..6c05346 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#undef LOG_TAG
-#define LOG_TAG "TextShaper"
-
#include "graphics_jni_helpers.h"
#include <nativehelper/ScopedStringChars.h>
#include <nativehelper/ScopedPrimitiveArray.h>
@@ -62,7 +59,7 @@
const minikin::Font* font = layout.getFont(i);
if (seenFonts.find(font) != seenFonts.end()) continue;
minikin::MinikinExtent extent = {};
- font->typeface()->GetFontExtent(&extent, minikinPaint, layout.getFakery(i));
+ layout.typeface(i)->GetFontExtent(&extent, minikinPaint, layout.getFakery(i));
overallAscent = std::min(overallAscent, extent.ascent);
overallDescent = std::max(overallDescent, extent.descent);
}
@@ -148,6 +145,30 @@
}
// CriticalNative
+static jboolean TextShaper_Result_getFakeBold(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getFakery(i).isFakeBold();
+}
+
+// CriticalNative
+static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getFakery(i).isFakeItalic();
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getFakery(i).wghtAdjustment();
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getFakery(i).italAdjustment();
+}
+
+// CriticalNative
static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
std::shared_ptr<minikin::Font> fontRef = layout->layout.getFontRef(i);
@@ -185,15 +206,19 @@
};
static const JNINativeMethod gResultMethods[] = {
- { "nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount },
- { "nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance },
- { "nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent },
- { "nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent },
- { "nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId },
- { "nGetX", "(JI)F", (void*)TextShaper_Result_getX },
- { "nGetY", "(JI)F", (void*)TextShaper_Result_getY },
- { "nGetFont", "(JI)J", (void*)TextShaper_Result_getFont },
- { "nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc },
+ {"nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount},
+ {"nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance},
+ {"nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent},
+ {"nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent},
+ {"nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId},
+ {"nGetX", "(JI)F", (void*)TextShaper_Result_getX},
+ {"nGetY", "(JI)F", (void*)TextShaper_Result_getY},
+ {"nGetFont", "(JI)J", (void*)TextShaper_Result_getFont},
+ {"nGetFakeBold", "(JI)Z", (void*)TextShaper_Result_getFakeBold},
+ {"nGetFakeItalic", "(JI)Z", (void*)TextShaper_Result_getFakeItalic},
+ {"nGetWeightOverride", "(JI)F", (void*)TextShaper_Result_getWeightOverride},
+ {"nGetItalicOverride", "(JI)F", (void*)TextShaper_Result_getItalicOverride},
+ {"nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc},
};
int register_android_graphics_text_TextShaper(JNIEnv* env) {
diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
index ffad699..e81cbfb 100644
--- a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
+++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
@@ -21,6 +21,9 @@
#include "RenderNode.h"
#include "RenderNodeDrawable.h"
+#ifdef __ANDROID__
+#include "include/gpu/ganesh/SkImageGanesh.h"
+#endif
namespace android {
namespace uirenderer {
@@ -72,9 +75,17 @@
}
auto imageSubset = mImageSubset.roundOut();
- backdropImage =
- backdropImage->makeWithFilter(canvas->recordingContext(), backdropFilter, imageSubset,
- imageSubset, &mOutSubset, &mOutOffset);
+#ifdef __ANDROID__
+ if (canvas->recordingContext()) {
+ backdropImage =
+ SkImages::MakeWithFilter(canvas->recordingContext(), backdropImage, backdropFilter,
+ imageSubset, imageSubset, &mOutSubset, &mOutOffset);
+ } else
+#endif
+ {
+ backdropImage = SkImages::MakeWithFilter(backdropImage, backdropFilter, imageSubset,
+ imageSubset, &mOutSubset, &mOutOffset);
+ }
canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds,
SkSamplingOptions(SkFilterMode::kLinear), &mPaint,
SkCanvas::kStrict_SrcRectConstraint);
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 8d5967b..5d3fb30 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -21,10 +21,15 @@
#include "GrBackendSurface.h"
#include "RenderNode.h"
#include "SkAndroidFrameworkUtils.h"
+#include "SkCanvas.h"
+#include "SkCanvasAndroid.h"
#include "SkClipStack.h"
#include "SkRect.h"
#include "SkM44.h"
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include "include/gpu/GpuTypes.h" // from Skia
+#include <include/gpu/gl/GrGLTypes.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
#include "utils/GLUtils.h"
#include <effects/GainmapRenderer.h>
#include "renderthread/CanvasContext.h"
@@ -34,7 +39,7 @@
namespace skiapipeline {
static void setScissor(int viewportHeight, const SkIRect& clip) {
- SkASSERT(!clip.isEmpty());
+ LOG_FATAL_IF(clip.isEmpty(), "empty scissor clip");
// transform to Y-flipped GL space, and prevent negatives
GLint y = viewportHeight - clip.fBottom;
GLint height = (viewportHeight - clip.fTop) - y;
@@ -42,9 +47,9 @@
}
static void GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) {
- GrBackendRenderTarget renderTarget = canvas->topLayerBackendRenderTarget();
+ GrBackendRenderTarget renderTarget = skgpu::ganesh::TopLayerBackendRenderTarget(canvas);
GrGLFramebufferInfo fboInfo;
- LOG_ALWAYS_FATAL_IF(!renderTarget.getGLFramebufferInfo(&fboInfo),
+ LOG_ALWAYS_FATAL_IF(!GrBackendRenderTargets::GetGLFramebufferInfo(renderTarget, &fboInfo),
"getGLFrameBufferInfo failed");
*outFboID = fboInfo.fFBOID;
@@ -76,13 +81,13 @@
}
// flush will create a GrRenderTarget if not already present.
- canvas->flush();
+ directContext->flushAndSubmit();
GLuint fboID = 0;
SkISize fboSize;
GetFboDetails(canvas, &fboID, &fboSize);
- SkIRect surfaceBounds = canvas->topLayerBounds();
+ SkIRect surfaceBounds = skgpu::ganesh::TopLayerBounds(canvas);
SkIRect clipBounds = canvas->getDeviceClipBounds();
SkM44 mat4(canvas->getLocalToDevice());
SkRegion clipRegion;
@@ -95,12 +100,14 @@
SkImageInfo surfaceInfo =
canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height());
tmpSurface =
- SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo);
+ SkSurfaces::RenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo);
tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
GrGLFramebufferInfo fboInfo;
- if (!tmpSurface->getBackendRenderTarget(SkSurface::kFlushWrite_BackendHandleAccess)
- .getGLFramebufferInfo(&fboInfo)) {
+ if (!GrBackendRenderTargets::GetGLFramebufferInfo(
+ SkSurfaces::GetBackendRenderTarget(
+ tmpSurface.get(), SkSurfaces::BackendHandleAccess::kFlushWrite),
+ &fboInfo)) {
ALOGW("Unable to extract renderTarget info from offscreen canvas; aborting GLFunctor");
return;
}
@@ -163,7 +170,7 @@
// GL ops get inserted here if previous flush is missing, which could dirty the stencil
bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(tmpCanvas);
- tmpCanvas->flush(); // need this flush for the single op that draws into the stencil
+ directContext->flushAndSubmit(); // need this flush for the single op that draws into the stencil
// ensure that the framebuffer that the webview will render into is bound before after we
// draw into the stencil
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 9d72c23..ffa915a 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -15,21 +15,25 @@
*/
#include "RenderNodeDrawable.h"
+
#include <SkPaint.h>
#include <SkPaintFilterCanvas.h>
#include <SkPoint.h>
#include <SkRRect.h>
#include <SkRect.h>
#include <gui/TraceUtils.h>
+#include <include/effects/SkImageFilters.h>
+#ifdef __ANDROID__
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#endif
+
+#include <optional>
+
#include "RenderNode.h"
#include "SkiaDisplayList.h"
#include "StretchMask.h"
#include "TransformCanvas.h"
-#include <include/effects/SkImageFilters.h>
-
-#include <optional>
-
namespace android {
namespace uirenderer {
namespace skiapipeline {
@@ -52,6 +56,7 @@
int nestLevel) const {
LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver);
for (auto& child : displayList.mChildNodes) {
+ if (!child.getRenderNode()->isRenderable()) continue;
const RenderProperties& childProperties = child.getNodeProperties();
// immediate children cannot be projected on their parent
@@ -255,9 +260,19 @@
snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot();
if (imageFilter) {
auto subset = SkIRect::MakeWH(srcBounds.width(), srcBounds.height());
- snapshotImage = snapshotImage->makeWithFilter(recordingContext, imageFilter,
- subset, clipBounds.roundOut(),
- &srcBounds, &offset);
+
+#ifdef __ANDROID__
+ if (recordingContext) {
+ snapshotImage = SkImages::MakeWithFilter(
+ recordingContext, snapshotImage, imageFilter, subset,
+ clipBounds.roundOut(), &srcBounds, &offset);
+ } else
+#endif
+ {
+ snapshotImage = SkImages::MakeWithFilter(snapshotImage, imageFilter, subset,
+ clipBounds.roundOut(), &srcBounds,
+ &offset);
+ }
}
} else {
const auto snapshotResult = renderNode->updateSnapshotIfRequired(
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
index 11977bd..136740c 100644
--- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
@@ -27,7 +27,6 @@
#include <SkRect.h>
#include <SkScalar.h>
#include <SkShadowUtils.h>
-#include <include/private/SkShadowFlags.h>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 7495550..6ccb212 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -26,6 +26,7 @@
#include <string>
#include <vector>
+class GrDirectContext;
class SkData;
namespace android {
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index af2d3b3..5c8285a 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -66,6 +66,12 @@
}
}
+void SkiaDisplayList::visit(std::function<void(const RenderNode&)> func) const {
+ for (auto& child : mChildNodes) {
+ child.getRenderNode()->visit(func);
+ }
+}
+
static bool intersects(const SkISize screenSize, const Matrix4& mat, const SkRect& bounds) {
Vector3 points[] = { Vector3 {bounds.fLeft, bounds.fTop, 0},
Vector3 {bounds.fRight, bounds.fTop, 0},
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 7af31a4..b9dc1c4 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -96,6 +96,8 @@
bool hasText() const { return mDisplayList.hasText(); }
+ bool hasFill() const { return mDisplayList.hasFill(); }
+
/**
* Attempts to reset and reuse this DisplayList.
*
@@ -145,6 +147,8 @@
*/
void updateChildren(std::function<void(RenderNode*)> updateFn);
+ void visit(std::function<void(const RenderNode&)> func) const;
+
/**
* Returns true if there is a child render node that is a projection receiver.
*/
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
index 1042703..814b682 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
@@ -32,13 +32,13 @@
, mTotalSize("bytes", 0)
, mPurgeableSize("bytes", 0) {}
-const char* SkiaMemoryTracer::mapName(const char* resourceName) {
+std::optional<std::string> SkiaMemoryTracer::mapName(const std::string& resourceName) {
for (auto& resource : mResourceMap) {
- if (SkStrContains(resourceName, resource.first)) {
+ if (resourceName.find(resource.first) != std::string::npos) {
return resource.second;
}
}
- return nullptr;
+ return std::nullopt;
}
void SkiaMemoryTracer::processElement() {
@@ -62,7 +62,7 @@
}
// find the type if one exists
- const char* type;
+ std::string type;
auto typeResult = mCurrentValues.find("type");
if (typeResult != mCurrentValues.end()) {
type = typeResult->second.units;
@@ -71,14 +71,13 @@
}
// compute the type if we are itemizing or use the default "size" if we are not
- const char* key = (mItemizeType) ? type : sizeResult->first;
- SkASSERT(key != nullptr);
+ std::string key = (mItemizeType) ? type : sizeResult->first;
// compute the top level element name using either the map or category key
- const char* resourceName = mapName(mCurrentElement.c_str());
- if (mCategoryKey != nullptr) {
+ std::optional<std::string> resourceName = mapName(mCurrentElement);
+ if (mCategoryKey) {
// find the category if one exists
- auto categoryResult = mCurrentValues.find(mCategoryKey);
+ auto categoryResult = mCurrentValues.find(*mCategoryKey);
if (categoryResult != mCurrentValues.end()) {
resourceName = categoryResult->second.units;
} else if (mItemizeType) {
@@ -87,11 +86,11 @@
}
// if we don't have a pretty name then use the dumpName
- if (resourceName == nullptr) {
- resourceName = mCurrentElement.c_str();
+ if (!resourceName) {
+ resourceName = mCurrentElement;
}
- auto result = mResults.find(resourceName);
+ auto result = mResults.find(*resourceName);
if (result != mResults.end()) {
auto& resourceValues = result->second;
typeResult = resourceValues.find(key);
@@ -106,7 +105,7 @@
TraceValue sizeValue = sizeResult->second;
mCurrentValues.clear();
mCurrentValues.insert({key, sizeValue});
- mResults.insert({resourceName, mCurrentValues});
+ mResults.insert({*resourceName, mCurrentValues});
}
}
@@ -139,8 +138,9 @@
for (const auto& typedValue : namedItem.second) {
TraceValue traceValue = convertUnits(typedValue.second);
const char* entry = (traceValue.count > 1) ? "entries" : "entry";
- log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first, traceValue.value,
- traceValue.units, traceValue.count, entry);
+ log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first.c_str(),
+ traceValue.value, traceValue.units.c_str(), traceValue.count,
+ entry);
}
} else {
auto result = namedItem.second.find("size");
@@ -148,7 +148,8 @@
TraceValue traceValue = convertUnits(result->second);
const char* entry = (traceValue.count > 1) ? "entries" : "entry";
log.appendFormat(" %s: %.2f %s (%d %s)\n", namedItem.first.c_str(),
- traceValue.value, traceValue.units, traceValue.count, entry);
+ traceValue.value, traceValue.units.c_str(), traceValue.count,
+ entry);
}
}
}
@@ -156,7 +157,7 @@
size_t SkiaMemoryTracer::total() {
processElement();
- if (!strcmp("bytes", mTotalSize.units)) {
+ if ("bytes" == mTotalSize.units) {
return mTotalSize.value;
}
return 0;
@@ -166,16 +167,16 @@
TraceValue total = convertUnits(mTotalSize);
TraceValue purgeable = convertUnits(mPurgeableSize);
log.appendFormat(" %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
- total.value, total.units, purgeable.value, purgeable.units);
+ total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
}
SkiaMemoryTracer::TraceValue SkiaMemoryTracer::convertUnits(const TraceValue& value) {
TraceValue output(value);
- if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+ if ("bytes" == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "KB";
}
- if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+ if ("KB" == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "MB";
}
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
index cba3b04..dbfc86b 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
@@ -16,8 +16,9 @@
#pragma once
-#include <SkString.h>
#include <SkTraceMemoryDump.h>
+#include <optional>
+#include <string>
#include <utils/String8.h>
#include <unordered_map>
#include <vector>
@@ -60,17 +61,17 @@
TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
- const char* units;
+ std::string units;
float value;
int count;
};
- const char* mapName(const char* resourceName);
+ std::optional<std::string> mapName(const std::string& resourceName);
void processElement();
TraceValue convertUnits(const TraceValue& value);
const std::vector<ResourcePair> mResourceMap;
- const char* mCategoryKey = nullptr;
+ std::optional<std::string> mCategoryKey;
const bool mItemizeType;
// variables storing the size of all elements being dumped
@@ -79,12 +80,12 @@
// variables storing information on the current node being dumped
std::string mCurrentElement;
- std::unordered_map<const char*, TraceValue> mCurrentValues;
+ std::unordered_map<std::string, TraceValue> mCurrentValues;
// variable that stores the final format of the data after the individual elements are processed
- std::unordered_map<std::string, std::unordered_map<const char*, TraceValue>> mResults;
+ std::unordered_map<std::string, std::unordered_map<std::string, TraceValue>> mResults;
};
} /* namespace skiapipeline */
} /* namespace uirenderer */
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 56f1e64..c8d5987 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -16,6 +16,9 @@
#include "SkiaOpenGLPipeline.h"
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/gl/GrGLTypes.h>
#include <GrBackendSurface.h>
#include <SkBlendMode.h>
#include <SkImageInfo.h>
@@ -138,7 +141,8 @@
LOG_ALWAYS_FATAL("Unsupported color type.");
}
- GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo);
+ auto backendRT = GrBackendRenderTargets::MakeGL(frame.width(), frame.height(), 0,
+ STENCIL_BUFFER_SIZE, fboInfo);
SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag,
kUnknown_SkPixelGeometry);
@@ -150,9 +154,9 @@
surface = getBufferSkSurface(bufferParams);
preTransform = bufferParams.getTransform();
} else {
- surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
- getSurfaceOrigin(), colorType,
- mSurfaceColorSpace, &props);
+ surface = SkSurfaces::WrapBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
+ getSurfaceOrigin(), colorType,
+ mSurfaceColorSpace, &props);
preTransform = SkMatrix::I();
}
@@ -175,7 +179,7 @@
{
ATRACE_NAME("flush commands");
- surface->flushAndSubmit();
+ skgpu::ganesh::FlushAndSubmit(surface);
}
layerUpdateQueue->clear();
@@ -184,11 +188,12 @@
dumpResourceCacheUsage();
}
- return {true, IRenderPipeline::DrawResult::kUnknownTime};
+ return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}};
}
-bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
- FrameInfo* currentFrameInfo, bool* requireSwap) {
+bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) {
GL_CHECKPOINT(LOW);
// Even if we decided to cancel the frame, from the perspective of jank
@@ -199,7 +204,7 @@
return false;
}
- *requireSwap = drew || mEglManager.damageRequiresSwap();
+ *requireSwap = drawResult.success || mEglManager.damageRequiresSwap();
if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
return false;
@@ -246,8 +251,7 @@
if (mEglSurface != EGL_NO_SURFACE) {
const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer);
- const bool isPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
- ALOGE_IF(preserveBuffer != isPreserved, "Unable to match the desired swap behavior.");
+ mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
return true;
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 0325593..ebe8b6e 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -45,8 +45,9 @@
const renderthread::HardwareBufferRenderParams& bufferParams,
std::mutex& profilerLock) override;
GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
- bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
- FrameInfo* currentFrameInfo, bool* requireSwap) override;
+ bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) override;
DeferredLayerUpdater* createTextureLayer() override;
bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
[[nodiscard]] android::base::unique_fd flush() override;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index cb23bcc..e0f1f6e 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -16,14 +16,16 @@
#include "SkiaPipeline.h"
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/encode/SkPngEncoder.h>
#include <SkCanvas.h>
#include <SkColor.h>
#include <SkColorSpace.h>
#include <SkData.h>
#include <SkImage.h>
-#include <SkImageEncoder.h>
+#include <SkImageAndroid.h>
#include <SkImageInfo.h>
-#include <SkImagePriv.h>
#include <SkMatrix.h>
#include <SkMultiPictureDocument.h>
#include <SkOverdrawCanvas.h>
@@ -75,7 +77,7 @@
return false;
}
for (SkImage* image : mutableImages) {
- if (SkImage_pinAsTexture(image, mRenderThread.getGrContext())) {
+ if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
mPinnedImages.emplace_back(sk_ref_sp(image));
} else {
return false;
@@ -86,7 +88,7 @@
void SkiaPipeline::unpinImages() {
for (auto& image : mPinnedImages) {
- SkImage_unpinAsTexture(image.get(), mRenderThread.getGrContext());
+ skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
}
mPinnedImages.clear();
}
@@ -187,9 +189,9 @@
kPremul_SkAlphaType, getSurfaceColorSpace());
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
- node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes, info, 0,
- this->getSurfaceOrigin(), &props));
+ node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes, info, 0,
+ this->getSurfaceOrigin(), &props));
if (node->getLayerSurface()) {
// update the transform in window of the layer to reset its origin wrt light source
// position
@@ -222,8 +224,8 @@
ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
auto image = bitmap->makeImage();
if (image.get()) {
- SkImage_pinAsTexture(image.get(), context);
- SkImage_unpinAsTexture(image.get(), context);
+ skgpu::ganesh::PinAsTexture(context, image.get());
+ skgpu::ganesh::UnpinTexture(context, image.get());
// A submit is necessary as there may not be a frame coming soon, so without a call
// to submit these texture uploads can just sit in the queue building up until
// we run out of RAM
@@ -439,6 +441,13 @@
procs.fTypefaceProc = [](SkTypeface* tf, void* ctx){
return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
};
+ procs.fImageProc = [](SkImage* img, void* ctx) -> sk_sp<SkData> {
+ GrDirectContext* dCtx = static_cast<GrDirectContext*>(ctx);
+ return SkPngEncoder::Encode(dCtx,
+ img,
+ SkPngEncoder::Options{});
+ };
+ procs.fImageCtx = mRenderThread.getGrContext();
auto data = picture->serialize(&procs);
savePictureAsync(data, mCapturedFile);
mCaptureSequence = 0;
@@ -621,7 +630,7 @@
auto bufferColorSpace = bufferParams.getColorSpace();
if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
!SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
- mBufferSurface = SkSurface::MakeFromAHardwareBuffer(
+ mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
bufferColorSpace, nullptr, true);
mBufferColorSpace = bufferColorSpace;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 58c14c1..e917f9a 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -236,6 +236,17 @@
}
}
+void SkiaRecordingCanvas::onFilterPaint(android::Paint& paint) {
+ INHERITED::onFilterPaint(paint);
+ SkShader* shader = paint.getShader();
+ // TODO(b/264559422): This only works for very specifically a BitmapShader.
+ // It's better than nothing, though
+ SkImage* image = shader ? shader->isAImage(nullptr, nullptr) : nullptr;
+ if (image) {
+ mDisplayList->mMutableImages.push_back(image);
+ }
+}
+
void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
auto payload = DrawImagePayload(bitmap);
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index a8e4580..3bd091d 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -105,6 +105,8 @@
void handleMutableImages(Bitmap& bitmap, DrawImagePayload& payload);
+ void onFilterPaint(Paint& paint) override;
+
using INHERITED = SkiaCanvas;
};
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 7d19232..fd0a8e0 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -87,7 +87,7 @@
}
if (backBuffer.get() == nullptr) {
- return {false, -1};
+ return {false, -1, android::base::unique_fd{}};
}
// update the coordinates of the global light position based on surface rotation
@@ -106,15 +106,15 @@
std::scoped_lock lock(profilerLock);
SkCanvas* profileCanvas = backBuffer->getCanvas();
SkAutoCanvasRestore saver(profileCanvas, true);
- profileCanvas->concat(mVkSurface->getCurrentPreTransform());
+ profileCanvas->concat(preTransform);
SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
profiler->draw(profileRenderer);
}
- nsecs_t submissionTime = IRenderPipeline::DrawResult::kUnknownTime;
+ VulkanManager::VkDrawResult drawResult;
{
ATRACE_NAME("flush commands");
- submissionTime = vulkanManager().finishFrame(backBuffer.get());
+ drawResult = vulkanManager().finishFrame(backBuffer.get());
}
layerUpdateQueue->clear();
@@ -123,11 +123,12 @@
dumpResourceCacheUsage();
}
- return {true, submissionTime};
+ return {true, drawResult.submissionTime, std::move(drawResult.presentFence)};
}
-bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
- FrameInfo* currentFrameInfo, bool* requireSwap) {
+bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) {
// Even if we decided to cancel the frame, from the perspective of jank
// metrics the frame was swapped at this point
currentFrameInfo->markSwapBuffers();
@@ -136,10 +137,10 @@
return false;
}
- *requireSwap = drew;
+ *requireSwap = drawResult.success;
if (*requireSwap) {
- vulkanManager().swapBuffers(mVkSurface, screenDirty);
+ vulkanManager().swapBuffers(mVkSurface, screenDirty, std::move(drawResult.presentFence));
}
return *requireSwap;
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 37b86f1..624eaa5 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -45,8 +45,9 @@
const renderthread::HardwareBufferRenderParams& bufferParams,
std::mutex& profilerLock) override;
GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
- bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
- FrameInfo* currentFrameInfo, bool* requireSwap) override;
+ bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) override;
DeferredLayerUpdater* createTextureLayer() override;
[[nodiscard]] android::base::unique_fd flush() override;
diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp
index cad3703..1676787 100644
--- a/libs/hwui/pipeline/skia/StretchMask.cpp
+++ b/libs/hwui/pipeline/skia/StretchMask.cpp
@@ -18,14 +18,13 @@
#include "SkBlendMode.h"
#include "SkCanvas.h"
#include "SkSurface.h"
-#include "include/gpu/GpuTypes.h" // from Skia
#include "TransformCanvas.h"
#include "SkiaDisplayList.h"
using android::uirenderer::StretchMask;
-void StretchMask::draw(GrRecordingContext* context,
+void StretchMask::draw(GrRecordingContext*,
const StretchEffect& stretch,
const SkRect& bounds,
skiapipeline::SkiaDisplayList* displayList,
@@ -35,16 +34,14 @@
if (mMaskSurface == nullptr || mMaskSurface->width() != width ||
mMaskSurface->height() != height) {
// Create a new surface if we don't have one or our existing size does
- // not match.
- mMaskSurface = SkSurface::MakeRenderTarget(
- context,
- skgpu::Budgeted::kYes,
- SkImageInfo::Make(
- width,
- height,
- SkColorType::kAlpha_8_SkColorType,
- SkAlphaType::kPremul_SkAlphaType)
- );
+ // not match. SkCanvas::makeSurface returns a new surface that will
+ // be GPU-backed if canvas was also.
+ mMaskSurface = canvas->makeSurface(SkImageInfo::Make(
+ width,
+ height,
+ SkColorType::kAlpha_8_SkColorType,
+ SkAlphaType::kPremul_SkAlphaType
+ ));
mIsDirty = true;
}
@@ -53,7 +50,7 @@
// Make sure to apply target transformation to the mask canvas
// to ensure the replayed drawing commands generate the same result
auto previousMatrix = displayList->mParentMatrix;
- displayList->mParentMatrix = maskCanvas->getTotalMatrix();
+ displayList->mParentMatrix = maskCanvas->getLocalToDeviceAs3x3();
maskCanvas->save();
maskCanvas->drawColor(0, SkBlendMode::kClear);
TransformCanvas transformCanvas(maskCanvas, SkBlendMode::kSrcOver);
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index adf3c06..475b110 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -35,6 +35,8 @@
#include "effects/GainmapRenderer.h"
#include <SkBlendMode.h>
+#include <SkImage.h>
+#include <SkImageAndroid.h>
namespace android {
namespace uirenderer {
@@ -183,9 +185,9 @@
// drawing into the offscreen surface, so we need to reset it here.
canvas->resetMatrix();
- auto functorImage = SkImage::MakeFromAHardwareBuffer(mFrameBuffer.get(), kPremul_SkAlphaType,
- canvas->imageInfo().refColorSpace(),
- kBottomLeft_GrSurfaceOrigin);
+ auto functorImage = SkImages::DeferredFromAHardwareBuffer(
+ mFrameBuffer.get(), kPremul_SkAlphaType, canvas->imageInfo().refColorSpace(),
+ kBottomLeft_GrSurfaceOrigin);
canvas->drawImage(functorImage, 0, 0, SkSamplingOptions(), &paint);
canvas->restore();
}
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index eb1f930..ed3fabc 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -24,8 +24,7 @@
namespace uirenderer {
/**
- * Structure used by OpenGLRenderer::callDrawGLFunction() to pass and
- * receive data from OpenGL functors.
+ * Structure used to pass and receive data from OpenGL functors.
*/
struct DrawGlInfo {
// Input: current clip rect
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index f695556..30d4612 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -17,6 +17,7 @@
#include "CacheManager.h"
#include <GrContextOptions.h>
+#include <GrTypes.h>
#include <SkExecutor.h>
#include <SkGraphics.h>
#include <math.h>
@@ -110,13 +111,18 @@
contextOptions->fPersistentCache = &cache;
}
+static GrPurgeResourceOptions toSkiaEnum(bool scratchOnly) {
+ return scratchOnly ? GrPurgeResourceOptions::kScratchResourcesOnly :
+ GrPurgeResourceOptions::kAllResources;
+}
+
void CacheManager::trimMemory(TrimLevel mode) {
if (!mGrContext) {
return;
}
// flush and submit all work to the gpu and wait for it to finish
- mGrContext->flushAndSubmit(/*syncCpu=*/true);
+ mGrContext->flushAndSubmit(GrSyncCpu::kYes);
switch (mode) {
case TrimLevel::BACKGROUND:
@@ -130,7 +136,7 @@
// that have persistent data to be purged in LRU order.
mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
- mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly);
+ mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
mGrContext->setResourceCacheLimit(mMaxResourceBytes);
SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
break;
@@ -150,7 +156,7 @@
case CacheTrimLevel::ALL_CACHES:
SkGraphics::PurgeAllCaches();
if (mGrContext) {
- mGrContext->purgeUnlockedResources(false);
+ mGrContext->purgeUnlockedResources(GrPurgeResourceOptions::kAllResources);
}
break;
default:
@@ -163,7 +169,8 @@
return;
}
mGrContext->flushAndSubmit();
- mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30));
+ mGrContext->performDeferredCleanup(std::chrono::seconds(30),
+ GrPurgeResourceOptions::kAllResources);
}
void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
@@ -282,9 +289,10 @@
const nsecs_t frameCompleteNanos = mFrameCompletions[0];
const nsecs_t frameDiffNanos = now - frameCompleteNanos;
const nsecs_t cleanupMillis =
- ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention));
+ ns2ms(std::clamp(frameDiffNanos, mMemoryPolicy.minimumResourceRetention,
+ mMemoryPolicy.maximumResourceRetention));
mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
- mMemoryPolicy.purgeScratchOnly);
+ toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
}
}
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 5e43ac2..bcfa4f3 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -64,12 +64,13 @@
void unregisterCanvasContext(CanvasContext* context);
void onContextStopped(CanvasContext* context);
+ bool areAllContextsStopped();
+
private:
friend class RenderThread;
explicit CacheManager(RenderThread& thread);
void setupCacheLimits();
- bool areAllContextsStopped();
void checkUiHidden();
void scheduleDestroyContext();
void cancelDestroyContext();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f690783..9c7f7cc 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -123,8 +123,9 @@
, mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
, mContentDrawBounds(0, 0, 0, 0)
, mRenderPipeline(std::move(renderPipeline))
- , mHintSessionWrapper(uiThreadId, renderThreadId) {
+ , mHintSessionWrapper(std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId)) {
mRenderThread.cacheManager().registerCanvasContext(this);
+ mRenderThread.renderState().registerContextCallback(this);
rootRenderNode->makeRoot();
mRenderNodes.emplace_back(rootRenderNode);
mProfiler.setDensity(DeviceInfo::getDensity());
@@ -137,6 +138,8 @@
}
mRenderNodes.clear();
mRenderThread.cacheManager().unregisterCanvasContext(this);
+ mRenderThread.renderState().removeContextCallback(this);
+ mHintSessionWrapper->destroy();
}
void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -160,6 +163,7 @@
destroyHardwareResources();
mAnimationContext->destroy();
mRenderThread.cacheManager().onContextStopped(this);
+ mHintSessionWrapper->delayedDestroy(mRenderThread, 2_s, mHintSessionWrapper);
}
static void setBufferCount(ANativeWindow* window) {
@@ -356,8 +360,9 @@
return true;
}
-static bool wasSkipped(FrameInfo* info) {
- return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame);
+static std::optional<SkippedFrameReason> wasSkipped(FrameInfo* info) {
+ if (info) return info->getSkippedFrameReason();
+ return std::nullopt;
}
bool CanvasContext::isSwapChainStuffed() {
@@ -406,13 +411,26 @@
// If the previous frame was dropped we don't need to hold onto it, so
// just keep using the previous frame's structure instead
- if (wasSkipped(mCurrentFrameInfo)) {
+ if (const auto reason = wasSkipped(mCurrentFrameInfo)) {
// Use the oldest skipped frame in case we skip more than a single frame
if (!mSkippedFrameInfo) {
- mSkippedFrameInfo.emplace();
- mSkippedFrameInfo->vsyncId =
- mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
- mSkippedFrameInfo->startTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+ switch (*reason) {
+ case SkippedFrameReason::AlreadyDrawn:
+ case SkippedFrameReason::NoBuffer:
+ case SkippedFrameReason::NoOutputTarget:
+ mSkippedFrameInfo.emplace();
+ mSkippedFrameInfo->vsyncId =
+ mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+ mSkippedFrameInfo->startTime =
+ mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+ break;
+ case SkippedFrameReason::DrawingOff:
+ case SkippedFrameReason::ContextIsStopped:
+ case SkippedFrameReason::NothingToDraw:
+ // Do not report those as skipped frames as there was no frame expected to be
+ // drawn
+ break;
+ }
}
} else {
mCurrentFrameInfo = mJankTracker.startFrame();
@@ -426,7 +444,7 @@
info.damageAccumulator = &mDamageAccumulator;
info.layerUpdateQueue = &mLayerUpdateQueue;
info.damageGenerationId = mDamageId++;
- info.out.canDrawThisFrame = true;
+ info.out.skippedFrameReason = std::nullopt;
mAnimationContext->startFrame(info.mode);
for (const sp<RenderNode>& node : mRenderNodes) {
@@ -446,8 +464,8 @@
mIsDirty = true;
if (CC_UNLIKELY(!hasOutputTarget())) {
- mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
- info.out.canDrawThisFrame = false;
+ info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget;
+ mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
return;
}
@@ -462,23 +480,23 @@
if (vsyncDelta < 2_ms) {
// Already drew for this vsync pulse, UI draw request missed
// the deadline for RT animations
- info.out.canDrawThisFrame = false;
+ info.out.skippedFrameReason = SkippedFrameReason::AlreadyDrawn;
}
} else {
- info.out.canDrawThisFrame = true;
+ info.out.skippedFrameReason = std::nullopt;
}
// TODO: Do we need to abort out if the backdrop is added but not ready? Should that even
// be an allowable combination?
if (mRenderNodes.size() > 2 && !mRenderNodes[1]->isRenderable()) {
- info.out.canDrawThisFrame = false;
+ info.out.skippedFrameReason = SkippedFrameReason::NothingToDraw;
}
- if (info.out.canDrawThisFrame) {
+ if (!info.out.skippedFrameReason) {
int err = mNativeSurface->reserveNext();
if (err != OK) {
- mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
- info.out.canDrawThisFrame = false;
+ info.out.skippedFrameReason = SkippedFrameReason::NoBuffer;
+ mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
ALOGW("reserveNext failed, error = %d (%s)", err, strerror(-err));
if (err != TIMED_OUT) {
// A timed out surface can still recover, but assume others are permanently dead.
@@ -487,11 +505,11 @@
}
}
} else {
- mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
+ mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
}
bool postedFrameCallback = false;
- if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
+ if (info.out.hasAnimations || info.out.skippedFrameReason) {
if (CC_UNLIKELY(!Properties::enableRTAnimations)) {
info.out.requiresUiRedraw = true;
}
@@ -544,7 +562,11 @@
void CanvasContext::draw(bool solelyTextureViewUpdates) {
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
- LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
+ if (grContext->isDeviceLost()) {
+ LOG_ALWAYS_FATAL("Lost GPU device unexpectedly");
+ return;
+ }
+ LOG_ALWAYS_FATAL("GrContext is abandoned at start of CanvasContext::draw");
return;
}
}
@@ -557,9 +579,20 @@
mSyncDelayDuration = 0;
mIdleDuration = 0;
- if (!Properties::isDrawingEnabled() ||
- (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw())) {
- mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
+ const auto skippedFrameReason = [&]() -> std::optional<SkippedFrameReason> {
+ if (!Properties::isDrawingEnabled()) {
+ return SkippedFrameReason::DrawingOff;
+ }
+
+ if (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw()) {
+ return SkippedFrameReason::NothingToDraw;
+ }
+
+ return std::nullopt;
+ }();
+ if (skippedFrameReason) {
+ mCurrentFrameInfo->setSkippedFrameReason(*skippedFrameReason);
+
if (auto grContext = getGrContext()) {
// Submit to ensure that any texture uploads complete and Skia can
// free its staging buffers.
@@ -592,10 +625,9 @@
{
// FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
// or it can lead to memory corruption.
- drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
- &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
- mLightInfo, mRenderNodes, &(profiler()), mBufferParams,
- profilerLock());
+ drawResult = mRenderPipeline->draw(
+ frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds,
+ mOpaque, mLightInfo, mRenderNodes, &(profiler()), mBufferParams, profilerLock());
}
uint64_t frameCompleteNr = getFrameNumber();
@@ -627,8 +659,8 @@
bool didDraw = false;
int error = OK;
- bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult.success, windowDirty,
- mCurrentFrameInfo, &requireSwap);
+ bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult, windowDirty, mCurrentFrameInfo,
+ &requireSwap);
mCurrentFrameInfo->set(FrameInfoIndex::CommandSubmissionCompleted) = std::max(
drawResult.commandSubmissionTime, mCurrentFrameInfo->get(FrameInfoIndex::SwapBuffers));
@@ -735,7 +767,7 @@
int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
- mHintSessionWrapper.updateTargetWorkDuration(frameDeadline - intendedVsync);
+ mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync);
if (didDraw) {
int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
@@ -743,7 +775,7 @@
int64_t actualDuration = frameDuration -
(std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
dequeueBufferDuration - idleDuration;
- mHintSessionWrapper.reportActualWorkDuration(actualDuration);
+ mHintSessionWrapper->reportActualWorkDuration(actualDuration);
}
mLastDequeueBufferDuration = dequeueBufferDuration;
@@ -886,10 +918,10 @@
}
void CanvasContext::prepareAndDraw(RenderNode* node) {
- ATRACE_CALL();
+ int64_t vsyncId = mRenderThread.timeLord().lastVsyncId();
+ ATRACE_FORMAT("%s %" PRId64, __func__, vsyncId);
nsecs_t vsync = mRenderThread.timeLord().computeFrameTimeNanos();
- int64_t vsyncId = mRenderThread.timeLord().lastVsyncId();
int64_t frameDeadline = mRenderThread.timeLord().lastFrameDeadline();
int64_t frameInterval = mRenderThread.timeLord().frameIntervalNanos();
int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
@@ -899,7 +931,7 @@
TreeInfo info(TreeInfo::MODE_RT_ONLY, *this);
prepareTree(info, frameInfo, systemTime(SYSTEM_TIME_MONOTONIC), node);
- if (info.out.canDrawThisFrame) {
+ if (!info.out.skippedFrameReason) {
draw(info.out.solelyTextureViewUpdates);
} else {
// wait on fences so tasks don't overlap next frame
@@ -961,6 +993,10 @@
}
}
+void CanvasContext::onContextDestroyed() {
+ destroyHardwareResources();
+}
+
DeferredLayerUpdater* CanvasContext::createTextureLayer() {
return mRenderPipeline->createTextureLayer();
}
@@ -1078,11 +1114,11 @@
}
void CanvasContext::sendLoadResetHint() {
- mHintSessionWrapper.sendLoadResetHint();
+ mHintSessionWrapper->sendLoadResetHint();
}
void CanvasContext::sendLoadIncreaseHint() {
- mHintSessionWrapper.sendLoadIncreaseHint();
+ mHintSessionWrapper->sendLoadIncreaseHint();
}
void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
@@ -1090,7 +1126,7 @@
}
void CanvasContext::startHintSession() {
- mHintSessionWrapper.init();
+ mHintSessionWrapper->init();
}
bool CanvasContext::shouldDither() {
@@ -1099,6 +1135,12 @@
return self->mColorMode != ColorMode::Default;
}
+void CanvasContext::visitAllRenderNodes(std::function<void(const RenderNode&)> func) const {
+ for (auto node : mRenderNodes) {
+ node->visit(func);
+ }
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 3978fbc..e2e3fa3 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -43,8 +43,10 @@
#include "Lighting.h"
#include "ReliableSurface.h"
#include "RenderNode.h"
+#include "renderstate/RenderState.h"
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
+#include "utils/ForceDark.h"
#include "utils/RingBuffer.h"
namespace android {
@@ -64,7 +66,7 @@
// This per-renderer class manages the bridge between the global EGL context
// and the render surface.
// TODO: Rename to Renderer or some other per-window, top-level manager
-class CanvasContext : public IFrameCallback {
+class CanvasContext : public IFrameCallback, public IGpuContextCallback {
public:
static CanvasContext* create(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, pid_t uiThreadId,
@@ -154,6 +156,7 @@
void markLayerInUse(RenderNode* node);
void destroyHardwareResources();
+ void onContextDestroyed() override;
DeferredLayerUpdater* createTextureLayer();
@@ -193,11 +196,9 @@
mRenderPipeline->setPictureCapturedCallback(callback);
}
- void setForceDark(bool enable) { mUseForceDark = enable; }
+ void setForceDark(ForceDarkType type) { mForceDarkType = type; }
- bool useForceDark() {
- return mUseForceDark;
- }
+ ForceDarkType getForceDarkType() { return mForceDarkType; }
SkISize getNextFrameSize() const;
@@ -237,6 +238,8 @@
static bool shouldDither();
+ void visitAllRenderNodes(std::function<void(const RenderNode&)>) const;
+
private:
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline,
@@ -318,7 +321,7 @@
nsecs_t mLastDropVsync = 0;
bool mOpaque;
- bool mUseForceDark = false;
+ ForceDarkType mForceDarkType = ForceDarkType::NONE;
LightInfo mLightInfo;
LightGeometry mLightGeometry = {{0, 0, 0}, 0};
@@ -340,8 +343,7 @@
std::string mName;
JankTracker mJankTracker;
FrameInfoVisualizer mProfiler;
- std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter
- GUARDED_BY(mFrameInfoMutex);
+ std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter GUARDED_BY(mFrameInfoMutex);
std::mutex mFrameInfoMutex;
std::set<RenderNode*> mPrefetchedLayers;
@@ -360,7 +362,7 @@
std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
std::function<void()> mPrepareSurfaceControlForWebviewCallback;
- HintSessionWrapper mHintSessionWrapper;
+ std::shared_ptr<HintSessionWrapper> mHintSessionWrapper;
nsecs_t mLastDequeueBufferDuration = 0;
nsecs_t mSyncDelayDuration = 0;
nsecs_t mIdleDuration = 0;
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 53b43ba..1b333bf 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -104,7 +104,7 @@
info.forceDrawFrame = mForceDrawFrame;
mForceDrawFrame = false;
canUnblockUiThread = syncFrameState(info);
- canDrawThisFrame = info.out.canDrawThisFrame;
+ canDrawThisFrame = !info.out.skippedFrameReason.has_value();
solelyTextureViewUpdates = info.out.solelyTextureViewUpdates;
if (mFrameCommitCallback) {
@@ -192,11 +192,12 @@
if (CC_UNLIKELY(!hasTarget || !canDraw)) {
if (!hasTarget) {
mSyncResult |= SyncResult::LostSurfaceRewardIfFound;
+ info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget;
} else {
// If we have a surface but can't draw we must be stopped
mSyncResult |= SyncResult::ContextIsStopped;
+ info.out.skippedFrameReason = SkippedFrameReason::ContextIsStopped;
}
- info.out.canDrawThisFrame = false;
}
if (info.out.hasAnimations) {
@@ -204,7 +205,7 @@
mSyncResult |= SyncResult::UIRedrawRequired;
}
}
- if (!info.out.canDrawThisFrame) {
+ if (info.out.skippedFrameReason) {
mSyncResult |= SyncResult::FrameDropped;
}
// If prepareTextures is false, we ran out of texture cache space
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index b34da51..2362331 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -24,6 +24,7 @@
#include <vector>
#include "../Properties.h"
+#include "RenderThread.h"
#include "thread/CommonPool.h"
using namespace std::chrono_literals;
@@ -62,24 +63,26 @@
}
void HintSessionWrapper::destroy() {
- if (mHintSessionFuture.valid()) {
- mHintSession = mHintSessionFuture.get();
+ if (mHintSessionFuture.has_value()) {
+ mHintSession = mHintSessionFuture->get();
+ mHintSessionFuture = std::nullopt;
}
if (mHintSession) {
mBinding->closeSession(mHintSession);
mSessionValid = true;
mHintSession = nullptr;
}
+ mResetsSinceLastReport = 0;
}
bool HintSessionWrapper::init() {
if (mHintSession != nullptr) return true;
-
// If we're waiting for the session
- if (mHintSessionFuture.valid()) {
+ if (mHintSessionFuture.has_value()) {
// If the session is here
- if (mHintSessionFuture.wait_for(0s) == std::future_status::ready) {
- mHintSession = mHintSessionFuture.get();
+ if (mHintSessionFuture->wait_for(0s) == std::future_status::ready) {
+ mHintSession = mHintSessionFuture->get();
+ mHintSessionFuture = std::nullopt;
if (mHintSession != nullptr) {
mSessionValid = true;
return true;
@@ -107,12 +110,13 @@
tids.push_back(mUiThreadId);
tids.push_back(mRenderThreadId);
- // Use a placeholder target value to initialize,
- // this will always be replaced elsewhere before it gets used
- int64_t defaultTargetDurationNanos = 16666667;
+ // Use the cached target value if there is one, otherwise use a default. This is to ensure
+ // the cached target and target in PowerHAL are consistent, and that it updates correctly
+ // whenever there is a change.
+ int64_t targetDurationNanos =
+ mLastTargetWorkDuration == 0 ? kDefaultTargetDuration : mLastTargetWorkDuration;
mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] {
- return mBinding->createSession(manager, tids.data(), tids.size(),
- defaultTargetDurationNanos);
+ return mBinding->createSession(manager, tids.data(), tids.size(), targetDurationNanos);
});
return false;
}
@@ -136,6 +140,7 @@
actualDurationNanos < kSanityCheckUpperBound) {
mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos);
}
+ mLastFrameNotification = systemTime();
}
void HintSessionWrapper::sendLoadResetHint() {
@@ -155,6 +160,27 @@
mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP));
}
+bool HintSessionWrapper::alive() {
+ return mHintSession != nullptr;
+}
+
+nsecs_t HintSessionWrapper::getLastUpdate() {
+ return mLastFrameNotification;
+}
+
+// Requires passing in its shared_ptr since it shouldn't own a shared_ptr to itself
+void HintSessionWrapper::delayedDestroy(RenderThread& rt, nsecs_t delay,
+ std::shared_ptr<HintSessionWrapper> wrapperPtr) {
+ nsecs_t lastUpdate = wrapperPtr->getLastUpdate();
+ rt.queue().postDelayed(delay, [lastUpdate = lastUpdate, wrapper = wrapperPtr]() mutable {
+ if (wrapper->getLastUpdate() == lastUpdate) {
+ wrapper->destroy();
+ }
+ // Ensure the shared_ptr is killed at the end of the method
+ wrapper = nullptr;
+ });
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index f8b876e..41891cd 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -19,6 +19,7 @@
#include <android/performance_hint.h>
#include <future>
+#include <optional>
#include "utils/TimeUtils.h"
@@ -27,6 +28,8 @@
namespace renderthread {
+class RenderThread;
+
class HintSessionWrapper {
public:
friend class HintSessionWrapperTests;
@@ -40,10 +43,15 @@
void sendLoadIncreaseHint();
bool init();
void destroy();
+ bool alive();
+ nsecs_t getLastUpdate();
+ void delayedDestroy(renderthread::RenderThread& rt, nsecs_t delay,
+ std::shared_ptr<HintSessionWrapper> wrapperPtr);
private:
APerformanceHintSession* mHintSession = nullptr;
- std::future<APerformanceHintSession*> mHintSessionFuture;
+ // This needs to work concurrently for testing
+ std::optional<std::shared_future<APerformanceHintSession*>> mHintSessionFuture;
int mResetsSinceLastReport = 0;
nsecs_t mLastFrameNotification = 0;
@@ -57,6 +65,7 @@
static constexpr nsecs_t kResetHintTimeout = 100_ms;
static constexpr int64_t kSanityCheckLowerBound = 100_us;
static constexpr int64_t kSanityCheckUpperBound = 10_s;
+ static constexpr int64_t kDefaultTargetDuration = 16666667;
// Allows easier stub when testing
class HintSessionBinding {
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 023c29a..b8c3a4d 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -61,6 +61,7 @@
// submission occurred. -1 if this time is unknown.
static constexpr nsecs_t kUnknownTime = -1;
nsecs_t commandSubmissionTime = kUnknownTime;
+ android::base::unique_fd presentFence;
};
virtual DrawResult draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
@@ -69,8 +70,9 @@
FrameInfoVisualizer* profiler,
const HardwareBufferRenderParams& bufferParams,
std::mutex& profilerLock) = 0;
- virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
- FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
+ virtual bool swapBuffers(const Frame& frame, IRenderPipeline::DrawResult&,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) = 0;
virtual DeferredLayerUpdater* createTextureLayer() = 0;
[[nodiscard]] virtual android::base::unique_fd flush() = 0;
virtual void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) = 0;
diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp
index 6df34be..64d38b9 100644
--- a/libs/hwui/renderthread/ReliableSurface.cpp
+++ b/libs/hwui/renderthread/ReliableSurface.cpp
@@ -150,11 +150,11 @@
}
AHardwareBuffer_Desc desc = AHardwareBuffer_Desc{
- .usage = mUsage,
- .format = mFormat,
.width = 1,
.height = 1,
.layers = 1,
+ .format = mFormat,
+ .usage = mUsage,
.rfu0 = 0,
.rfu1 = 0,
};
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 224c878..eab3605 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -16,7 +16,13 @@
#include "RenderProxy.h"
+#include <SkBitmap.h>
+#include <SkImage.h>
+#include <SkPicture.h>
#include <gui/TraceUtils.h>
+#include <pthread.h>
+#include <ui/GraphicBufferAllocator.h>
+
#include "DeferredLayerUpdater.h"
#include "DisplayList.h"
#include "Properties.h"
@@ -29,12 +35,6 @@
#include "utils/Macros.h"
#include "utils/TimeUtils.h"
-#include <SkBitmap.h>
-#include <SkImage.h>
-#include <SkPicture.h>
-
-#include <pthread.h>
-
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -123,7 +123,7 @@
}
void RenderProxy::allocateBuffers() {
- mRenderThread.queue().post([=]() { mContext->allocateBuffers(); });
+ mRenderThread.queue().post([this]() { mContext->allocateBuffers(); });
}
bool RenderProxy::pause() {
@@ -136,15 +136,16 @@
void RenderProxy::setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
mRenderThread.queue().post(
- [=]() { mContext->setLightAlpha(ambientShadowAlpha, spotShadowAlpha); });
+ [=, this]() { mContext->setLightAlpha(ambientShadowAlpha, spotShadowAlpha); });
}
void RenderProxy::setLightGeometry(const Vector3& lightCenter, float lightRadius) {
- mRenderThread.queue().post([=]() { mContext->setLightGeometry(lightCenter, lightRadius); });
+ mRenderThread.queue().post(
+ [=, this]() { mContext->setLightGeometry(lightCenter, lightRadius); });
}
void RenderProxy::setOpaque(bool opaque) {
- mRenderThread.queue().post([=]() { mContext->setOpaque(opaque); });
+ mRenderThread.queue().post([=, this]() { mContext->setOpaque(opaque); });
}
float RenderProxy::setColorMode(ColorMode mode) {
@@ -152,9 +153,9 @@
// an async call since we already know the return value
if (mode == ColorMode::Hdr || mode == ColorMode::Hdr10) {
return mRenderThread.queue().runSync(
- [=]() -> float { return mContext->setColorMode(mode); });
+ [=, this]() -> float { return mContext->setColorMode(mode); });
} else {
- mRenderThread.queue().post([=]() { mContext->setColorMode(mode); });
+ mRenderThread.queue().post([=, this]() { mContext->setColorMode(mode); });
return 1.f;
}
}
@@ -179,7 +180,7 @@
// destroyCanvasAndSurface() needs a fence as when it returns the
// underlying BufferQueue is going to be released from under
// the render thread.
- mRenderThread.queue().runSync([=]() { mContext->destroy(); });
+ mRenderThread.queue().runSync([this]() { mContext->destroy(); });
}
void RenderProxy::destroyFunctor(int functor) {
@@ -300,7 +301,7 @@
}
void RenderProxy::resetProfileInfo() {
- mRenderThread.queue().runSync([=]() {
+ mRenderThread.queue().runSync([this]() {
std::lock_guard lock(mRenderThread.getJankDataMutex());
mContext->resetFrameStats();
});
@@ -323,6 +324,11 @@
}
});
}
+ if (!Properties::isolatedProcess) {
+ std::string grallocInfo;
+ GraphicBufferAllocator::getInstance().dump(grallocInfo);
+ dprintf(fd, "%s\n", grallocInfo.c_str());
+ }
}
void RenderProxy::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
@@ -350,15 +356,15 @@
}
void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
- mRenderThread.queue().post([=]() { mContext->addRenderNode(node, placeFront); });
+ mRenderThread.queue().post([=, this]() { mContext->addRenderNode(node, placeFront); });
}
void RenderProxy::removeRenderNode(RenderNode* node) {
- mRenderThread.queue().post([=]() { mContext->removeRenderNode(node); });
+ mRenderThread.queue().post([=, this]() { mContext->removeRenderNode(node); });
}
void RenderProxy::drawRenderNode(RenderNode* node) {
- mRenderThread.queue().runSync([=]() { mContext->prepareAndDraw(node); });
+ mRenderThread.queue().runSync([=, this]() { mContext->prepareAndDraw(node); });
}
void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) {
@@ -412,8 +418,8 @@
});
}
-void RenderProxy::setForceDark(bool enable) {
- mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
+void RenderProxy::setForceDark(ForceDarkType type) {
+ mRenderThread.queue().post([this, type]() { mContext->setForceDark(type); });
}
void RenderProxy::copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request) {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 47c1b0c..f2d8e94 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -31,6 +31,7 @@
#include "DrawFrameTask.h"
#include "SwapBehavior.h"
#include "hwui/Bitmap.h"
+#include "utils/ForceDark.h"
class SkBitmap;
class SkPicture;
@@ -142,7 +143,7 @@
void addFrameMetricsObserver(FrameMetricsObserver* observer);
void removeFrameMetricsObserver(FrameMetricsObserver* observer);
- void setForceDark(bool enable);
+ void setForceDark(ForceDarkType type);
static void copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request);
static void prepareToDraw(Bitmap& bitmap);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 0dea941..623ee4e 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -17,6 +17,7 @@
#include "RenderThread.h"
#include <GrContextOptions.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <android-base/properties.h>
#include <dlfcn.h>
#include <gl/GrGLInterface.h>
@@ -149,7 +150,7 @@
ATRACE_FORMAT("queue mFrameCallbackTask to run after %.2fms",
toFloatMillis(runAt - SteadyClock::now()).count());
queue().postAt(toNsecs_t(runAt.time_since_epoch()).count(),
- [=]() { dispatchFrameCallbacks(); });
+ [this]() { dispatchFrameCallbacks(); });
}
}
@@ -286,7 +287,7 @@
auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
auto size = glesVersion ? strlen(glesVersion) : -1;
cacheManager().configureContext(&options, glesVersion, size);
- sk_sp<GrDirectContext> grContext(GrDirectContext::MakeGL(std::move(glInterface), options));
+ sk_sp<GrDirectContext> grContext(GrDirectContexts::MakeGL(std::move(glInterface), options));
LOG_ALWAYS_FATAL_IF(!grContext.get());
setGrContext(grContext);
}
@@ -357,7 +358,15 @@
String8 cachesOutput;
mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState);
- dprintf(fd, "\nPipeline=%s\n%s\n", pipelineToString(), cachesOutput.c_str());
+ dprintf(fd, "\nPipeline=%s\n%s", pipelineToString(), cachesOutput.c_str());
+ for (auto&& context : mCacheManager->mCanvasContexts) {
+ context->visitAllRenderNodes([&](const RenderNode& node) {
+ if (node.isTextureView()) {
+ dprintf(fd, "TextureView: %dx%d\n", node.getWidth(), node.getHeight());
+ }
+ });
+ }
+ dprintf(fd, "\n");
}
void RenderThread::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 46698a6..d55d28d 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -24,6 +24,9 @@
#include <GrTypes.h>
#include <android/sync.h>
#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
#include <ui/FatVector.h>
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
@@ -33,9 +36,6 @@
#include "pipeline/skia/ShaderCache.h"
#include "renderstate/RenderState.h"
-#undef LOG_TAG
-#define LOG_TAG "VulkanManager"
-
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -386,25 +386,23 @@
}
void VulkanManager::initialize() {
- std::lock_guard _lock{mInitializeLock};
+ std::call_once(mInitFlag, [&] {
+ GET_PROC(EnumerateInstanceVersion);
+ uint32_t instanceVersion;
+ LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion));
+ LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0));
- if (mDevice != VK_NULL_HANDLE) {
- return;
- }
+ this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
- GET_PROC(EnumerateInstanceVersion);
- uint32_t instanceVersion;
- LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion));
- LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0));
+ mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
+ mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
- this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
+ if (Properties::enablePartialUpdates && Properties::useBufferAge) {
+ mSwapBehavior = SwapBehavior::BufferAge;
+ }
- mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
- mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
-
- if (Properties::enablePartialUpdates && Properties::useBufferAge) {
- mSwapBehavior = SwapBehavior::BufferAge;
- }
+ mInitialized = true;
+ });
}
static void onGrContextReleased(void* context) {
@@ -438,7 +436,7 @@
options.fContextDeleteContext = this;
options.fContextDeleteProc = onGrContextReleased;
- return GrDirectContext::MakeVulkan(backendContext, options);
+ return GrDirectContexts::MakeVulkan(backendContext, options);
}
VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const {
@@ -518,7 +516,7 @@
// The following flush blocks the GPU immediately instead of waiting for
// other drawing ops. It seems dequeue_fence is not respected otherwise.
// TODO: remove the flush after finding why backendSemaphore is not working.
- bufferInfo->skSurface->flushAndSubmit();
+ skgpu::ganesh::FlushAndSubmit(bufferInfo->skSurface.get());
}
}
}
@@ -529,128 +527,121 @@
return Frame(surface->logicalWidth(), surface->logicalHeight(), bufferAge);
}
-struct DestroySemaphoreInfo {
+class SharedSemaphoreInfo : public LightRefBase<SharedSemaphoreInfo> {
PFN_vkDestroySemaphore mDestroyFunction;
VkDevice mDevice;
VkSemaphore mSemaphore;
- // We need to make sure we don't delete the VkSemaphore until it is done being used by both Skia
- // (including by the GPU) and inside the VulkanManager. So we always start with two refs, one
- // owned by Skia and one owned by the VulkanManager. The refs are decremented each time
- // destroy_semaphore is called with this object. Skia will call destroy_semaphore once it is
- // done with the semaphore and the GPU has finished work on the semaphore. The VulkanManager
- // calls destroy_semaphore after sending the semaphore to Skia and exporting it if need be.
- int mRefs = 2;
+ GrBackendSemaphore mGrBackendSemaphore;
- DestroySemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
- VkSemaphore semaphore)
- : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {}
+ SharedSemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
+ VkSemaphore semaphore)
+ : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {
+ mGrBackendSemaphore.initVulkan(semaphore);
+ }
+
+ ~SharedSemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); }
+
+ friend class LightRefBase<SharedSemaphoreInfo>;
+ friend class sp<SharedSemaphoreInfo>;
+
+public:
+ VkSemaphore semaphore() const { return mSemaphore; }
+
+ GrBackendSemaphore* grBackendSemaphore() { return &mGrBackendSemaphore; }
};
static void destroy_semaphore(void* context) {
- DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(context);
- --info->mRefs;
- if (!info->mRefs) {
- info->mDestroyFunction(info->mDevice, info->mSemaphore, nullptr);
- delete info;
- }
+ SharedSemaphoreInfo* info = reinterpret_cast<SharedSemaphoreInfo*>(context);
+ info->decStrong(0);
}
-nsecs_t VulkanManager::finishFrame(SkSurface* surface) {
+VulkanManager::VkDrawResult VulkanManager::finishFrame(SkSurface* surface) {
ATRACE_NAME("Vulkan finish frame");
- ALOGE_IF(mSwapSemaphore != VK_NULL_HANDLE || mDestroySemaphoreContext != nullptr,
- "finishFrame already has an outstanding semaphore");
- VkExportSemaphoreCreateInfo exportInfo;
- exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
- exportInfo.pNext = nullptr;
- exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
- VkSemaphoreCreateInfo semaphoreInfo;
- semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
- semaphoreInfo.pNext = &exportInfo;
- semaphoreInfo.flags = 0;
- VkSemaphore semaphore;
- VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
- ALOGE_IF(VK_SUCCESS != err, "VulkanManager::makeSwapSemaphore(): Failed to create semaphore");
-
- GrBackendSemaphore backendSemaphore;
- backendSemaphore.initVulkan(semaphore);
-
+ sp<SharedSemaphoreInfo> sharedSemaphore;
GrFlushInfo flushInfo;
- if (err == VK_SUCCESS) {
- mDestroySemaphoreContext = new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
- flushInfo.fNumSemaphores = 1;
- flushInfo.fSignalSemaphores = &backendSemaphore;
- flushInfo.fFinishedProc = destroy_semaphore;
- flushInfo.fFinishedContext = mDestroySemaphoreContext;
- } else {
- semaphore = VK_NULL_HANDLE;
- }
- GrSemaphoresSubmitted submitted =
- surface->flush(SkSurface::BackendSurfaceAccess::kPresent, flushInfo);
- GrDirectContext* context = GrAsDirectContext(surface->recordingContext());
- ALOGE_IF(!context, "Surface is not backed by gpu");
- context->submit();
- const nsecs_t submissionTime = systemTime();
- if (semaphore != VK_NULL_HANDLE) {
- if (submitted == GrSemaphoresSubmitted::kYes) {
- mSwapSemaphore = semaphore;
- if (mFrameBoundaryANDROID) {
- // retrieve VkImage used as render target
- VkImage image = VK_NULL_HANDLE;
- GrBackendRenderTarget backendRenderTarget =
- surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess);
- if (backendRenderTarget.isValid()) {
- GrVkImageInfo info;
- if (backendRenderTarget.getVkImageInfo(&info)) {
- image = info.fImage;
- } else {
- ALOGE("Frame boundary: backend is not vulkan");
- }
- } else {
- ALOGE("Frame boundary: invalid backend render target");
- }
- // frameBoundaryANDROID needs to know about mSwapSemaphore, but
- // it won't wait on it.
- mFrameBoundaryANDROID(mDevice, mSwapSemaphore, image);
- }
- } else {
- destroy_semaphore(mDestroySemaphoreContext);
- mDestroySemaphoreContext = nullptr;
+
+ {
+ VkExportSemaphoreCreateInfo exportInfo;
+ exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
+ exportInfo.pNext = nullptr;
+ exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+ VkSemaphoreCreateInfo semaphoreInfo;
+ semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+ semaphoreInfo.pNext = &exportInfo;
+ semaphoreInfo.flags = 0;
+ VkSemaphore semaphore;
+ VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
+ ALOGE_IF(VK_SUCCESS != err,
+ "VulkanManager::makeSwapSemaphore(): Failed to create semaphore");
+
+ if (err == VK_SUCCESS) {
+ sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore);
+ flushInfo.fNumSemaphores = 1;
+ flushInfo.fSignalSemaphores = sharedSemaphore->grBackendSemaphore();
+ flushInfo.fFinishedProc = destroy_semaphore;
+ sharedSemaphore->incStrong(0);
+ flushInfo.fFinishedContext = sharedSemaphore.get();
}
}
+
+ GrDirectContext* context = GrAsDirectContext(surface->recordingContext());
+ ALOGE_IF(!context, "Surface is not backed by gpu");
+ GrSemaphoresSubmitted submitted = context->flush(
+ surface, SkSurfaces::BackendSurfaceAccess::kPresent, flushInfo);
+ context->submit();
+ VkDrawResult drawResult{
+ .submissionTime = systemTime(),
+ };
+ if (sharedSemaphore) {
+ if (submitted == GrSemaphoresSubmitted::kYes && mFrameBoundaryANDROID) {
+ // retrieve VkImage used as render target
+ VkImage image = VK_NULL_HANDLE;
+ GrBackendRenderTarget backendRenderTarget = SkSurfaces::GetBackendRenderTarget(
+ surface, SkSurfaces::BackendHandleAccess::kFlushRead);
+ if (backendRenderTarget.isValid()) {
+ GrVkImageInfo info;
+ if (GrBackendRenderTargets::GetVkImageInfo(backendRenderTarget, &info)) {
+ image = info.fImage;
+ } else {
+ ALOGE("Frame boundary: backend is not vulkan");
+ }
+ } else {
+ ALOGE("Frame boundary: invalid backend render target");
+ }
+ // frameBoundaryANDROID needs to know about mSwapSemaphore, but
+ // it won't wait on it.
+ mFrameBoundaryANDROID(mDevice, sharedSemaphore->semaphore(), image);
+ }
+ VkSemaphoreGetFdInfoKHR getFdInfo;
+ getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
+ getFdInfo.pNext = nullptr;
+ getFdInfo.semaphore = sharedSemaphore->semaphore();
+ getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+ int fenceFd = -1;
+ VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
+ ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
+ drawResult.presentFence.reset(fenceFd);
+ } else {
+ ALOGE("VulkanManager::finishFrame(): Semaphore submission failed");
+ mQueueWaitIdle(mGraphicsQueue);
+ }
+
skiapipeline::ShaderCache::get().onVkFrameFlushed(context);
- return submissionTime;
+ return drawResult;
}
-void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) {
+void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect,
+ android::base::unique_fd&& presentFence) {
if (CC_UNLIKELY(Properties::waitForGpuCompletion)) {
ATRACE_NAME("Finishing GPU work");
mDeviceWaitIdle(mDevice);
}
- int fenceFd = -1;
- if (mSwapSemaphore != VK_NULL_HANDLE) {
- VkSemaphoreGetFdInfoKHR getFdInfo;
- getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
- getFdInfo.pNext = nullptr;
- getFdInfo.semaphore = mSwapSemaphore;
- getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
- VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
- ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
- } else {
- ALOGE("VulkanManager::swapBuffers(): Semaphore submission failed");
- mQueueWaitIdle(mGraphicsQueue);
- }
- if (mDestroySemaphoreContext) {
- destroy_semaphore(mDestroySemaphoreContext);
- }
-
- surface->presentCurrentBuffer(dirtyRect, fenceFd);
- mSwapSemaphore = VK_NULL_HANDLE;
- mDestroySemaphoreContext = nullptr;
+ surface->presentCurrentBuffer(dirtyRect, presentFence.release());
}
void VulkanManager::destroySurface(VulkanSurface* surface) {
@@ -751,25 +742,20 @@
return INVALID_OPERATION;
}
- GrBackendSemaphore backendSemaphore;
- backendSemaphore.initVulkan(semaphore);
+ auto sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore);
- DestroySemaphoreInfo* destroyInfo =
- new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
// Even if Skia fails to submit the semaphore, it will still call the destroy_semaphore callback
- // which will remove its ref to the semaphore. The VulkanManager must still release its ref,
- // when it is done with the semaphore.
GrFlushInfo flushInfo;
flushInfo.fNumSemaphores = 1;
- flushInfo.fSignalSemaphores = &backendSemaphore;
+ flushInfo.fSignalSemaphores = sharedSemaphore->grBackendSemaphore();
flushInfo.fFinishedProc = destroy_semaphore;
- flushInfo.fFinishedContext = destroyInfo;
+ sharedSemaphore->incStrong(0);
+ flushInfo.fFinishedContext = sharedSemaphore.get();
GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
grContext->submit();
if (submitted == GrSemaphoresSubmitted::kNo) {
ALOGE("VulkanManager::createReleaseFence: Failed to submit semaphore");
- destroy_semaphore(destroyInfo);
return INVALID_OPERATION;
}
@@ -782,7 +768,6 @@
int fenceFd = 0;
err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
- destroy_semaphore(destroyInfo);
if (VK_SUCCESS != err) {
ALOGE("VulkanManager::createReleaseFence: Failed to get semaphore Fd");
return INVALID_OPERATION;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 2be1ffd..b92ebb3 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -22,6 +22,7 @@
#endif
#include <GrContextOptions.h>
#include <SkSurface.h>
+#include <android-base/unique_fd.h>
#include <utils/StrongPointer.h>
#include <vk/GrVkBackendContext.h>
#include <vk/GrVkExtensions.h>
@@ -70,7 +71,7 @@
void initialize();
// Quick check to see if the VulkanManager has been initialized.
- bool hasVkContext() { return mDevice != VK_NULL_HANDLE; }
+ bool hasVkContext() { return mInitialized; }
// Create and destroy functions for wrapping an ANativeWindow in a VulkanSurface
VulkanSurface* createSurface(ANativeWindow* window,
@@ -82,10 +83,17 @@
void destroySurface(VulkanSurface* surface);
Frame dequeueNextBuffer(VulkanSurface* surface);
+
+ struct VkDrawResult {
+ // The estimated start time for intiating GPU work, -1 if unknown.
+ nsecs_t submissionTime;
+ android::base::unique_fd presentFence;
+ };
+
// Finishes the frame and submits work to the GPU
- // Returns the estimated start time for intiating GPU work, -1 otherwise.
- nsecs_t finishFrame(SkSurface* surface);
- void swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect);
+ VkDrawResult finishFrame(SkSurface* surface);
+ void swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect,
+ android::base::unique_fd&& presentFence);
// Inserts a wait on fence command into the Vulkan command buffer.
status_t fenceWait(int fence, GrDirectContext* grContext);
@@ -201,10 +209,8 @@
GrVkExtensions mExtensions;
uint32_t mDriverVersion = 0;
- VkSemaphore mSwapSemaphore = VK_NULL_HANDLE;
- void* mDestroySemaphoreContext = nullptr;
-
- std::mutex mInitializeLock;
+ std::once_flag mInitFlag;
+ std::atomic_bool mInitialized = false;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 3168cb0..20b743b 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -16,6 +16,7 @@
#include "VulkanSurface.h"
+#include <include/android/SkSurfaceAndroid.h>
#include <GrDirectContext.h>
#include <SkSurface.h>
#include <algorithm>
@@ -24,9 +25,6 @@
#include "VulkanManager.h"
#include "utils/Color.h"
-#undef LOG_TAG
-#define LOG_TAG "VulkanSurface"
-
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -470,12 +468,12 @@
surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(),
surfaceProps.pixelGeometry());
}
- bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer(
+ bufferInfo->skSurface = SkSurfaces::WrapAndroidHardwareBuffer(
mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()),
kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps,
/*from_window=*/true);
if (bufferInfo->skSurface.get() == nullptr) {
- ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
+ ALOGE("SkSurfaces::WrapAndroidHardwareBuffer failed");
mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
mNativeBuffers[idx].dequeue_fence.release());
mNativeBuffers[idx].dequeued = false;
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index a4890ed..ad963dd 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -19,6 +19,8 @@
#include "DeferredLayerUpdater.h"
#include "hwui/Paint.h"
+#include <hwui/MinikinSkia.h>
+#include <hwui/Typeface.h>
#include <minikin/Layout.h>
#include <pipeline/skia/SkiaOpenGLPipeline.h>
#include <pipeline/skia/SkiaVulkanPipeline.h>
@@ -179,5 +181,13 @@
return outlineInLocalCoord;
}
+SkFont TestUtils::defaultFont() {
+ const std::shared_ptr<minikin::MinikinFont>& minikinFont =
+ Typeface::resolveDefault(nullptr)->fFontCollection->getFamilyAt(0)->getFont(0)->baseTypeface();
+ SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(minikinFont.get())->GetSkTypeface();
+ LOG_ALWAYS_FATAL_IF(skTypeface == nullptr);
+ return SkFont(sk_ref_sp(skTypeface));
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 81ecfe5..0ede902 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -30,6 +30,7 @@
#include <SkBitmap.h>
#include <SkColor.h>
+#include <SkFont.h>
#include <SkImageInfo.h>
#include <SkRefCnt.h>
@@ -61,18 +62,10 @@
ADD_FAILURE() << "ClipState not a rect"; \
}
-#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \
- TEST(test_case_name, test_name##_##pipeline) { \
- RenderPipelineType oldType = Properties::getRenderPipelineType(); \
- Properties::overrideRenderPipelineType(RenderPipelineType::pipeline); \
- functionCall; \
- Properties::overrideRenderPipelineType(oldType); \
- };
-
-#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \
- INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, \
- TestUtils::runOnRenderThread( \
- test_case_name##_##test_name##_RenderThreadTest::doTheThing))
+#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name) \
+ TEST(test_case_name, test_name) { \
+ TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \
+ }
/**
* Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
@@ -83,21 +76,7 @@
public: \
static void doTheThing(renderthread::RenderThread& renderThread); \
}; \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
- /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
- /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \
- void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
- renderthread::RenderThread& renderThread)
-
-/**
- * Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes
- */
-#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \
- class test_case_name##_##test_name##_RenderThreadTest { \
- public: \
- static void doTheThing(renderthread::RenderThread& renderThread); \
- }; \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name); \
/* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
/* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \
void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
@@ -307,13 +286,21 @@
int destroyed = 0;
int removeOverlays = 0;
int glesDraw = 0;
+ int vkInitialize = 0;
+ int vkDraw = 0;
+ int vkPostDraw = 0;
};
static void expectOnRenderThread(const std::string_view& function = "unknown") {
EXPECT_EQ(gettid(), TestUtils::getRenderThreadTid()) << "Called on wrong thread: " << function;
}
- static WebViewFunctorCallbacks createMockFunctor(RenderMode mode) {
+ static int createMockFunctor() {
+ const auto renderMode = WebViewFunctor_queryPlatformRenderMode();
+ return WebViewFunctor_create(nullptr, createMockFunctorCallbacks(renderMode), renderMode);
+ }
+
+ static WebViewFunctorCallbacks createMockFunctorCallbacks(RenderMode mode) {
auto callbacks = WebViewFunctorCallbacks{
.onSync =
[](int functor, void* client_data, const WebViewSyncData& data) {
@@ -345,15 +332,30 @@
sMockFunctorCounts[functor].glesDraw++;
};
break;
- default:
- ADD_FAILURE();
- return WebViewFunctorCallbacks{};
+ case RenderMode::Vulkan:
+ callbacks.vk.initialize = [](int functor, void* data,
+ const VkFunctorInitParams& params) {
+ expectOnRenderThread("initialize");
+ sMockFunctorCounts[functor].vkInitialize++;
+ };
+ callbacks.vk.draw = [](int functor, void* data, const VkFunctorDrawParams& params,
+ const WebViewOverlayData& overlayParams) {
+ expectOnRenderThread("draw");
+ sMockFunctorCounts[functor].vkDraw++;
+ };
+ callbacks.vk.postDraw = [](int functor, void* data) {
+ expectOnRenderThread("postDraw");
+ sMockFunctorCounts[functor].vkPostDraw++;
+ };
+ break;
}
return callbacks;
}
static CallCounts& countsForFunctor(int functor) { return sMockFunctorCounts[functor]; }
+ static SkFont defaultFont();
+
private:
static std::unordered_map<int, CallCounts> sMockFunctorCounts;
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index 4a5d946..97d4c82 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -57,7 +57,7 @@
128 * 3;
paint.setColor(bgDark ? Color::White : Color::Grey_700);
- SkFont font;
+ SkFont font = TestUtils::defaultFont();
font.setSize(size / 2);
char charToShow = 'A' + (rand() % 26);
const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
index bb95490..159541c 100644
--- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
@@ -102,7 +102,7 @@
128 * 3;
paint.setColor(bgDark ? Color::White : Color::Grey_700);
- SkFont font;
+ SkFont font = TestUtils::defaultFont();
font.setSize(size / 2);
char charToShow = 'A' + (rand() % 26);
const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index f3f32eb..e227999 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -14,30 +14,32 @@
* limitations under the License.
*/
-#include "tests/common/LeakChecker.h"
-#include "tests/common/TestScene.h"
-
-#include "Properties.h"
-#include "hwui/Typeface.h"
-#include "HardwareBitmapUploader.h"
-#include "renderthread/RenderProxy.h"
-
+#include <android-base/parsebool.h>
#include <benchmark/benchmark.h>
+#include <errno.h>
+#include <fcntl.h>
#include <fnmatch.h>
#include <getopt.h>
#include <pthread.h>
#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
+
+#include <regex>
#include <string>
#include <unordered_map>
#include <vector>
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
+#include "HardwareBitmapUploader.h"
+#include "Properties.h"
+#include "hwui/Typeface.h"
+#include "renderthread/RenderProxy.h"
+#include "tests/common/LeakChecker.h"
+#include "tests/common/TestScene.h"
using namespace android;
+using namespace android::base;
using namespace android::uirenderer;
using namespace android::uirenderer::test;
@@ -69,6 +71,9 @@
--onscreen Render tests on device screen. By default tests
are offscreen rendered
--benchmark_format Set output format. Possible values are tabular, json, csv
+ --benchmark_list_tests Lists the tests that would run but does not run them
+ --benchmark_filter=<regex> Filters the test set to the given regex. If prefixed with `-` and test
+ that doesn't match the given regex is run
--renderer=TYPE Sets the render pipeline to use. May be skiagl or skiavk
--skip-leak-check Skips the memory leak check
--report-gpu-memory[=verbose] Dumps the GPU memory usage after each test run
@@ -140,6 +145,9 @@
if (!strcmp(format, "tabular")) {
gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
} else if (!strcmp(format, "json")) {
+ // We cannot print the leak check if outputing to JSON as that will break
+ // JSON parsers since it's not JSON-formatted
+ gRunLeakCheck = false;
gBenchmarkReporter.reset(new benchmark::JSONReporter());
} else {
fprintf(stderr, "Unknown format '%s'\n", format);
@@ -160,6 +168,24 @@
return true;
}
+static void addTestsThatMatchFilter(std::string spec) {
+ if (spec.empty() || spec == "all") {
+ spec = "."; // Regexp that matches all benchmarks
+ }
+ bool isNegativeFilter = false;
+ if (spec[0] == '-') {
+ spec.replace(0, 1, "");
+ isNegativeFilter = true;
+ }
+ std::regex re(spec, std::regex_constants::extended);
+ for (auto& iter : TestScene::testMap()) {
+ if ((isNegativeFilter && !std::regex_search(iter.first, re)) ||
+ (!isNegativeFilter && std::regex_search(iter.first, re))) {
+ gRunTests.push_back(iter.second);
+ }
+ }
+}
+
// For options that only exist in long-form. Anything in the
// 0-255 range is reserved for short options (which just use their ASCII value)
namespace LongOpts {
@@ -170,6 +196,8 @@
ReportFrametime,
CpuSet,
BenchmarkFormat,
+ BenchmarkListTests,
+ BenchmarkFilter,
Onscreen,
Offscreen,
Renderer,
@@ -179,14 +207,16 @@
}
static const struct option LONG_OPTIONS[] = {
- {"frames", required_argument, nullptr, 'f'},
- {"repeat", required_argument, nullptr, 'r'},
+ {"count", required_argument, nullptr, 'c'},
+ {"runs", required_argument, nullptr, 'r'},
{"help", no_argument, nullptr, 'h'},
{"list", no_argument, nullptr, LongOpts::List},
{"wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu},
{"report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime},
{"cpuset", required_argument, nullptr, LongOpts::CpuSet},
{"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
+ {"benchmark_list_tests", optional_argument, nullptr, LongOpts::BenchmarkListTests},
+ {"benchmark_filter", required_argument, nullptr, LongOpts::BenchmarkFilter},
{"onscreen", no_argument, nullptr, LongOpts::Onscreen},
{"offscreen", no_argument, nullptr, LongOpts::Offscreen},
{"renderer", required_argument, nullptr, LongOpts::Renderer},
@@ -197,8 +227,12 @@
static const char* SHORT_OPTIONS = "c:r:h";
void parseOptions(int argc, char* argv[]) {
+ benchmark::BenchmarkReporter::Context::executable_name = (argc > 0) ? argv[0] : "unknown";
+
int c;
bool error = false;
+ bool listTestsOnly = false;
+ bool testsAreFiltered = false;
opterr = 0;
while (true) {
@@ -272,6 +306,21 @@
}
break;
+ case LongOpts::BenchmarkListTests:
+ if (!optarg || ParseBool(optarg) == ParseBoolResult::kTrue) {
+ listTestsOnly = true;
+ }
+ break;
+
+ case LongOpts::BenchmarkFilter:
+ if (!optarg) {
+ error = true;
+ break;
+ }
+ addTestsThatMatchFilter(optarg);
+ testsAreFiltered = true;
+ break;
+
case LongOpts::Renderer:
if (!optarg) {
error = true;
@@ -346,11 +395,18 @@
}
}
} while (optind < argc);
- } else {
+ } else if (gRunTests.empty() && !testsAreFiltered) {
for (auto& iter : TestScene::testMap()) {
gRunTests.push_back(iter.second);
}
}
+
+ if (listTestsOnly) {
+ for (auto& iter : gRunTests) {
+ std::cout << iter.name << std::endl;
+ }
+ exit(EXIT_SUCCESS);
+ }
}
int main(int argc, char* argv[]) {
diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
index 138b3efd..b8b3f0a 100644
--- a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
+++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
@@ -46,7 +46,7 @@
EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
- // SkImage::MakeFromTexture should fail if given null GrDirectContext.
+ // SkImages::BorrowTextureFrom should fail if given null GrDirectContext.
textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, /*context = */ nullptr);
EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
index 2b90bda..89d00d3 100644
--- a/libs/hwui/tests/unit/CacheManagerTests.cpp
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -20,7 +20,8 @@
#include "renderthread/EglManager.h"
#include "tests/common/TestUtils.h"
-#include <SkImagePriv.h>
+#include <SkImageAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include "include/gpu/GpuTypes.h" // from Skia
using namespace android;
@@ -34,7 +35,7 @@
}
// TOOD(258700630): fix this test and re-enable
-RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
+RENDERTHREAD_TEST(CacheManager, DISABLED_trimMemory) {
int32_t width = DeviceInfo::get()->getWidth();
int32_t height = DeviceInfo::get()->getHeight();
GrDirectContext* grContext = renderThread.getGrContext();
@@ -46,8 +47,8 @@
while (getCacheUsage(grContext) <= renderThread.cacheManager().getBackgroundCacheSize()) {
SkImageInfo info = SkImageInfo::MakeA8(width, height);
- sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grContext, skgpu::Budgeted::kYes,
- info);
+ sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(grContext, skgpu::Budgeted::kYes,
+ info);
surface->getCanvas()->drawColor(SK_AlphaTRANSPARENT);
grContext->flushAndSubmit();
@@ -57,9 +58,8 @@
// create an image and pin it so that we have something with a unique key in the cache
sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::MakeA8(width, height));
- sk_sp<SkImage> image = bitmap->makeImage();
- ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext));
-
+ sk_sp<SkImage> image = bitmap->makeImage(); // calls skgpu::ganesh::PinAsTexture under the hood.
+ ASSERT_TRUE(skgpu::ganesh::PinAsTexture(grContext, image.get()));
// attempt to trim all memory while we still hold strong refs
renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
@@ -71,7 +71,7 @@
}
// unpin the image which should add a unique purgeable key to the cache
- SkImage_unpinAsTexture(image.get(), grContext);
+ skgpu::ganesh::UnpinTexture(grContext, image.get());
// verify that we have enough purgeable bytes
const size_t purgeableBytes = grContext->getResourceCachePurgeableBytes();
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 9e376e3..47a4105 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -19,6 +19,7 @@
#include "AnimationContext.h"
#include "IContextFactory.h"
#include "renderthread/CanvasContext.h"
+#include "renderthread/VulkanManager.h"
#include "tests/common/TestUtils.h"
using namespace android;
@@ -42,3 +43,38 @@
canvasContext->destroy();
}
+
+RENDERTHREAD_TEST(CanvasContext, buildLayerDoesntLeak) {
+ auto node = TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+ canvas.drawColor(0xFFFF0000, SkBlendMode::kSrc);
+ });
+ ASSERT_TRUE(node->isValid());
+ EXPECT_EQ(LayerType::None, node->stagingProperties().effectiveLayerType());
+ node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+
+ auto& cacheManager = renderThread.cacheManager();
+ EXPECT_TRUE(cacheManager.areAllContextsStopped());
+ ContextFactory contextFactory;
+ std::unique_ptr<CanvasContext> canvasContext(
+ CanvasContext::create(renderThread, false, node.get(), &contextFactory, 0, 0));
+ canvasContext->buildLayer(node.get());
+ EXPECT_TRUE(node->hasLayer());
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ auto instance = VulkanManager::peekInstance();
+ if (instance) {
+ EXPECT_TRUE(instance->hasVkContext());
+ } else {
+ ADD_FAILURE() << "VulkanManager wasn't initialized to buildLayer?";
+ }
+ }
+ renderThread.destroyRenderingContext();
+ EXPECT_FALSE(node->hasLayer()) << "Node still has a layer after rendering context destroyed";
+
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ auto instance = VulkanManager::peekInstance();
+ if (instance) {
+ ADD_FAILURE() << "VulkanManager still exists";
+ EXPECT_FALSE(instance->hasVkContext());
+ }
+ }
+}
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index 1f6edf3..18c5047 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -225,9 +225,9 @@
TEST(CanvasOp, simpleDrawRect) {
CanvasOpBuffer buffer;
EXPECT_EQ(buffer.size(), 0);
- buffer.push<Op::DrawRect> ({
- .paint = SkPaint{},
- .rect = SkRect::MakeEmpty()
+ buffer.push<Op::DrawRect>({
+ .rect = SkRect::MakeEmpty(),
+ .paint = SkPaint{},
});
CallCountingCanvas canvas;
@@ -242,9 +242,9 @@
EXPECT_EQ(buffer.size(), 0);
SkRegion region;
region.setRect(SkIRect::MakeWH(12, 50));
- buffer.push<Op::DrawRegion> ({
- .paint = SkPaint{},
- .region = region
+ buffer.push<Op::DrawRegion>({
+ .region = region,
+ .paint = SkPaint{},
});
CallCountingCanvas canvas;
@@ -264,9 +264,9 @@
clip.setRect(SkIRect::MakeWH(100, 100));
SkRegion region;
region.setPath(path, clip);
- buffer.push<Op::DrawRegion> ({
- .paint = SkPaint{},
- .region = region
+ buffer.push<Op::DrawRegion>({
+ .region = region,
+ .paint = SkPaint{},
});
CallCountingCanvas canvas;
@@ -279,11 +279,11 @@
TEST(CanvasOp, simpleDrawRoundRect) {
CanvasOpBuffer buffer;
EXPECT_EQ(buffer.size(), 0);
- buffer.push<Op::DrawRoundRect> ({
- .paint = SkPaint{},
- .rect = SkRect::MakeEmpty(),
- .rx = 10,
- .ry = 10
+ buffer.push<Op::DrawRoundRect>({
+ .rect = SkRect::MakeEmpty(),
+ .rx = 10,
+ .ry = 10,
+ .paint = SkPaint{},
});
CallCountingCanvas canvas;
@@ -611,9 +611,9 @@
EXPECT_EQ(0, canvas->sumTotalDrawCalls());
ImmediateModeRasterizer rasterizer{canvas};
- auto op = CanvasOp<Op::DrawRect> {
- .paint = SkPaint{},
- .rect = SkRect::MakeEmpty()
+ auto op = CanvasOp<Op::DrawRect>{
+ .rect = SkRect::MakeEmpty(),
+ .paint = SkPaint{},
};
EXPECT_TRUE(CanvasOpTraits::can_draw<decltype(op)>);
rasterizer.draw(op);
diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
index 0c389bfe8..cfa18ae 100644
--- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
+++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
@@ -40,7 +40,7 @@
// push the deferred updates to the layer
SkBitmap bitmap;
bitmap.allocN32Pixels(16, 16);
- sk_sp<SkImage> layerImage = SkImage::MakeFromBitmap(bitmap);
+ sk_sp<SkImage> layerImage = SkImages::RasterFromBitmap(bitmap);
layerUpdater->updateLayer(true, layerImage, 0, SkRect::MakeEmpty());
// the backing layer should now have all the properties applied.
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
index 623be1e..10a740a1 100644
--- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -23,9 +23,11 @@
#include <chrono>
#include "Properties.h"
+#include "tests/common/TestUtils.h"
using namespace testing;
using namespace std::chrono_literals;
+using namespace android::uirenderer::renderthread;
APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123);
APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456);
@@ -42,6 +44,9 @@
protected:
std::shared_ptr<HintSessionWrapper> mWrapper;
+ std::promise<int> blockDestroyCallUntil;
+ std::promise<int> waitForDestroyFinished;
+
class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding {
public:
void init() override;
@@ -53,11 +58,17 @@
MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t));
MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t));
MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t));
+ // Needs to be on the binding so it can be accessed from static methods
+ std::promise<int> allowCreationToFinish;
};
// Must be static so it can have function pointers we can point to with static methods
static std::shared_ptr<MockHintSessionBinding> sMockBinding;
+ static void allowCreationToFinish() { sMockBinding->allowCreationToFinish.set_value(1); }
+ void allowDelayedDestructionToStart() { blockDestroyCallUntil.set_value(1); }
+ void waitForDelayedDestructionToFinish() { waitForDestroyFinished.get_future().wait(); }
+
// Must be static so we can point to them as normal fn pointers with HintSessionBinding
static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); };
static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager,
@@ -65,6 +76,12 @@
int64_t initialTarget) {
return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
}
+ static APerformanceHintSession* stubManagedCreateSession(APerformanceHintManager* manager,
+ const int32_t* ids, size_t idsSize,
+ int64_t initialTarget) {
+ sMockBinding->allowCreationToFinish.get_future().wait();
+ return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
+ }
static APerformanceHintSession* stubSlowCreateSession(APerformanceHintManager* manager,
const int32_t* ids, size_t idsSize,
int64_t initialTarget) {
@@ -85,7 +102,21 @@
static void stubSendHint(APerformanceHintSession* session, int32_t hintId) {
sMockBinding->fakeSendHint(session, hintId);
};
- void waitForWrapperReady() { mWrapper->mHintSessionFuture.wait(); }
+ void waitForWrapperReady() {
+ if (mWrapper->mHintSessionFuture.has_value()) {
+ mWrapper->mHintSessionFuture->wait();
+ }
+ }
+ void scheduleDelayedDestroyManaged() {
+ TestUtils::runOnRenderThread([&](renderthread::RenderThread& rt) {
+ // Guaranteed to be scheduled first, allows destruction to start
+ rt.queue().postDelayed(0_ms, [&] { blockDestroyCallUntil.get_future().wait(); });
+ // Guaranteed to be scheduled second, destroys the session
+ mWrapper->delayedDestroy(rt, 1_ms, mWrapper);
+ // This is guaranteed to be queued after the destroy, signals that destruction is done
+ rt.queue().postDelayed(1_ms, [&] { waitForDestroyFinished.set_value(1); });
+ });
+ }
};
std::shared_ptr<HintSessionWrapperTests::MockHintSessionBinding>
@@ -113,6 +144,7 @@
}
void HintSessionWrapperTests::TearDown() {
+ // Ensure that anything running on RT is completely finished
mWrapper = nullptr;
sMockBinding = nullptr;
}
@@ -122,6 +154,7 @@
sMockBinding->createSession = stubSlowCreateSession;
mWrapper->init();
mWrapper = nullptr;
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
}
TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) {
@@ -148,4 +181,162 @@
mWrapper->sendLoadResetHint();
}
+TEST_F(HintSessionWrapperTests, delayedDeletionWorksCorrectlyAndOnlyClosesOnce) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to ensure the wrapper grabs the promise value
+ mWrapper->init();
+
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // Schedule delayed destruction, allow it to run, and check when it's done
+ scheduleDelayedDestroyManaged();
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+ // If we then delete the wrapper, it shouldn't close the session again
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(_)).Times(0);
+ mWrapper = nullptr;
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionResolvesBeforeAsyncCreationFinishes) {
+ // Here we test whether queueing delayedDestroy works while creation is still happening, if
+ // creation happens after
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ sMockBinding->createSession = &stubManagedCreateSession;
+
+ // Start creating the session and destroying it at the same time
+ mWrapper->init();
+ scheduleDelayedDestroyManaged();
+
+ // Allow destruction to happen first
+ allowDelayedDestructionToStart();
+
+ // Make sure destruction has had time to happen
+ std::this_thread::sleep_for(50ms);
+
+ // Then, allow creation to finish after delayed destroy runs
+ allowCreationToFinish();
+
+ // Wait for destruction to finish
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionResolvesAfterAsyncCreationFinishes) {
+ // Here we test whether queueing delayedDestroy works while creation is still happening, if
+ // creation happens before
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ sMockBinding->createSession = &stubManagedCreateSession;
+
+ // Start creating the session and destroying it at the same time
+ mWrapper->init();
+ scheduleDelayedDestroyManaged();
+
+ // Allow creation to happen first
+ allowCreationToFinish();
+
+ // Make sure creation has had time to happen
+ waitForWrapperReady();
+
+ // Then allow destruction to happen after creation is done
+ allowDelayedDestructionToStart();
+
+ // Wait for it to finish
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+ EXPECT_CALL(*sMockBinding, fakeReportActualWorkDuration(sessionPtr, 5_ms)).Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, report an actual duration
+ mWrapper->reportActualWorkDuration(5_ms);
+
+ // Then, run the delayed deletion after sending the update
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it didn't close within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), true);
+}
+
+TEST_F(HintSessionWrapperTests, loadUpDoesNotResetDeletionTimer) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+ EXPECT_CALL(*sMockBinding,
+ fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
+ .Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, send a load_up hint
+ mWrapper->sendLoadIncreaseHint();
+
+ // Then, run the delayed deletion after sending the update
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it closed within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, kill the session
+ mWrapper->destroy();
+
+ // Verify it died
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+
+ // Then, run the delayed deletion after manually killing the session
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it didn't close again and is still dead
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
} // namespace android::uirenderer::renderthread
\ No newline at end of file
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index f67042b..073a835 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-#include <VectorDrawable.h>
-#include <gtest/gtest.h>
-
#include <SkBlendMode.h>
#include <SkClipStack.h>
#include <SkSurface_Base.h>
+#include <VectorDrawable.h>
+#include <gtest/gtest.h>
#include <include/effects/SkImageFilters.h>
#include <string.h>
@@ -144,7 +143,7 @@
}
TEST(RenderNodeDrawable, composeOnLayer) {
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
SkCanvas& canvas = *surface->getCanvas();
canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
@@ -155,7 +154,7 @@
});
// attach a layer to the render node
- auto surfaceLayer = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surfaceLayer = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
auto canvas2 = surfaceLayer->getCanvas();
canvas2->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
rootNode->setLayerSurface(surfaceLayer);
@@ -190,7 +189,7 @@
}
TEST(RenderNodeDrawable, saveLayerClipAndMatrixRestore) {
- auto surface = SkSurface::MakeRasterN32Premul(400, 800);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(400, 800));
SkCanvas& canvas = *surface->getCanvas();
canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
@@ -356,7 +355,7 @@
EXPECT_EQ(3, canvas.getIndex());
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) {
+RENDERTHREAD_TEST(RenderNodeDrawable, emptyReceiver) {
class ProjectionTestCanvas : public SkCanvas {
public:
ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
@@ -420,7 +419,7 @@
EXPECT_EQ(2, canvas.getDrawCounter());
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
/* R is backward projected on B and C is a layer.
A
/ \
@@ -1053,7 +1052,7 @@
}
// Verify that layers are composed with linear filtering.
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) {
+RENDERTHREAD_TEST(RenderNodeDrawable, layerComposeQuality) {
static const int CANVAS_WIDTH = 1;
static const int CANVAS_HEIGHT = 1;
static const int LAYER_WIDTH = 1;
@@ -1077,7 +1076,8 @@
});
layerNode->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
- layerNode->setLayerSurface(SkSurface::MakeRasterN32Premul(LAYER_WIDTH, LAYER_HEIGHT));
+ layerNode->setLayerSurface(SkSurfaces::Raster(SkImageInfo::MakeN32Premul(LAYER_WIDTH,
+ LAYER_HEIGHT)));
FrameTestCanvas canvas;
RenderNodeDrawable drawable(layerNode.get(), &canvas, true);
@@ -1170,7 +1170,7 @@
}
// Draw a vector drawable twice but with different bounds and verify correct bounds are used.
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
+RENDERTHREAD_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
static const int CANVAS_WIDTH = 100;
static const int CANVAS_HEIGHT = 200;
class VectorDrawableTestCanvas : public TestCanvasBase {
@@ -1243,7 +1243,7 @@
SkBitmap bitmap;
bitmap.allocN32Pixels(CANVAS_WIDTH, CANVAS_HEIGHT);
bitmap.setImmutable();
- return SkImage::MakeFromBitmap(bitmap);
+ return bitmap.asImage();
}
SkCanvas* onNewCanvas() override { return new SimpleTestCanvas(); }
sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; }
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 80796f4..e727ea8 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -231,8 +231,7 @@
}
TEST(RenderNode, releasedCallback) {
- int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ int functor = TestUtils::createMockFunctor();
auto node = TestUtils::createNode(0, 0, 200, 400, [&](RenderProperties& props, Canvas& canvas) {
canvas.drawWebViewFunctor(functor);
@@ -332,3 +331,31 @@
EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage);
canvasContext->destroy();
}
+
+TEST(RenderNode, hasNoFill) {
+ auto rootNode =
+ TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+ Paint paint;
+ paint.setStyle(SkPaint::Style::kStroke_Style);
+ canvas.drawRect(10, 10, 100, 100, paint);
+ });
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode);
+
+ EXPECT_FALSE(rootNode.get()->getDisplayList().hasFill());
+ EXPECT_FALSE(rootNode.get()->getDisplayList().hasText());
+}
+
+TEST(RenderNode, hasFill) {
+ auto rootNode =
+ TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+ Paint paint;
+ paint.setStyle(SkPaint::kStrokeAndFill_Style);
+ canvas.drawRect(10, 10, 100, 100, paint);
+ });
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode);
+
+ EXPECT_TRUE(rootNode.get()->getDisplayList().hasFill());
+ EXPECT_FALSE(rootNode.get()->getDisplayList().hasText());
+}
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 9aa2e1d..0f8bd13 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -370,9 +370,9 @@
}
using namespace android::uirenderer;
-RENDERTHREAD_SKIA_PIPELINE_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
+RENDERTHREAD_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) {
- // RENDERTHREAD_SKIA_PIPELINE_TEST declares both SkiaVK and SkiaGL variants.
+ // RENDERTHREAD_TEST declares both SkiaVK and SkiaGL variants.
GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan";
}
if (!folderExist(getExternalStorageFolder())) {
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 87c5216..e53fcaa 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -36,7 +36,7 @@
using namespace android::uirenderer;
TEST(SkiaCanvas, drawShadowLayer) {
- auto surface = SkSurface::MakeRasterN32Premul(10, 10);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(10, 10));
SkiaCanvas canvas(surface->getCanvas());
// clear to white
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index f825d7c..064d42e 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -48,8 +48,7 @@
SkCanvas dummyCanvas;
RenderNodeDrawable drawable(nullptr, &dummyCanvas);
skiaDL->mChildNodes.emplace_back(nullptr, &dummyCanvas);
- int functor1 = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ int functor1 = TestUtils::createMockFunctor();
GLFunctorDrawable functorDrawable{functor1, &dummyCanvas};
WebViewFunctor_release(functor1);
skiaDL->mChildFunctors.push_back(&functorDrawable);
@@ -101,8 +100,7 @@
SkCanvas dummyCanvas;
- int functor1 = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ int functor1 = TestUtils::createMockFunctor();
auto& counts = TestUtils::countsForFunctor(functor1);
skiaDL.mChildFunctors.push_back(
skiaDL.allocateDrawable<GLFunctorDrawable>(functor1, &dummyCanvas));
@@ -131,6 +129,33 @@
EXPECT_EQ(counts.destroyed, 1);
}
+TEST(SkiaDisplayList, recordMutableBitmap) {
+ SkiaRecordingCanvas canvas{nullptr, 100, 100};
+ auto bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+ 10, 20, SkColorType::kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType));
+ EXPECT_FALSE(bitmap->isImmutable());
+ canvas.drawBitmap(*bitmap, 0, 0, nullptr);
+ auto displayList = canvas.finishRecording();
+ ASSERT_EQ(1, displayList->mMutableImages.size());
+ EXPECT_EQ(10, displayList->mMutableImages[0]->width());
+ EXPECT_EQ(20, displayList->mMutableImages[0]->height());
+}
+
+TEST(SkiaDisplayList, recordMutableBitmapInShader) {
+ SkiaRecordingCanvas canvas{nullptr, 100, 100};
+ auto bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+ 10, 20, SkColorType::kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType));
+ EXPECT_FALSE(bitmap->isImmutable());
+ SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
+ Paint paint;
+ paint.setShader(bitmap->makeImage()->makeShader(sampling));
+ canvas.drawPaint(paint);
+ auto displayList = canvas.finishRecording();
+ ASSERT_EQ(1, displayList->mMutableImages.size());
+ EXPECT_EQ(10, displayList->mMutableImages[0]->width());
+ EXPECT_EQ(20, displayList->mMutableImages[0]->height());
+}
+
class ContextFactory : public IContextFactory {
public:
virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
@@ -138,7 +163,7 @@
}
};
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
+RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
@@ -197,7 +222,7 @@
canvasContext->destroy();
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
+RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 4d0595e..3ded540 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -42,7 +42,7 @@
using namespace android::uirenderer::renderthread;
using namespace android::uirenderer::skiapipeline;
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrame) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -54,7 +54,7 @@
bool opaque = true;
android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
@@ -62,7 +62,7 @@
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) {
auto halfGreenNode = TestUtils::createSkiaNode(
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
Paint greenPaint;
@@ -76,7 +76,7 @@
renderNodes.push_back(halfGreenNode);
android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
- auto surface = SkSurface::MakeRasterN32Premul(2, 2);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
@@ -89,7 +89,7 @@
ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -100,7 +100,7 @@
renderNodes.push_back(redNode);
android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
- auto surface = SkSurface::MakeRasterN32Premul(2, 2);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
@@ -111,12 +111,12 @@
ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) {
+RENDERTHREAD_TEST(SkiaPipeline, renderLayer) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
- auto surfaceLayer1 = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surfaceLayer1 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
surfaceLayer1->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorWHITE);
redNode->setLayerSurface(surfaceLayer1);
@@ -127,7 +127,7 @@
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) {
blueCanvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
});
- auto surfaceLayer2 = SkSurface::MakeRasterN32Premul(2, 2);
+ auto surfaceLayer2 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
surfaceLayer2->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorWHITE);
blueNode->setLayerSurface(surfaceLayer2);
@@ -154,7 +154,7 @@
blueNode->setLayerSurface(sk_sp<SkSurface>());
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) {
+RENDERTHREAD_TEST(SkiaPipeline, renderOverdraw) {
ScopedProperty<bool> prop(Properties::debugOverdraw, true);
auto whiteNode = TestUtils::createSkiaNode(
@@ -169,7 +169,7 @@
// empty contentDrawBounds is avoiding backdrop/content logic, which would lead to less overdraw
android::uirenderer::Rect contentDrawBounds(0, 0, 0, 0);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
// Initialize the canvas to blue.
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
@@ -227,7 +227,7 @@
};
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) {
+RENDERTHREAD_TEST(SkiaPipeline, deferRenderNodeScene) {
class DeferTestCanvas : public SkCanvas {
public:
DeferTestCanvas() : SkCanvas(800, 600) {}
@@ -297,7 +297,7 @@
EXPECT_EQ(4, surface->canvas()->mDrawCounter);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) {
+RENDERTHREAD_TEST(SkiaPipeline, clipped) {
static const int CANVAS_WIDTH = 200;
static const int CANVAS_HEIGHT = 200;
class ClippedTestCanvas : public SkCanvas {
@@ -330,7 +330,7 @@
}
// Test renderFrame with a dirty clip and a pre-transform matrix.
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped_rotated) {
+RENDERTHREAD_TEST(SkiaPipeline, clipped_rotated) {
static const int CANVAS_WIDTH = 200;
static const int CANVAS_HEIGHT = 100;
static const SkMatrix rotateMatrix = SkMatrix::MakeAll(0, -1, CANVAS_HEIGHT, 1, 0, 0, 0, 0, 1);
@@ -366,7 +366,7 @@
EXPECT_EQ(1, surface->canvas()->mDrawCounter);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) {
+RENDERTHREAD_TEST(SkiaPipeline, clip_replace) {
static const int CANVAS_WIDTH = 50;
static const int CANVAS_HEIGHT = 50;
class ClipReplaceTestCanvas : public SkCanvas {
@@ -396,7 +396,7 @@
EXPECT_EQ(1, surface->canvas()->mDrawCounter);
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) {
+RENDERTHREAD_TEST(SkiaPipeline, context_lost) {
test::TestContext context;
auto surface = context.surface();
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
@@ -410,7 +410,7 @@
EXPECT_TRUE(pipeline->isSurfaceReady());
}
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) {
+RENDERTHREAD_TEST(SkiaPipeline, pictureCallback) {
// create a pipeline and add a picture callback
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
int callbackCount = 0;
@@ -428,7 +428,7 @@
renderNodes.push_back(redNode);
bool opaque = true;
android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
- auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 499afa0..c71c4d2 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -29,6 +29,7 @@
#include "hwui/MinikinSkia.h"
#include "hwui/Typeface.h"
+#include "utils/TypefaceUtils.h"
using namespace android;
@@ -56,7 +57,7 @@
sk_sp<SkData> skData =
SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
- sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
std::shared_ptr<minikin::MinikinFont> font =
diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp
new file mode 100644
index 0000000..c70a304
--- /dev/null
+++ b/libs/hwui/tests/unit/UnderlineTest.cpp
@@ -0,0 +1,154 @@
+/*
+ * 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 <fcntl.h>
+#include <flag_macros.h>
+#include <gtest/gtest.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <utils/Log.h>
+
+#include "SkAlphaType.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkFontMgr.h"
+#include "SkImageInfo.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+#include "SkiaCanvas.h"
+#include "hwui/Bitmap.h"
+#include "hwui/DrawTextFunctor.h"
+#include "hwui/MinikinSkia.h"
+#include "hwui/MinikinUtils.h"
+#include "hwui/Paint.h"
+#include "hwui/Typeface.h"
+#include "utils/TypefaceUtils.h"
+
+using namespace android;
+
+namespace {
+
+constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf";
+constexpr char kJPFont[] = "/system/fonts/NotoSansCJK-Regular.ttc";
+
+// The underline position and thickness are cames from post table.
+constexpr float ROBOTO_POSITION_EM = 150.0 / 2048.0;
+constexpr float ROBOTO_THICKNESS_EM = 100.0 / 2048.0;
+constexpr float NOTO_CJK_POSITION_EM = 125.0 / 1000.0;
+constexpr float NOTO_CJK_THICKNESS_EM = 50.0 / 1000.0;
+
+void unmap(const void* ptr, void* context) {
+ void* p = const_cast<void*>(ptr);
+ size_t len = reinterpret_cast<size_t>(context);
+ munmap(p, len);
+}
+
+// Create a font family from a single font file.
+std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) {
+ int fd = open(fileName, O_RDONLY);
+ LOG_ALWAYS_FATAL_IF(fd == -1, "Failed to open file %s", fileName);
+ struct stat st = {};
+ LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", fileName);
+ void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ sk_sp<SkData> skData =
+ SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
+ std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
+ sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
+ LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
+ std::shared_ptr<minikin::MinikinFont> font =
+ std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, fileName, 0,
+ std::vector<minikin::FontVariation>());
+ std::vector<std::shared_ptr<minikin::Font>> fonts;
+ fonts.push_back(minikin::Font::Builder(font).build());
+ return minikin::FontFamily::create(std::move(fonts));
+}
+
+// Create a typeface from roboto and NotoCJK.
+Typeface* makeTypeface() {
+ return Typeface::createFromFamilies(
+ std::vector<std::shared_ptr<minikin::FontFamily>>(
+ {buildFamily(kRobotoVariable), buildFamily(kJPFont)}),
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, nullptr /* fallback */);
+}
+
+// Execute a text layout.
+minikin::Layout doLayout(const std::vector<uint16_t> text, Paint paint, Typeface* typeface) {
+ return MinikinUtils::doLayout(&paint, minikin::Bidi::LTR, typeface, text.data(), text.size(),
+ 0 /* start */, text.size(), 0, text.size(), nullptr);
+}
+
+DrawTextFunctor processFunctor(const std::vector<uint16_t>& text, Paint* paint) {
+ // Create canvas
+ SkImageInfo info = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType);
+ sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(info);
+ SkBitmap skBitmap;
+ bitmap->getSkBitmap(&skBitmap);
+ SkiaCanvas canvas(skBitmap);
+
+ // Create minikin::Layout
+ std::unique_ptr<Typeface> typeface(makeTypeface());
+ minikin::Layout layout = doLayout(text, *paint, typeface.get());
+
+ DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance());
+ MinikinUtils::forFontRun(layout, paint, f);
+ return f;
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Roboto,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // the text is "abc"
+ DrawTextFunctor functor = processFunctor({0x0061, 0x0062, 0x0063}, &paint);
+
+ EXPECT_EQ(ROBOTO_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(ROBOTO_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, NotoCJK,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // The text is ććć in Japanese
+ DrawTextFunctor functor = processFunctor({0x3042, 0x3044, 0x3046}, &paint);
+
+ EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Mixture,
+ REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+ fix_double_underline))) {
+ float textSize = 100;
+ Paint paint;
+ paint.getSkFont().setSize(textSize);
+ paint.setUnderline(true);
+ // The text is aćc. The only middle of the character is Japanese.
+ DrawTextFunctor functor = processFunctor({0x0061, 0x3044, 0x0063}, &paint);
+
+ // We use the bottom, thicker line as underline. Here, use Noto's one.
+ EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+ EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+} // namespace
diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
index e1fb8b7..5e8f13d 100644
--- a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
+++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
@@ -26,9 +26,15 @@
using namespace android;
using namespace android::uirenderer;
+#define ASSUME_GLES() \
+ if (WebViewFunctor_queryPlatformRenderMode() != RenderMode::OpenGL_ES) \
+ GTEST_SKIP() << "Not in GLES, skipping test"
+
TEST(WebViewFunctor, createDestroyGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
WebViewFunctor_release(functor);
TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
@@ -41,8 +47,10 @@
}
TEST(WebViewFunctor, createSyncHandleGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
@@ -82,8 +90,10 @@
}
TEST(WebViewFunctor, createSyncDrawGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
@@ -108,9 +118,11 @@
EXPECT_EQ(1, counts.destroyed);
}
-TEST(WebViewFunctor, contextDestroyed) {
+TEST(WebViewFunctor, contextDestroyedGLES) {
+ ASSUME_GLES();
int functor = WebViewFunctor_create(
- nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+ nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+ RenderMode::OpenGL_ES);
ASSERT_NE(-1, functor);
auto handle = WebViewFunctorManager::instance().handleFor(functor);
ASSERT_TRUE(handle);
diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp
index 10c874e..76cbc8a 100644
--- a/libs/hwui/tests/unit/main.cpp
+++ b/libs/hwui/tests/unit/main.cpp
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
+#include <getopt.h>
+#include <signal.h>
#include "Properties.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
#include "hwui/Typeface.h"
#include "tests/common/LeakChecker.h"
-#include <signal.h>
-
using namespace std;
using namespace android;
using namespace android::uirenderer;
@@ -45,6 +45,57 @@
raise(sig);
}
+// For options that only exist in long-form. Anything in the
+// 0-255 range is reserved for short options (which just use their ASCII value)
+namespace LongOpts {
+enum {
+ Reserved = 255,
+ Renderer,
+};
+}
+
+static const struct option LONG_OPTIONS[] = {
+ {"renderer", required_argument, nullptr, LongOpts::Renderer}, {0, 0, 0, 0}};
+
+static RenderPipelineType parseRenderer(const char* renderer) {
+ // Anything that's not skiavk is skiagl
+ if (!strcmp(renderer, "skiavk")) {
+ return RenderPipelineType::SkiaVulkan;
+ }
+ return RenderPipelineType::SkiaGL;
+}
+
+struct Options {
+ RenderPipelineType renderer = RenderPipelineType::SkiaGL;
+};
+
+Options parseOptions(int argc, char* argv[]) {
+ int c;
+ opterr = 0;
+ Options opts;
+
+ while (true) {
+ /* getopt_long stores the option index here. */
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "", LONG_OPTIONS, &option_index);
+
+ if (c == -1) break;
+
+ switch (c) {
+ case 0:
+ // Option set a flag, don't need to do anything
+ // (although none of the current LONG_OPTIONS do this...)
+ break;
+
+ case LongOpts::Renderer:
+ opts.renderer = parseRenderer(optarg);
+ break;
+ }
+ }
+ return opts;
+}
+
class TypefaceEnvironment : public testing::Environment {
public:
virtual void SetUp() { Typeface::setRobotoTypefaceForTest(); }
@@ -64,8 +115,9 @@
// Avoid talking to SF
Properties::isolatedProcess = true;
- // Default to GLES (Vulkan-aware tests will override this)
- Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL);
+
+ auto opts = parseOptions(argc, argv);
+ Properties::overrideRenderPipelineType(opts.renderer);
// Run the tests
testing::InitGoogleTest(&argc, argv);
diff --git a/libs/hwui/utils/ForceDark.h b/libs/hwui/utils/ForceDark.h
new file mode 100644
index 0000000..28538c4b
--- /dev/null
+++ b/libs/hwui/utils/ForceDark.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef FORCEDARKUTILS_H
+#define FORCEDARKUTILS_H
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * The type of force dark set on the renderer, if any.
+ *
+ * This should stay in sync with the java @IntDef in
+ * frameworks/base/graphics/java/android/graphics/ForceDarkType.java
+ */
+enum class ForceDarkType : __uint8_t { NONE = 0, FORCE_DARK = 1, FORCE_INVERT_COLOR_DARK = 2 };
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif // FORCEDARKUTILS_H
\ No newline at end of file
diff --git a/libs/hwui/utils/TypefaceUtils.cpp b/libs/hwui/utils/TypefaceUtils.cpp
new file mode 100644
index 0000000..a30b925
--- /dev/null
+++ b/libs/hwui/utils/TypefaceUtils.cpp
@@ -0,0 +1,28 @@
+/*
+ * 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 <utils/TypefaceUtils.h>
+
+#include "include/ports/SkFontMgr_empty.h"
+
+namespace android {
+
+sk_sp<SkFontMgr> FreeTypeFontMgr() {
+ static sk_sp<SkFontMgr> mgr = SkFontMgr_New_Custom_Empty();
+ return mgr;
+}
+
+} // namespace android
diff --git a/libs/hwui/utils/TypefaceUtils.h b/libs/hwui/utils/TypefaceUtils.h
new file mode 100644
index 0000000..c0adeae
--- /dev/null
+++ b/libs/hwui/utils/TypefaceUtils.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "SkFontMgr.h"
+#include "SkRefCnt.h"
+
+namespace android {
+
+// Return an SkFontMgr which is capable of turning bytes into a SkTypeface using Freetype.
+// There are no other fonts inside this SkFontMgr (e.g. no system fonts).
+sk_sp<SkFontMgr> FreeTypeFontMgr();
+
+} // namespace android
\ No newline at end of file