summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/androidfw/Android.mk11
-rw-r--r--libs/androidfw/AssetManager.cpp46
-rw-r--r--libs/androidfw/DisplayEventDispatcher.cpp147
-rw-r--r--libs/androidfw/LocaleData.cpp219
-rw-r--r--libs/androidfw/LocaleDataTables.cpp1701
-rw-r--r--libs/androidfw/ResourceTypes.cpp506
-rw-r--r--libs/androidfw/ZipFileRO.cpp8
-rw-r--r--libs/androidfw/tests/Android.mk1
-rw-r--r--libs/androidfw/tests/AppAsLib_test.cpp68
-rw-r--r--libs/androidfw/tests/BackupData_test.cpp2
-rw-r--r--libs/androidfw/tests/ConfigLocale_test.cpp469
-rw-r--r--libs/androidfw/tests/ResTable_test.cpp75
-rw-r--r--libs/androidfw/tests/TestHelpers.h2
-rw-r--r--libs/androidfw/tests/ZipUtils_test.cpp7
-rw-r--r--libs/androidfw/tests/data/appaslib/AndroidManifest.xml21
-rw-r--r--libs/androidfw/tests/data/appaslib/R.h52
-rw-r--r--libs/androidfw/tests/data/appaslib/appaslib_arsc.h68
-rw-r--r--libs/androidfw/tests/data/appaslib/appaslib_lib_arsc.h68
-rwxr-xr-xlibs/androidfw/tests/data/appaslib/build28
-rw-r--r--libs/androidfw/tests/data/appaslib/res/values/values.xml24
-rw-r--r--libs/androidfw/tests/data/basic/R.h2
-rw-r--r--libs/androidfw/tests/data/basic/basic_arsc.h114
-rw-r--r--libs/androidfw/tests/data/basic/res/values-vs/values.xml19
-rw-r--r--libs/androidfw/tests/data/basic/split_de_fr_arsc.h54
-rw-r--r--libs/androidfw/tests/data/basic/split_hdpi_v4_arsc.h29
-rw-r--r--libs/androidfw/tests/data/basic/split_xhdpi_v4_arsc.h29
-rw-r--r--libs/androidfw/tests/data/basic/split_xxhdpi_v4_arsc.h29
-rw-r--r--libs/androidfw/tests/data/system/R.h6
-rw-r--r--libs/androidfw/tests/data/system/res/values-sv/values.xml20
-rw-r--r--libs/androidfw/tests/data/system/system_arsc.h69
-rw-r--r--libs/common_time/common_time_server.cpp2
-rw-r--r--libs/hwui/AmbientShadow.cpp13
-rw-r--r--libs/hwui/Android.common.mk127
-rw-r--r--libs/hwui/Android.mk335
-rw-r--r--libs/hwui/Animator.cpp208
-rw-r--r--libs/hwui/Animator.h73
-rw-r--r--libs/hwui/AnimatorManager.cpp88
-rw-r--r--libs/hwui/AnimatorManager.h7
-rw-r--r--libs/hwui/AssetAtlas.cpp63
-rw-r--r--libs/hwui/AssetAtlas.h39
-rw-r--r--libs/hwui/BakedOpDispatcher.cpp850
-rw-r--r--libs/hwui/BakedOpDispatcher.h53
-rw-r--r--libs/hwui/BakedOpRenderer.cpp373
-rw-r--r--libs/hwui/BakedOpRenderer.h150
-rw-r--r--libs/hwui/BakedOpState.cpp169
-rw-r--r--libs/hwui/BakedOpState.h172
-rw-r--r--libs/hwui/Caches.cpp84
-rw-r--r--libs/hwui/Caches.h40
-rw-r--r--libs/hwui/CanvasState.cpp110
-rw-r--r--libs/hwui/CanvasState.h42
-rw-r--r--libs/hwui/ClipArea.cpp327
-rw-r--r--libs/hwui/ClipArea.h125
-rw-r--r--libs/hwui/DamageAccumulator.cpp22
-rw-r--r--libs/hwui/DamageAccumulator.h7
-rw-r--r--libs/hwui/Debug.h15
-rw-r--r--libs/hwui/DeferredDisplayList.cpp71
-rw-r--r--libs/hwui/DeferredDisplayList.h27
-rw-r--r--libs/hwui/DeferredLayerUpdater.cpp12
-rw-r--r--libs/hwui/DeferredLayerUpdater.h14
-rw-r--r--libs/hwui/DeviceInfo.cpp49
-rw-r--r--libs/hwui/DeviceInfo.h54
-rw-r--r--libs/hwui/DisplayList.cpp56
-rw-r--r--libs/hwui/DisplayList.h113
-rw-r--r--libs/hwui/DisplayListCanvas.cpp234
-rw-r--r--libs/hwui/DisplayListCanvas.h131
-rw-r--r--libs/hwui/DisplayListOp.h167
-rw-r--r--libs/hwui/Dither.cpp2
-rw-r--r--libs/hwui/Extensions.cpp79
-rw-r--r--libs/hwui/Extensions.h26
-rw-r--r--libs/hwui/FboCache.cpp13
-rw-r--r--libs/hwui/FloatColor.h12
-rw-r--r--libs/hwui/FontRenderer.cpp141
-rw-r--r--libs/hwui/FontRenderer.h68
-rw-r--r--libs/hwui/FrameBuilder.cpp954
-rw-r--r--libs/hwui/FrameBuilder.h257
-rw-r--r--libs/hwui/FrameInfo.h4
-rw-r--r--libs/hwui/FrameInfoVisualizer.cpp66
-rw-r--r--libs/hwui/FrameInfoVisualizer.h12
-rw-r--r--libs/hwui/FrameMetricsObserver.h30
-rw-r--r--libs/hwui/FrameMetricsReporter.h65
-rw-r--r--libs/hwui/GammaFontRenderer.cpp213
-rw-r--r--libs/hwui/GammaFontRenderer.h159
-rw-r--r--libs/hwui/GlFunctorLifecycleListener.h32
-rw-r--r--libs/hwui/Glop.h16
-rw-r--r--libs/hwui/GlopBuilder.cpp101
-rw-r--r--libs/hwui/GlopBuilder.h19
-rw-r--r--libs/hwui/GpuMemoryTracker.cpp140
-rw-r--r--libs/hwui/GpuMemoryTracker.h76
-rw-r--r--libs/hwui/GradientCache.cpp58
-rw-r--r--libs/hwui/GradientCache.h11
-rw-r--r--libs/hwui/Image.cpp2
-rw-r--r--libs/hwui/Interpolator.cpp8
-rw-r--r--libs/hwui/JankTracker.cpp73
-rw-r--r--libs/hwui/JankTracker.h4
-rw-r--r--libs/hwui/Layer.cpp63
-rw-r--r--libs/hwui/Layer.h43
-rw-r--r--libs/hwui/LayerBuilder.cpp378
-rw-r--r--libs/hwui/LayerBuilder.h137
-rw-r--r--libs/hwui/LayerCache.cpp59
-rw-r--r--libs/hwui/LayerCache.h13
-rw-r--r--libs/hwui/LayerRenderer.cpp44
-rw-r--r--libs/hwui/LayerRenderer.h6
-rw-r--r--libs/hwui/LayerUpdateQueue.cpp42
-rw-r--r--libs/hwui/LayerUpdateQueue.h53
-rw-r--r--libs/hwui/Matrix.cpp14
-rw-r--r--libs/hwui/Matrix.h41
-rw-r--r--libs/hwui/OpDumper.cpp49
-rw-r--r--libs/hwui/OpDumper.h32
-rw-r--r--libs/hwui/OpenGLRenderer.cpp392
-rwxr-xr-xlibs/hwui/OpenGLRenderer.h150
-rw-r--r--libs/hwui/Outline.h68
-rw-r--r--libs/hwui/Patch.cpp21
-rw-r--r--libs/hwui/Patch.h6
-rw-r--r--libs/hwui/PatchCache.cpp14
-rw-r--r--libs/hwui/PatchCache.h2
-rw-r--r--libs/hwui/PathCache.cpp130
-rw-r--r--libs/hwui/PathCache.h33
-rw-r--r--libs/hwui/PathParser.cpp256
-rw-r--r--libs/hwui/PathParser.h52
-rw-r--r--libs/hwui/PathTessellator.cpp93
-rw-r--r--libs/hwui/PathTessellator.h18
-rw-r--r--libs/hwui/PixelBuffer.cpp23
-rw-r--r--libs/hwui/PixelBuffer.h16
-rw-r--r--libs/hwui/Program.cpp3
-rw-r--r--libs/hwui/Program.h33
-rw-r--r--libs/hwui/ProgramCache.cpp67
-rw-r--r--libs/hwui/Properties.cpp70
-rw-r--r--libs/hwui/Properties.h84
-rw-r--r--libs/hwui/PropertyValuesAnimatorSet.cpp145
-rw-r--r--libs/hwui/PropertyValuesAnimatorSet.h84
-rw-r--r--libs/hwui/PropertyValuesHolder.cpp99
-rw-r--r--libs/hwui/PropertyValuesHolder.h121
-rw-r--r--libs/hwui/Readback.cpp192
-rw-r--r--libs/hwui/Readback.h44
-rw-r--r--libs/hwui/RecordedOp.h533
-rw-r--r--libs/hwui/RecordingCanvas.cpp657
-rw-r--r--libs/hwui/RecordingCanvas.h322
-rw-r--r--libs/hwui/Rect.h93
-rw-r--r--libs/hwui/RenderBufferCache.cpp47
-rw-r--r--libs/hwui/RenderBufferCache.h19
-rw-r--r--libs/hwui/RenderNode.cpp531
-rw-r--r--libs/hwui/RenderNode.h156
-rw-r--r--libs/hwui/RenderProperties.cpp67
-rw-r--r--libs/hwui/RenderProperties.h83
-rw-r--r--libs/hwui/ResourceCache.cpp2
-rw-r--r--libs/hwui/RevealClip.h3
-rw-r--r--libs/hwui/ShadowTessellator.cpp4
-rw-r--r--libs/hwui/SkiaCanvas.cpp307
-rw-r--r--libs/hwui/SkiaCanvasProxy.cpp122
-rw-r--r--libs/hwui/SkiaCanvasProxy.h8
-rw-r--r--libs/hwui/SkiaShader.cpp21
-rw-r--r--libs/hwui/Snapshot.cpp74
-rw-r--r--libs/hwui/Snapshot.h41
-rw-r--r--libs/hwui/SpotShadow.cpp17
-rw-r--r--libs/hwui/TessellationCache.cpp137
-rw-r--r--libs/hwui/TessellationCache.h98
-rw-r--r--libs/hwui/TextDropShadowCache.cpp90
-rw-r--r--libs/hwui/TextDropShadowCache.h54
-rw-r--r--libs/hwui/Texture.cpp231
-rw-r--r--libs/hwui/Texture.h115
-rw-r--r--libs/hwui/TextureCache.cpp181
-rw-r--r--libs/hwui/TextureCache.h31
-rw-r--r--libs/hwui/TreeInfo.h82
-rw-r--r--libs/hwui/Vector.h4
-rw-r--r--libs/hwui/VectorDrawable.cpp588
-rw-r--r--libs/hwui/VectorDrawable.h720
-rw-r--r--libs/hwui/Vertex.h1
-rw-r--r--libs/hwui/VertexBuffer.h8
-rw-r--r--libs/hwui/debug/nullegl.cpp (renamed from libs/hwui/tests/nullegl.cpp)0
-rw-r--r--libs/hwui/debug/nullgles.cpp (renamed from libs/hwui/tests/nullgles.cpp)13
-rw-r--r--libs/hwui/debug/unwrap_gles.h918
-rw-r--r--libs/hwui/debug/wrap_gles.cpp93
-rw-r--r--libs/hwui/debug/wrap_gles.h918
-rw-r--r--libs/hwui/font/CacheTexture.cpp28
-rw-r--r--libs/hwui/font/CacheTexture.h9
-rw-r--r--libs/hwui/font/Font.cpp50
-rw-r--r--libs/hwui/font/Font.h11
-rw-r--r--libs/hwui/font/FontUtil.h23
-rw-r--r--libs/hwui/hwui/Canvas.cpp225
-rw-r--r--libs/hwui/hwui/Canvas.h (renamed from libs/hwui/Canvas.h)134
-rw-r--r--libs/hwui/hwui/MinikinSkia.cpp128
-rw-r--r--libs/hwui/hwui/MinikinSkia.h68
-rw-r--r--libs/hwui/hwui/MinikinUtils.cpp107
-rw-r--r--libs/hwui/hwui/MinikinUtils.h82
-rw-r--r--libs/hwui/hwui/Paint.h93
-rw-r--r--libs/hwui/hwui/PaintImpl.cpp59
-rw-r--r--libs/hwui/hwui/Typeface.cpp167
-rw-r--r--libs/hwui/hwui/Typeface.h55
-rw-r--r--libs/hwui/hwui_static_deps.mk31
-rw-r--r--libs/hwui/protos/ProtoHelpers.h41
-rw-r--r--libs/hwui/protos/hwui.proto99
-rw-r--r--libs/hwui/renderstate/Blend.cpp41
-rw-r--r--libs/hwui/renderstate/Blend.h3
-rw-r--r--libs/hwui/renderstate/MeshState.cpp67
-rw-r--r--libs/hwui/renderstate/MeshState.h28
-rw-r--r--libs/hwui/renderstate/OffscreenBufferPool.cpp207
-rw-r--r--libs/hwui/renderstate/OffscreenBufferPool.h161
-rw-r--r--libs/hwui/renderstate/RenderState.cpp99
-rw-r--r--libs/hwui/renderstate/RenderState.h41
-rw-r--r--libs/hwui/renderstate/Scissor.cpp22
-rw-r--r--libs/hwui/renderstate/Scissor.h3
-rw-r--r--libs/hwui/renderstate/Stencil.cpp24
-rw-r--r--libs/hwui/renderstate/Stencil.h25
-rw-r--r--libs/hwui/renderstate/TextureState.cpp9
-rw-r--r--libs/hwui/renderstate/TextureState.h5
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp555
-rw-r--r--libs/hwui/renderthread/CanvasContext.h129
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp34
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.h21
-rw-r--r--libs/hwui/renderthread/EglManager.cpp185
-rw-r--r--libs/hwui/renderthread/EglManager.h44
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp226
-rw-r--r--libs/hwui/renderthread/RenderProxy.h42
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp35
-rw-r--r--libs/hwui/renderthread/RenderThread.h11
-rw-r--r--libs/hwui/tests/Android.mk36
-rw-r--r--libs/hwui/tests/common/TestContext.cpp (renamed from libs/hwui/tests/TestContext.cpp)31
-rw-r--r--libs/hwui/tests/common/TestContext.h (renamed from libs/hwui/tests/TestContext.h)2
-rw-r--r--libs/hwui/tests/common/TestScene.cpp36
-rw-r--r--libs/hwui/tests/common/TestScene.h79
-rw-r--r--libs/hwui/tests/common/TestUtils.cpp128
-rw-r--r--libs/hwui/tests/common/TestUtils.h233
-rw-r--r--libs/hwui/tests/common/scenes/ClippingAnimation.cpp67
-rw-r--r--libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp64
-rw-r--r--libs/hwui/tests/common/scenes/HwLayerAnimation.cpp46
-rw-r--r--libs/hwui/tests/common/scenes/ListViewAnimation.cpp150
-rw-r--r--libs/hwui/tests/common/scenes/OpPropAnimation.cpp72
-rw-r--r--libs/hwui/tests/common/scenes/OvalAnimation.cpp49
-rw-r--r--libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp67
-rw-r--r--libs/hwui/tests/common/scenes/RecentsAnimation.cpp88
-rw-r--r--libs/hwui/tests/common/scenes/RectGridAnimation.cpp61
-rw-r--r--libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp65
-rw-r--r--libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp63
-rw-r--r--libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp63
-rw-r--r--libs/hwui/tests/common/scenes/ShapeAnimation.cpp105
-rw-r--r--libs/hwui/tests/common/scenes/TestSceneBase.h34
-rw-r--r--libs/hwui/tests/common/scenes/TextAnimation.cpp60
-rw-r--r--libs/hwui/tests/how_to_run.txt17
-rw-r--r--libs/hwui/tests/macrobench/TestSceneRunner.cpp126
-rw-r--r--libs/hwui/tests/macrobench/how_to_run.txt5
-rw-r--r--libs/hwui/tests/macrobench/main.cpp266
-rw-r--r--libs/hwui/tests/main.cpp340
-rw-r--r--libs/hwui/tests/microbench/DisplayListCanvasBench.cpp171
-rw-r--r--libs/hwui/tests/microbench/FontBench.cpp50
-rw-r--r--libs/hwui/tests/microbench/FrameBuilderBench.cpp151
-rw-r--r--libs/hwui/tests/microbench/LinearAllocatorBench.cpp48
-rw-r--r--libs/hwui/tests/microbench/PathParserBench.cpp51
-rw-r--r--libs/hwui/tests/microbench/ShadowBench.cpp111
-rw-r--r--libs/hwui/tests/microbench/TaskManagerBench.cpp88
-rwxr-xr-xlibs/hwui/tests/microbench/how_to_run.txt4
-rw-r--r--libs/hwui/tests/microbench/main.cpp (renamed from libs/hwui/unit_tests/main.cpp)9
-rwxr-xr-xlibs/hwui/tests/scripts/prep_volantis.sh54
-rw-r--r--libs/hwui/tests/unit/BakedOpDispatcherTests.cpp157
-rw-r--r--libs/hwui/tests/unit/BakedOpRendererTests.cpp54
-rw-r--r--libs/hwui/tests/unit/BakedOpStateTests.cpp275
-rw-r--r--libs/hwui/tests/unit/CanvasStateTests.cpp166
-rw-r--r--libs/hwui/tests/unit/ClipAreaTests.cpp279
-rw-r--r--libs/hwui/tests/unit/DamageAccumulatorTests.cpp129
-rw-r--r--libs/hwui/tests/unit/DeviceInfoTests.cpp30
-rw-r--r--libs/hwui/tests/unit/FatVectorTests.cpp119
-rw-r--r--libs/hwui/tests/unit/FontRendererTests.cpp55
-rw-r--r--libs/hwui/tests/unit/FrameBuilderTests.cpp2172
-rw-r--r--libs/hwui/tests/unit/GlopBuilderTests.cpp147
-rw-r--r--libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp70
-rw-r--r--libs/hwui/tests/unit/GradientCacheTests.cpp40
-rw-r--r--libs/hwui/tests/unit/LayerUpdateQueueTests.cpp82
-rw-r--r--libs/hwui/tests/unit/LeakCheckTests.cpp67
-rw-r--r--libs/hwui/tests/unit/LinearAllocatorTests.cpp133
-rw-r--r--libs/hwui/tests/unit/MatrixTests.cpp50
-rw-r--r--libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp155
-rw-r--r--libs/hwui/tests/unit/OpDumperTests.cpp43
-rw-r--r--libs/hwui/tests/unit/RecordingCanvasTests.cpp762
-rw-r--r--libs/hwui/tests/unit/RenderNodeTests.cpp91
-rw-r--r--libs/hwui/tests/unit/SkiaBehaviorTests.cpp50
-rw-r--r--libs/hwui/tests/unit/SkiaCanvasTests.cpp61
-rw-r--r--libs/hwui/tests/unit/SnapshotTests.cpp74
-rw-r--r--libs/hwui/tests/unit/StringUtilsTests.cpp53
-rw-r--r--libs/hwui/tests/unit/TextDropShadowCacheTests.cpp55
-rw-r--r--libs/hwui/tests/unit/VectorDrawableTests.cpp430
-rwxr-xr-xlibs/hwui/tests/unit/how_to_run.txt (renamed from libs/hwui/unit_tests/how_to_run.txt)2
-rw-r--r--libs/hwui/tests/unit/main.cpp136
-rw-r--r--libs/hwui/thread/Barrier.h5
-rw-r--r--libs/hwui/thread/TaskManager.cpp16
-rw-r--r--libs/hwui/thread/TaskManager.h9
-rw-r--r--libs/hwui/unit_tests/Android.mk35
-rw-r--r--libs/hwui/unit_tests/ClipAreaTests.cpp127
-rw-r--r--libs/hwui/unit_tests/DamageAccumulatorTests.cpp81
-rw-r--r--libs/hwui/unit_tests/LinearAllocatorTests.cpp108
-rw-r--r--libs/hwui/utils/Blur.cpp18
-rw-r--r--libs/hwui/utils/Blur.h2
-rw-r--r--libs/hwui/utils/Color.h86
-rw-r--r--libs/hwui/utils/FatVector.h107
-rw-r--r--libs/hwui/utils/GLUtils.cpp10
-rw-r--r--libs/hwui/utils/GLUtils.h16
-rw-r--r--libs/hwui/utils/LinearAllocator.cpp35
-rw-r--r--libs/hwui/utils/LinearAllocator.h97
-rw-r--r--libs/hwui/utils/Macros.h13
-rw-r--r--libs/hwui/utils/MathUtils.h13
-rw-r--r--libs/hwui/utils/NinePatch.h37
-rw-r--r--libs/hwui/utils/NinePatchImpl.cpp326
-rw-r--r--libs/hwui/utils/PaintUtils.h56
-rw-r--r--libs/hwui/utils/RingBuffer.h6
-rw-r--r--libs/hwui/utils/SortedList.h242
-rw-r--r--libs/hwui/utils/SortedListImpl.cpp126
-rw-r--r--libs/hwui/utils/SortedListImpl.h65
-rw-r--r--libs/hwui/utils/StringUtils.cpp38
-rw-r--r--libs/hwui/utils/StringUtils.h57
-rw-r--r--libs/hwui/utils/TestWindowContext.cpp220
-rw-r--r--libs/hwui/utils/TestWindowContext.h68
-rw-r--r--libs/hwui/utils/TimeUtils.h31
-rw-r--r--libs/hwui/utils/TinyHashMap.h70
-rw-r--r--libs/hwui/utils/VectorDrawableUtils.cpp491
-rw-r--r--libs/hwui/utils/VectorDrawableUtils.h40
-rw-r--r--libs/input/PointerController.cpp193
-rw-r--r--libs/input/PointerController.h44
315 files changed, 31058 insertions, 5820 deletions
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk
index 2f287000f746..6bbfcd2f9d8a 100644
--- a/libs/androidfw/Android.mk
+++ b/libs/androidfw/Android.mk
@@ -21,6 +21,7 @@ commonSources := \
Asset.cpp \
AssetDir.cpp \
AssetManager.cpp \
+ LocaleData.cpp \
misc.cpp \
ObbFile.cpp \
ResourceTypes.cpp \
@@ -33,17 +34,17 @@ deviceSources := \
$(commonSources) \
BackupData.cpp \
BackupHelpers.cpp \
- CursorWindow.cpp
+ CursorWindow.cpp \
+ DisplayEventDispatcher.cpp
hostSources := $(commonSources)
# For the host
# =====================================================
include $(CLEAR_VARS)
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
LOCAL_MODULE:= libandroidfw
-LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_HOST_OS := darwin linux windows
LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS
LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
LOCAL_SRC_FILES:= $(hostSources)
@@ -56,19 +57,17 @@ include $(BUILD_HOST_STATIC_LIBRARY)
# =====================================================
include $(CLEAR_VARS)
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
LOCAL_MODULE:= libandroidfw
-LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES:= $(deviceSources)
LOCAL_C_INCLUDES := \
- external/zlib \
system/core/include
LOCAL_STATIC_LIBRARIES := libziparchive libbase
LOCAL_SHARED_LIBRARIES := \
libbinder \
liblog \
libcutils \
+ libgui \
libutils \
libz
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 2dc1c96259c0..715c875d064d 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -34,9 +34,7 @@
#include <utils/String8.h>
#include <utils/threads.h>
#include <utils/Timers.h>
-#ifdef HAVE_ANDROID_OS
-#include <cutils/trace.h>
-#endif
+#include <utils/Trace.h>
#include <assert.h>
#include <dirent.h>
@@ -54,14 +52,6 @@
_rc; })
#endif
-#ifdef HAVE_ANDROID_OS
-#define MY_TRACE_BEGIN(x) ATRACE_BEGIN(x)
-#define MY_TRACE_END() ATRACE_END()
-#else
-#define MY_TRACE_BEGIN(x)
-#define MY_TRACE_END()
-#endif
-
using namespace android;
static const bool kIsDebug = false;
@@ -176,7 +166,8 @@ AssetManager::~AssetManager(void)
delete[] mVendor;
}
-bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
+bool AssetManager::addAssetPath(
+ const String8& path, int32_t* cookie, bool appAsLib, bool isSystemAsset)
{
AutoMutex _l(mLock);
@@ -222,6 +213,7 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
}
delete manifestAsset;
+ ap.isSystemAsset = isSystemAsset;
mAssetPaths.add(ap);
// new paths are always added at the end
@@ -229,16 +221,17 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
*cookie = static_cast<int32_t>(mAssetPaths.size());
}
-#ifdef HAVE_ANDROID_OS
+#ifdef __ANDROID__
// Load overlays, if any
asset_path oap;
for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {
+ oap.isSystemAsset = isSystemAsset;
mAssetPaths.add(oap);
}
#endif
if (mResources != NULL) {
- appendPathToResTable(ap);
+ appendPathToResTable(ap, appAsLib);
}
return true;
@@ -340,7 +333,7 @@ bool AssetManager::addDefaultAssets()
String8 path(root);
path.appendPath(kSystemAssets);
- return addAssetPath(path, NULL);
+ return addAssetPath(path, NULL, false /* appAsLib */, true /* isSystemAsset */);
}
int32_t AssetManager::nextAssetPath(const int32_t cookie) const
@@ -610,7 +603,7 @@ FileType AssetManager::getFileType(const char* fileName)
return kFileTypeRegular;
}
-bool AssetManager::appendPathToResTable(const asset_path& ap) const {
+bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) const {
// skip those ap's that correspond to system overlays
if (ap.isSystemOverlay) {
return true;
@@ -620,7 +613,7 @@ bool AssetManager::appendPathToResTable(const asset_path& ap) const {
ResTable* sharedRes = NULL;
bool shared = true;
bool onlyEmptyResources = true;
- MY_TRACE_BEGIN(ap.path.string());
+ ATRACE_NAME(ap.path.string());
Asset* idmap = openIdmapLocked(ap);
size_t nextEntryIdx = mResources->getTableCount();
ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
@@ -657,7 +650,7 @@ bool AssetManager::appendPathToResTable(const asset_path& ap) const {
ALOGV("Creating shared resources for %s", ap.path.string());
sharedRes = new ResTable();
sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
-#ifdef HAVE_ANDROID_OS
+#ifdef __ANDROID__
const char* data = getenv("ANDROID_DATA");
LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
String8 overlaysListPath(data);
@@ -682,10 +675,10 @@ bool AssetManager::appendPathToResTable(const asset_path& ap) const {
ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
if (sharedRes != NULL) {
ALOGV("Copying existing resources for %s", ap.path.string());
- mResources->add(sharedRes);
+ mResources->add(sharedRes, ap.isSystemAsset);
} else {
ALOGV("Parsing resources for %s", ap.path.string());
- mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
+ mResources->add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib, ap.isSystemAsset);
}
onlyEmptyResources = false;
@@ -700,8 +693,6 @@ bool AssetManager::appendPathToResTable(const asset_path& ap) const {
if (idmap != NULL) {
delete idmap;
}
- MY_TRACE_END();
-
return onlyEmptyResources;
}
@@ -749,6 +740,7 @@ const ResTable* AssetManager::getResTable(bool required) const
void AssetManager::updateResourceParamsLocked() const
{
+ ATRACE_CALL();
ResTable* res = mResources;
if (!res) {
return;
@@ -831,11 +823,11 @@ bool AssetManager::isUpToDate()
return mZipSet.isUpToDate();
}
-void AssetManager::getLocales(Vector<String8>* locales) const
+void AssetManager::getLocales(Vector<String8>* locales, bool includeSystemLocales) const
{
ResTable* res = mResources;
if (res != NULL) {
- res->getLocales(locales);
+ res->getLocales(locales, includeSystemLocales);
}
const size_t numLocales = locales->size();
@@ -1545,7 +1537,7 @@ bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMerg
*/
int dirNameLen = dirName.length();
void *iterationCookie;
- if (!pZip->startIteration(&iterationCookie)) {
+ if (!pZip->startIteration(&iterationCookie, dirName.string(), NULL)) {
ALOGW("ZipFileRO::startIteration returned false");
return false;
}
@@ -1560,9 +1552,7 @@ bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMerg
continue;
}
//printf("Comparing %s in %s?\n", nameBuf, dirName.string());
- if (dirNameLen == 0 ||
- (strncmp(nameBuf, dirName.string(), dirNameLen) == 0 &&
- nameBuf[dirNameLen] == '/'))
+ if (dirNameLen == 0 || nameBuf[dirNameLen] == '/')
{
const char* cp;
const char* nextSlash;
diff --git a/libs/androidfw/DisplayEventDispatcher.cpp b/libs/androidfw/DisplayEventDispatcher.cpp
new file mode 100644
index 000000000000..b8ef9ea49293
--- /dev/null
+++ b/libs/androidfw/DisplayEventDispatcher.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#define LOG_TAG "DisplayEventDispatcher"
+
+#include <cinttypes>
+#include <cstdint>
+
+#include <androidfw/DisplayEventDispatcher.h>
+#include <gui/DisplayEventReceiver.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+
+#include <utils/Timers.h>
+
+namespace android {
+
+// Number of events to read at a time from the DisplayEventDispatcher pipe.
+// The value should be large enough that we can quickly drain the pipe
+// using just a few large reads.
+static const size_t EVENT_BUFFER_SIZE = 100;
+
+DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper) :
+ mLooper(looper), mWaitingForVsync(false) {
+ ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this);
+}
+
+status_t DisplayEventDispatcher::initialize() {
+ status_t result = mReceiver.initCheck();
+ if (result) {
+ ALOGW("Failed to initialize display event receiver, status=%d", result);
+ return result;
+ }
+
+ int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT,
+ this, NULL);
+ if (rc < 0) {
+ return UNKNOWN_ERROR;
+ }
+ return OK;
+}
+
+void DisplayEventDispatcher::dispose() {
+ ALOGV("dispatcher %p ~ Disposing display event dispatcher.", this);
+
+ if (!mReceiver.initCheck()) {
+ mLooper->removeFd(mReceiver.getFd());
+ }
+}
+
+status_t DisplayEventDispatcher::scheduleVsync() {
+ if (!mWaitingForVsync) {
+ ALOGV("dispatcher %p ~ Scheduling vsync.", this);
+
+ // Drain all pending events.
+ nsecs_t vsyncTimestamp;
+ int32_t vsyncDisplayId;
+ uint32_t vsyncCount;
+ if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
+ ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "",
+ this, ns2ms(static_cast<nsecs_t>(vsyncTimestamp)));
+ }
+
+ status_t status = mReceiver.requestNextVsync();
+ if (status) {
+ ALOGW("Failed to request next vsync, status=%d", status);
+ return status;
+ }
+
+ mWaitingForVsync = true;
+ }
+ return OK;
+}
+
+int DisplayEventDispatcher::handleEvent(int, int events, void*) {
+ if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+ ALOGE("Display event receiver pipe was closed or an error occurred. "
+ "events=0x%x", events);
+ return 0; // remove the callback
+ }
+
+ if (!(events & Looper::EVENT_INPUT)) {
+ ALOGW("Received spurious callback for unhandled poll event. "
+ "events=0x%x", events);
+ return 1; // keep the callback
+ }
+
+ // Drain all pending events, keep the last vsync.
+ nsecs_t vsyncTimestamp;
+ int32_t vsyncDisplayId;
+ uint32_t vsyncCount;
+ if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
+ ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64 ", id=%d, count=%d",
+ this, ns2ms(vsyncTimestamp), vsyncDisplayId, vsyncCount);
+ mWaitingForVsync = false;
+ dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);
+ }
+
+ return 1; // keep the callback
+}
+
+bool DisplayEventDispatcher::processPendingEvents(
+ nsecs_t* outTimestamp, int32_t* outId, uint32_t* outCount) {
+ bool gotVsync = false;
+ DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+ ssize_t n;
+ while ((n = mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+ ALOGV("dispatcher %p ~ Read %d events.", this, int(n));
+ for (ssize_t i = 0; i < n; i++) {
+ const DisplayEventReceiver::Event& ev = buf[i];
+ switch (ev.header.type) {
+ case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
+ // Later vsync events will just overwrite the info from earlier
+ // ones. That's fine, we only care about the most recent.
+ gotVsync = true;
+ *outTimestamp = ev.header.timestamp;
+ *outId = ev.header.id;
+ *outCount = ev.vsync.count;
+ break;
+ case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
+ dispatchHotplug(ev.header.timestamp, ev.header.id, ev.hotplug.connected);
+ break;
+ default:
+ ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
+ break;
+ }
+ }
+ }
+ if (n < 0) {
+ ALOGW("Failed to get events from display event dispatcher, status=%d", status_t(n));
+ }
+ return gotVsync;
+}
+}
diff --git a/libs/androidfw/LocaleData.cpp b/libs/androidfw/LocaleData.cpp
new file mode 100644
index 000000000000..038ef5839fe2
--- /dev/null
+++ b/libs/androidfw/LocaleData.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2016 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 <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <androidfw/LocaleData.h>
+
+namespace android {
+
+#include "LocaleDataTables.cpp"
+
+inline uint32_t packLocale(const char* language, const char* region) {
+ return (((uint8_t) language[0]) << 24u) | (((uint8_t) language[1]) << 16u) |
+ (((uint8_t) region[0]) << 8u) | ((uint8_t) region[1]);
+}
+
+inline uint32_t dropRegion(uint32_t packed_locale) {
+ return packed_locale & 0xFFFF0000lu;
+}
+
+inline bool hasRegion(uint32_t packed_locale) {
+ return (packed_locale & 0x0000FFFFlu) != 0;
+}
+
+const size_t SCRIPT_LENGTH = 4;
+const size_t SCRIPT_PARENTS_COUNT = sizeof(SCRIPT_PARENTS)/sizeof(SCRIPT_PARENTS[0]);
+const uint32_t PACKED_ROOT = 0; // to represent the root locale
+
+uint32_t findParent(uint32_t packed_locale, const char* script) {
+ if (hasRegion(packed_locale)) {
+ for (size_t i = 0; i < SCRIPT_PARENTS_COUNT; i++) {
+ if (memcmp(script, SCRIPT_PARENTS[i].script, SCRIPT_LENGTH) == 0) {
+ auto map = SCRIPT_PARENTS[i].map;
+ auto lookup_result = map->find(packed_locale);
+ if (lookup_result != map->end()) {
+ return lookup_result->second;
+ }
+ break;
+ }
+ }
+ return dropRegion(packed_locale);
+ }
+ return PACKED_ROOT;
+}
+
+// Find the ancestors of a locale, and fill 'out' with it (assumes out has enough
+// space). If any of the members of stop_list was seen, write it in the
+// output but stop afterwards.
+//
+// This also outputs the index of the last written ancestor in the stop_list
+// to stop_list_index, which will be -1 if it is not found in the stop_list.
+//
+// Returns the number of ancestors written in the output, which is always
+// at least one.
+//
+// (If 'out' is nullptr, we do everything the same way but we simply don't write
+// any results in 'out'.)
+size_t findAncestors(uint32_t* out, ssize_t* stop_list_index,
+ uint32_t packed_locale, const char* script,
+ const uint32_t* stop_list, size_t stop_set_length) {
+ uint32_t ancestor = packed_locale;
+ size_t count = 0;
+ do {
+ if (out != nullptr) out[count] = ancestor;
+ count++;
+ for (size_t i = 0; i < stop_set_length; i++) {
+ if (stop_list[i] == ancestor) {
+ *stop_list_index = (ssize_t) i;
+ return count;
+ }
+ }
+ ancestor = findParent(ancestor, script);
+ } while (ancestor != PACKED_ROOT);
+ *stop_list_index = (ssize_t) -1;
+ return count;
+}
+
+size_t findDistance(uint32_t supported,
+ const char* script,
+ const uint32_t* request_ancestors,
+ size_t request_ancestors_count) {
+ ssize_t request_ancestors_index;
+ const size_t supported_ancestor_count = findAncestors(
+ nullptr, &request_ancestors_index,
+ supported, script,
+ request_ancestors, request_ancestors_count);
+ // Since both locales share the same root, there will always be a shared
+ // ancestor, so the distance in the parent tree is the sum of the distance
+ // of 'supported' to the lowest common ancestor (number of ancestors
+ // written for 'supported' minus 1) plus the distance of 'request' to the
+ // lowest common ancestor (the index of the ancestor in request_ancestors).
+ return supported_ancestor_count + request_ancestors_index - 1;
+}
+
+inline bool isRepresentative(uint32_t language_and_region, const char* script) {
+ const uint64_t packed_locale = (
+ (((uint64_t) language_and_region) << 32u) |
+ (((uint64_t) script[0]) << 24u) |
+ (((uint64_t) script[1]) << 16u) |
+ (((uint64_t) script[2]) << 8u) |
+ ((uint64_t) script[3]));
+
+ return (REPRESENTATIVE_LOCALES.count(packed_locale) != 0);
+}
+
+int localeDataCompareRegions(
+ const char* left_region, const char* right_region,
+ const char* requested_language, const char* requested_script,
+ const char* requested_region) {
+
+ if (left_region[0] == right_region[0] && left_region[1] == right_region[1]) {
+ return 0;
+ }
+ const uint32_t left = packLocale(requested_language, left_region);
+ const uint32_t right = packLocale(requested_language, right_region);
+ const uint32_t request = packLocale(requested_language, requested_region);
+
+ uint32_t request_ancestors[MAX_PARENT_DEPTH+1];
+ ssize_t left_right_index;
+ // Find the parents of the request, but stop as soon as we saw left or right
+ const uint32_t left_and_right[] = {left, right};
+ const size_t ancestor_count = findAncestors(
+ request_ancestors, &left_right_index,
+ request, requested_script,
+ left_and_right, sizeof(left_and_right)/sizeof(left_and_right[0]));
+ if (left_right_index == 0) { // We saw left earlier
+ return 1;
+ }
+ if (left_right_index == 1) { // We saw right earlier
+ return -1;
+ }
+
+ // If we are here, neither left nor right are an ancestor of the
+ // request. This means that all the ancestors have been computed and
+ // the last ancestor is just the language by itself. We will use the
+ // distance in the parent tree for determining the better match.
+ const size_t left_distance = findDistance(
+ left, requested_script, request_ancestors, ancestor_count);
+ const size_t right_distance = findDistance(
+ right, requested_script, request_ancestors, ancestor_count);
+ if (left_distance != right_distance) {
+ return (int) right_distance - (int) left_distance; // smaller distance is better
+ }
+
+ // If we are here, left and right are equidistant from the request. We will
+ // try and see if any of them is a representative locale.
+ const bool left_is_representative = isRepresentative(left, requested_script);
+ const bool right_is_representative = isRepresentative(right, requested_script);
+ if (left_is_representative != right_is_representative) {
+ return (int) left_is_representative - (int) right_is_representative;
+ }
+
+ // We have no way of figuring out which locale is a better match. For
+ // the sake of stability, we consider the locale with the lower region
+ // code (in dictionary order) better, with two-letter codes before
+ // three-digit codes (since two-letter codes are more specific).
+ return (int64_t) right - (int64_t) left;
+}
+
+void localeDataComputeScript(char out[4], const char* language, const char* region) {
+ if (language[0] == '\0') {
+ memset(out, '\0', SCRIPT_LENGTH);
+ return;
+ }
+ uint32_t lookup_key = packLocale(language, region);
+ auto lookup_result = LIKELY_SCRIPTS.find(lookup_key);
+ if (lookup_result == LIKELY_SCRIPTS.end()) {
+ // We couldn't find the locale. Let's try without the region
+ if (region[0] != '\0') {
+ lookup_key = dropRegion(lookup_key);
+ lookup_result = LIKELY_SCRIPTS.find(lookup_key);
+ if (lookup_result != LIKELY_SCRIPTS.end()) {
+ memcpy(out, SCRIPT_CODES[lookup_result->second], SCRIPT_LENGTH);
+ return;
+ }
+ }
+ // We don't know anything about the locale
+ memset(out, '\0', SCRIPT_LENGTH);
+ return;
+ } else {
+ // We found the locale.
+ memcpy(out, SCRIPT_CODES[lookup_result->second], SCRIPT_LENGTH);
+ }
+}
+
+const uint32_t ENGLISH_STOP_LIST[2] = {
+ 0x656E0000lu, // en
+ 0x656E8400lu, // en-001
+};
+const char ENGLISH_CHARS[2] = {'e', 'n'};
+const char LATIN_CHARS[4] = {'L', 'a', 't', 'n'};
+
+bool localeDataIsCloseToUsEnglish(const char* region) {
+ const uint32_t locale = packLocale(ENGLISH_CHARS, region);
+ ssize_t stop_list_index;
+ findAncestors(nullptr, &stop_list_index, locale, LATIN_CHARS, ENGLISH_STOP_LIST, 2);
+ // A locale is like US English if we see "en" before "en-001" in its ancestor list.
+ return stop_list_index == 0; // 'en' is first in ENGLISH_STOP_LIST
+}
+
+} // namespace android
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
new file mode 100644
index 000000000000..1ac508525061
--- /dev/null
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -0,0 +1,1701 @@
+// Auto-generated by frameworks/base/tools/localedata/extract_icu_data.py
+
+const char SCRIPT_CODES[][4] = {
+ /* 0 */ {'A', 'h', 'o', 'm'},
+ /* 1 */ {'A', 'r', 'a', 'b'},
+ /* 2 */ {'A', 'r', 'm', 'i'},
+ /* 3 */ {'A', 'r', 'm', 'n'},
+ /* 4 */ {'A', 'v', 's', 't'},
+ /* 5 */ {'B', 'a', 'm', 'u'},
+ /* 6 */ {'B', 'a', 's', 's'},
+ /* 7 */ {'B', 'e', 'n', 'g'},
+ /* 8 */ {'B', 'r', 'a', 'h'},
+ /* 9 */ {'C', 'a', 'n', 's'},
+ /* 10 */ {'C', 'a', 'r', 'i'},
+ /* 11 */ {'C', 'h', 'a', 'm'},
+ /* 12 */ {'C', 'h', 'e', 'r'},
+ /* 13 */ {'C', 'o', 'p', 't'},
+ /* 14 */ {'C', 'p', 'r', 't'},
+ /* 15 */ {'C', 'y', 'r', 'l'},
+ /* 16 */ {'D', 'e', 'v', 'a'},
+ /* 17 */ {'E', 'g', 'y', 'p'},
+ /* 18 */ {'E', 't', 'h', 'i'},
+ /* 19 */ {'G', 'e', 'o', 'r'},
+ /* 20 */ {'G', 'o', 't', 'h'},
+ /* 21 */ {'G', 'r', 'e', 'k'},
+ /* 22 */ {'G', 'u', 'j', 'r'},
+ /* 23 */ {'G', 'u', 'r', 'u'},
+ /* 24 */ {'H', 'a', 'n', 's'},
+ /* 25 */ {'H', 'a', 'n', 't'},
+ /* 26 */ {'H', 'a', 't', 'r'},
+ /* 27 */ {'H', 'e', 'b', 'r'},
+ /* 28 */ {'H', 'l', 'u', 'w'},
+ /* 29 */ {'H', 'm', 'n', 'g'},
+ /* 30 */ {'I', 't', 'a', 'l'},
+ /* 31 */ {'J', 'p', 'a', 'n'},
+ /* 32 */ {'K', 'a', 'l', 'i'},
+ /* 33 */ {'K', 'a', 'n', 'a'},
+ /* 34 */ {'K', 'h', 'a', 'r'},
+ /* 35 */ {'K', 'h', 'm', 'r'},
+ /* 36 */ {'K', 'n', 'd', 'a'},
+ /* 37 */ {'K', 'o', 'r', 'e'},
+ /* 38 */ {'K', 't', 'h', 'i'},
+ /* 39 */ {'L', 'a', 'n', 'a'},
+ /* 40 */ {'L', 'a', 'o', 'o'},
+ /* 41 */ {'L', 'a', 't', 'n'},
+ /* 42 */ {'L', 'e', 'p', 'c'},
+ /* 43 */ {'L', 'i', 'n', 'a'},
+ /* 44 */ {'L', 'i', 's', 'u'},
+ /* 45 */ {'L', 'y', 'c', 'i'},
+ /* 46 */ {'L', 'y', 'd', 'i'},
+ /* 47 */ {'M', 'a', 'n', 'd'},
+ /* 48 */ {'M', 'a', 'n', 'i'},
+ /* 49 */ {'M', 'e', 'r', 'c'},
+ /* 50 */ {'M', 'l', 'y', 'm'},
+ /* 51 */ {'M', 'o', 'n', 'g'},
+ /* 52 */ {'M', 'r', 'o', 'o'},
+ /* 53 */ {'M', 'y', 'm', 'r'},
+ /* 54 */ {'N', 'a', 'r', 'b'},
+ /* 55 */ {'N', 'k', 'o', 'o'},
+ /* 56 */ {'O', 'g', 'a', 'm'},
+ /* 57 */ {'O', 'r', 'k', 'h'},
+ /* 58 */ {'O', 'r', 'y', 'a'},
+ /* 59 */ {'P', 'a', 'u', 'c'},
+ /* 60 */ {'P', 'h', 'l', 'i'},
+ /* 61 */ {'P', 'h', 'n', 'x'},
+ /* 62 */ {'P', 'l', 'r', 'd'},
+ /* 63 */ {'P', 'r', 't', 'i'},
+ /* 64 */ {'R', 'u', 'n', 'r'},
+ /* 65 */ {'S', 'a', 'm', 'r'},
+ /* 66 */ {'S', 'a', 'r', 'b'},
+ /* 67 */ {'S', 'a', 'u', 'r'},
+ /* 68 */ {'S', 'g', 'n', 'w'},
+ /* 69 */ {'S', 'i', 'n', 'h'},
+ /* 70 */ {'S', 'o', 'r', 'a'},
+ /* 71 */ {'S', 'y', 'r', 'c'},
+ /* 72 */ {'T', 'a', 'l', 'e'},
+ /* 73 */ {'T', 'a', 'l', 'u'},
+ /* 74 */ {'T', 'a', 'm', 'l'},
+ /* 75 */ {'T', 'a', 'v', 't'},
+ /* 76 */ {'T', 'e', 'l', 'u'},
+ /* 77 */ {'T', 'f', 'n', 'g'},
+ /* 78 */ {'T', 'h', 'a', 'a'},
+ /* 79 */ {'T', 'h', 'a', 'i'},
+ /* 80 */ {'T', 'i', 'b', 't'},
+ /* 81 */ {'U', 'g', 'a', 'r'},
+ /* 82 */ {'V', 'a', 'i', 'i'},
+ /* 83 */ {'X', 'p', 'e', 'o'},
+ /* 84 */ {'X', 's', 'u', 'x'},
+ /* 85 */ {'Y', 'i', 'i', 'i'},
+ /* 86 */ {'~', '~', '~', 'A'},
+ /* 87 */ {'~', '~', '~', 'B'},
+};
+
+
+const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
+ {0x61610000u, 41u}, // aa -> Latn
+ {0x61620000u, 15u}, // ab -> Cyrl
+ {0xC4200000u, 41u}, // abr -> Latn
+ {0x90400000u, 41u}, // ace -> Latn
+ {0x9C400000u, 41u}, // ach -> Latn
+ {0x80600000u, 41u}, // ada -> Latn
+ {0xE0600000u, 15u}, // ady -> Cyrl
+ {0x61650000u, 4u}, // ae -> Avst
+ {0x84800000u, 1u}, // aeb -> Arab
+ {0x61660000u, 41u}, // af -> Latn
+ {0xC0C00000u, 41u}, // agq -> Latn
+ {0xB8E00000u, 0u}, // aho -> Ahom
+ {0x616B0000u, 41u}, // ak -> Latn
+ {0xA9400000u, 84u}, // akk -> Xsux
+ {0xB5600000u, 41u}, // aln -> Latn
+ {0xCD600000u, 15u}, // alt -> Cyrl
+ {0x616D0000u, 18u}, // am -> Ethi
+ {0xB9800000u, 41u}, // amo -> Latn
+ {0xE5C00000u, 41u}, // aoz -> Latn
+ {0x61720000u, 1u}, // ar -> Arab
+ {0x61725842u, 87u}, // ar-XB -> ~~~B
+ {0x8A200000u, 2u}, // arc -> Armi
+ {0xB6200000u, 41u}, // arn -> Latn
+ {0xBA200000u, 41u}, // aro -> Latn
+ {0xC2200000u, 1u}, // arq -> Arab
+ {0xE2200000u, 1u}, // ary -> Arab
+ {0xE6200000u, 1u}, // arz -> Arab
+ {0x61730000u, 7u}, // as -> Beng
+ {0x82400000u, 41u}, // asa -> Latn
+ {0x92400000u, 68u}, // ase -> Sgnw
+ {0xCE400000u, 41u}, // ast -> Latn
+ {0xA6600000u, 41u}, // atj -> Latn
+ {0x61760000u, 15u}, // av -> Cyrl
+ {0x82C00000u, 16u}, // awa -> Deva
+ {0x61790000u, 41u}, // ay -> Latn
+ {0x617A0000u, 41u}, // az -> Latn
+ {0x617A4951u, 1u}, // az-IQ -> Arab
+ {0x617A4952u, 1u}, // az-IR -> Arab
+ {0x617A5255u, 15u}, // az-RU -> Cyrl
+ {0x62610000u, 15u}, // ba -> Cyrl
+ {0xAC010000u, 1u}, // bal -> Arab
+ {0xB4010000u, 41u}, // ban -> Latn
+ {0xBC010000u, 16u}, // bap -> Deva
+ {0xC4010000u, 41u}, // bar -> Latn
+ {0xC8010000u, 41u}, // bas -> Latn
+ {0xDC010000u, 5u}, // bax -> Bamu
+ {0x88210000u, 41u}, // bbc -> Latn
+ {0xA4210000u, 41u}, // bbj -> Latn
+ {0xA0410000u, 41u}, // bci -> Latn
+ {0x62650000u, 15u}, // be -> Cyrl
+ {0xA4810000u, 1u}, // bej -> Arab
+ {0xB0810000u, 41u}, // bem -> Latn
+ {0xD8810000u, 41u}, // bew -> Latn
+ {0xE4810000u, 41u}, // bez -> Latn
+ {0x8CA10000u, 41u}, // bfd -> Latn
+ {0xC0A10000u, 74u}, // bfq -> Taml
+ {0xCCA10000u, 1u}, // bft -> Arab
+ {0xE0A10000u, 16u}, // bfy -> Deva
+ {0x62670000u, 15u}, // bg -> Cyrl
+ {0x88C10000u, 16u}, // bgc -> Deva
+ {0xB4C10000u, 1u}, // bgn -> Arab
+ {0xDCC10000u, 21u}, // bgx -> Grek
+ {0x62680000u, 38u}, // bh -> Kthi
+ {0x84E10000u, 16u}, // bhb -> Deva
+ {0xA0E10000u, 16u}, // bhi -> Deva
+ {0xA8E10000u, 41u}, // bhk -> Latn
+ {0xB8E10000u, 16u}, // bho -> Deva
+ {0x62690000u, 41u}, // bi -> Latn
+ {0xA9010000u, 41u}, // bik -> Latn
+ {0xB5010000u, 41u}, // bin -> Latn
+ {0xA5210000u, 16u}, // bjj -> Deva
+ {0xB5210000u, 41u}, // bjn -> Latn
+ {0xB1410000u, 41u}, // bkm -> Latn
+ {0xD1410000u, 41u}, // bku -> Latn
+ {0xCD610000u, 75u}, // blt -> Tavt
+ {0x626D0000u, 41u}, // bm -> Latn
+ {0xC1810000u, 41u}, // bmq -> Latn
+ {0x626E0000u, 7u}, // bn -> Beng
+ {0x626F0000u, 80u}, // bo -> Tibt
+ {0xE1E10000u, 7u}, // bpy -> Beng
+ {0xA2010000u, 1u}, // bqi -> Arab
+ {0xD6010000u, 41u}, // bqv -> Latn
+ {0x62720000u, 41u}, // br -> Latn
+ {0x82210000u, 16u}, // bra -> Deva
+ {0x9E210000u, 1u}, // brh -> Arab
+ {0xDE210000u, 16u}, // brx -> Deva
+ {0x62730000u, 41u}, // bs -> Latn
+ {0xC2410000u, 6u}, // bsq -> Bass
+ {0xCA410000u, 41u}, // bss -> Latn
+ {0xBA610000u, 41u}, // bto -> Latn
+ {0xD6610000u, 16u}, // btv -> Deva
+ {0x82810000u, 15u}, // bua -> Cyrl
+ {0x8A810000u, 41u}, // buc -> Latn
+ {0x9A810000u, 41u}, // bug -> Latn
+ {0xB2810000u, 41u}, // bum -> Latn
+ {0x86A10000u, 41u}, // bvb -> Latn
+ {0xB7010000u, 18u}, // byn -> Ethi
+ {0xD7010000u, 41u}, // byv -> Latn
+ {0x93210000u, 41u}, // bze -> Latn
+ {0x63610000u, 41u}, // ca -> Latn
+ {0x9C420000u, 41u}, // cch -> Latn
+ {0xBC420000u, 7u}, // ccp -> Beng
+ {0x63650000u, 15u}, // ce -> Cyrl
+ {0x84820000u, 41u}, // ceb -> Latn
+ {0x98C20000u, 41u}, // cgg -> Latn
+ {0x63680000u, 41u}, // ch -> Latn
+ {0xA8E20000u, 41u}, // chk -> Latn
+ {0xB0E20000u, 15u}, // chm -> Cyrl
+ {0xB8E20000u, 41u}, // cho -> Latn
+ {0xBCE20000u, 41u}, // chp -> Latn
+ {0xC4E20000u, 12u}, // chr -> Cher
+ {0x81220000u, 1u}, // cja -> Arab
+ {0xB1220000u, 11u}, // cjm -> Cham
+ {0x85420000u, 1u}, // ckb -> Arab
+ {0x636F0000u, 41u}, // co -> Latn
+ {0xBDC20000u, 13u}, // cop -> Copt
+ {0xC9E20000u, 41u}, // cps -> Latn
+ {0x63720000u, 9u}, // cr -> Cans
+ {0xA6220000u, 9u}, // crj -> Cans
+ {0xAA220000u, 9u}, // crk -> Cans
+ {0xAE220000u, 9u}, // crl -> Cans
+ {0xB2220000u, 9u}, // crm -> Cans
+ {0xCA220000u, 41u}, // crs -> Latn
+ {0x63730000u, 41u}, // cs -> Latn
+ {0x86420000u, 41u}, // csb -> Latn
+ {0xDA420000u, 9u}, // csw -> Cans
+ {0x8E620000u, 59u}, // ctd -> Pauc
+ {0x63750000u, 15u}, // cu -> Cyrl
+ {0x63760000u, 15u}, // cv -> Cyrl
+ {0x63790000u, 41u}, // cy -> Latn
+ {0x64610000u, 41u}, // da -> Latn
+ {0xA8030000u, 41u}, // dak -> Latn
+ {0xC4030000u, 15u}, // dar -> Cyrl
+ {0xD4030000u, 41u}, // dav -> Latn
+ {0x88430000u, 1u}, // dcc -> Arab
+ {0x64650000u, 41u}, // de -> Latn
+ {0xB4830000u, 41u}, // den -> Latn
+ {0xC4C30000u, 41u}, // dgr -> Latn
+ {0x91230000u, 41u}, // dje -> Latn
+ {0xA5A30000u, 41u}, // dnj -> Latn
+ {0xA1C30000u, 1u}, // doi -> Arab
+ {0x86430000u, 41u}, // dsb -> Latn
+ {0xB2630000u, 41u}, // dtm -> Latn
+ {0xBE630000u, 41u}, // dtp -> Latn
+ {0x82830000u, 41u}, // dua -> Latn
+ {0x64760000u, 78u}, // dv -> Thaa
+ {0xBB030000u, 41u}, // dyo -> Latn
+ {0xD3030000u, 41u}, // dyu -> Latn
+ {0x647A0000u, 80u}, // dz -> Tibt
+ {0xD0240000u, 41u}, // ebu -> Latn
+ {0x65650000u, 41u}, // ee -> Latn
+ {0xA0A40000u, 41u}, // efi -> Latn
+ {0xACC40000u, 41u}, // egl -> Latn
+ {0xE0C40000u, 17u}, // egy -> Egyp
+ {0xE1440000u, 32u}, // eky -> Kali
+ {0x656C0000u, 21u}, // el -> Grek
+ {0x656E0000u, 41u}, // en -> Latn
+ {0x656E5841u, 86u}, // en-XA -> ~~~A
+ {0x656F0000u, 41u}, // eo -> Latn
+ {0x65730000u, 41u}, // es -> Latn
+ {0xD2440000u, 41u}, // esu -> Latn
+ {0x65740000u, 41u}, // et -> Latn
+ {0xCE640000u, 30u}, // ett -> Ital
+ {0x65750000u, 41u}, // eu -> Latn
+ {0xBAC40000u, 41u}, // ewo -> Latn
+ {0xCEE40000u, 41u}, // ext -> Latn
+ {0x66610000u, 1u}, // fa -> Arab
+ {0xB4050000u, 41u}, // fan -> Latn
+ {0x66660000u, 41u}, // ff -> Latn
+ {0xB0A50000u, 41u}, // ffm -> Latn
+ {0x66690000u, 41u}, // fi -> Latn
+ {0x81050000u, 1u}, // fia -> Arab
+ {0xAD050000u, 41u}, // fil -> Latn
+ {0xCD050000u, 41u}, // fit -> Latn
+ {0x666A0000u, 41u}, // fj -> Latn
+ {0x666F0000u, 41u}, // fo -> Latn
+ {0xB5C50000u, 41u}, // fon -> Latn
+ {0x66720000u, 41u}, // fr -> Latn
+ {0x8A250000u, 41u}, // frc -> Latn
+ {0xBE250000u, 41u}, // frp -> Latn
+ {0xC6250000u, 41u}, // frr -> Latn
+ {0xCA250000u, 41u}, // frs -> Latn
+ {0x8E850000u, 41u}, // fud -> Latn
+ {0xC2850000u, 41u}, // fuq -> Latn
+ {0xC6850000u, 41u}, // fur -> Latn
+ {0xD6850000u, 41u}, // fuv -> Latn
+ {0xC6A50000u, 41u}, // fvr -> Latn
+ {0x66790000u, 41u}, // fy -> Latn
+ {0x67610000u, 41u}, // ga -> Latn
+ {0x80060000u, 41u}, // gaa -> Latn
+ {0x98060000u, 41u}, // gag -> Latn
+ {0xB4060000u, 24u}, // gan -> Hans
+ {0xE0060000u, 41u}, // gay -> Latn
+ {0xB0260000u, 16u}, // gbm -> Deva
+ {0xE4260000u, 1u}, // gbz -> Arab
+ {0xC4460000u, 41u}, // gcr -> Latn
+ {0x67640000u, 41u}, // gd -> Latn
+ {0xE4860000u, 18u}, // gez -> Ethi
+ {0xB4C60000u, 16u}, // ggn -> Deva
+ {0xAD060000u, 41u}, // gil -> Latn
+ {0xA9260000u, 1u}, // gjk -> Arab
+ {0xD1260000u, 1u}, // gju -> Arab
+ {0x676C0000u, 41u}, // gl -> Latn
+ {0xA9660000u, 1u}, // glk -> Arab
+ {0x676E0000u, 41u}, // gn -> Latn
+ {0xB1C60000u, 16u}, // gom -> Deva
+ {0xB5C60000u, 76u}, // gon -> Telu
+ {0xC5C60000u, 41u}, // gor -> Latn
+ {0xC9C60000u, 41u}, // gos -> Latn
+ {0xCDC60000u, 20u}, // got -> Goth
+ {0x8A260000u, 14u}, // grc -> Cprt
+ {0xCE260000u, 7u}, // grt -> Beng
+ {0xDA460000u, 41u}, // gsw -> Latn
+ {0x67750000u, 22u}, // gu -> Gujr
+ {0x86860000u, 41u}, // gub -> Latn
+ {0x8A860000u, 41u}, // guc -> Latn
+ {0xC6860000u, 41u}, // gur -> Latn
+ {0xE6860000u, 41u}, // guz -> Latn
+ {0x67760000u, 41u}, // gv -> Latn
+ {0xC6A60000u, 16u}, // gvr -> Deva
+ {0xA2C60000u, 41u}, // gwi -> Latn
+ {0x68610000u, 41u}, // ha -> Latn
+ {0x6861434Du, 1u}, // ha-CM -> Arab
+ {0x68615344u, 1u}, // ha-SD -> Arab
+ {0xA8070000u, 24u}, // hak -> Hans
+ {0xD8070000u, 41u}, // haw -> Latn
+ {0xE4070000u, 1u}, // haz -> Arab
+ {0x68650000u, 27u}, // he -> Hebr
+ {0x68690000u, 16u}, // hi -> Deva
+ {0x95070000u, 41u}, // hif -> Latn
+ {0xAD070000u, 41u}, // hil -> Latn
+ {0xD1670000u, 28u}, // hlu -> Hluw
+ {0x8D870000u, 62u}, // hmd -> Plrd
+ {0x8DA70000u, 1u}, // hnd -> Arab
+ {0x91A70000u, 16u}, // hne -> Deva
+ {0xA5A70000u, 29u}, // hnj -> Hmng
+ {0xB5A70000u, 41u}, // hnn -> Latn
+ {0xB9A70000u, 1u}, // hno -> Arab
+ {0x686F0000u, 41u}, // ho -> Latn
+ {0x89C70000u, 16u}, // hoc -> Deva
+ {0xA5C70000u, 16u}, // hoj -> Deva
+ {0x68720000u, 41u}, // hr -> Latn
+ {0x86470000u, 41u}, // hsb -> Latn
+ {0xB6470000u, 24u}, // hsn -> Hans
+ {0x68740000u, 41u}, // ht -> Latn
+ {0x68750000u, 41u}, // hu -> Latn
+ {0x68790000u, 3u}, // hy -> Armn
+ {0x687A0000u, 41u}, // hz -> Latn
+ {0x69610000u, 41u}, // ia -> Latn
+ {0x80280000u, 41u}, // iba -> Latn
+ {0x84280000u, 41u}, // ibb -> Latn
+ {0x69640000u, 41u}, // id -> Latn
+ {0x69670000u, 41u}, // ig -> Latn
+ {0x69690000u, 85u}, // ii -> Yiii
+ {0x696B0000u, 41u}, // ik -> Latn
+ {0xCD480000u, 41u}, // ikt -> Latn
+ {0xB9680000u, 41u}, // ilo -> Latn
+ {0x696E0000u, 41u}, // in -> Latn
+ {0x9DA80000u, 15u}, // inh -> Cyrl
+ {0x69730000u, 41u}, // is -> Latn
+ {0x69740000u, 41u}, // it -> Latn
+ {0x69750000u, 9u}, // iu -> Cans
+ {0x69770000u, 27u}, // iw -> Hebr
+ {0x9F280000u, 41u}, // izh -> Latn
+ {0x6A610000u, 31u}, // ja -> Jpan
+ {0xB0090000u, 41u}, // jam -> Latn
+ {0xB8C90000u, 41u}, // jgo -> Latn
+ {0x6A690000u, 27u}, // ji -> Hebr
+ {0x89890000u, 41u}, // jmc -> Latn
+ {0xAD890000u, 16u}, // jml -> Deva
+ {0xCE890000u, 41u}, // jut -> Latn
+ {0x6A760000u, 41u}, // jv -> Latn
+ {0x6A770000u, 41u}, // jw -> Latn
+ {0x6B610000u, 19u}, // ka -> Geor
+ {0x800A0000u, 15u}, // kaa -> Cyrl
+ {0x840A0000u, 41u}, // kab -> Latn
+ {0x880A0000u, 41u}, // kac -> Latn
+ {0xA40A0000u, 41u}, // kaj -> Latn
+ {0xB00A0000u, 41u}, // kam -> Latn
+ {0xB80A0000u, 41u}, // kao -> Latn
+ {0x8C2A0000u, 15u}, // kbd -> Cyrl
+ {0x984A0000u, 41u}, // kcg -> Latn
+ {0xA84A0000u, 41u}, // kck -> Latn
+ {0x906A0000u, 41u}, // kde -> Latn
+ {0xCC6A0000u, 79u}, // kdt -> Thai
+ {0x808A0000u, 41u}, // kea -> Latn
+ {0xB48A0000u, 41u}, // ken -> Latn
+ {0xB8AA0000u, 41u}, // kfo -> Latn
+ {0xC4AA0000u, 16u}, // kfr -> Deva
+ {0xE0AA0000u, 16u}, // kfy -> Deva
+ {0x6B670000u, 41u}, // kg -> Latn
+ {0x90CA0000u, 41u}, // kge -> Latn
+ {0xBCCA0000u, 41u}, // kgp -> Latn
+ {0x80EA0000u, 41u}, // kha -> Latn
+ {0x84EA0000u, 73u}, // khb -> Talu
+ {0xB4EA0000u, 16u}, // khn -> Deva
+ {0xC0EA0000u, 41u}, // khq -> Latn
+ {0xCCEA0000u, 53u}, // kht -> Mymr
+ {0xD8EA0000u, 1u}, // khw -> Arab
+ {0x6B690000u, 41u}, // ki -> Latn
+ {0xD10A0000u, 41u}, // kiu -> Latn
+ {0x6B6A0000u, 41u}, // kj -> Latn
+ {0x992A0000u, 40u}, // kjg -> Laoo
+ {0x6B6B0000u, 15u}, // kk -> Cyrl
+ {0x6B6B4146u, 1u}, // kk-AF -> Arab
+ {0x6B6B434Eu, 1u}, // kk-CN -> Arab
+ {0x6B6B4952u, 1u}, // kk-IR -> Arab
+ {0x6B6B4D4Eu, 1u}, // kk-MN -> Arab
+ {0xA54A0000u, 41u}, // kkj -> Latn
+ {0x6B6C0000u, 41u}, // kl -> Latn
+ {0xB56A0000u, 41u}, // kln -> Latn
+ {0x6B6D0000u, 35u}, // km -> Khmr
+ {0x858A0000u, 41u}, // kmb -> Latn
+ {0x6B6E0000u, 36u}, // kn -> Knda
+ {0x6B6F0000u, 37u}, // ko -> Kore
+ {0xA1CA0000u, 15u}, // koi -> Cyrl
+ {0xA9CA0000u, 16u}, // kok -> Deva
+ {0xC9CA0000u, 41u}, // kos -> Latn
+ {0x91EA0000u, 41u}, // kpe -> Latn
+ {0x8A2A0000u, 15u}, // krc -> Cyrl
+ {0xA22A0000u, 41u}, // kri -> Latn
+ {0xA62A0000u, 41u}, // krj -> Latn
+ {0xAE2A0000u, 41u}, // krl -> Latn
+ {0xD22A0000u, 16u}, // kru -> Deva
+ {0x6B730000u, 1u}, // ks -> Arab
+ {0x864A0000u, 41u}, // ksb -> Latn
+ {0x964A0000u, 41u}, // ksf -> Latn
+ {0x9E4A0000u, 41u}, // ksh -> Latn
+ {0x6B750000u, 41u}, // ku -> Latn
+ {0x6B754952u, 1u}, // ku-IR -> Arab
+ {0x6B754C42u, 1u}, // ku-LB -> Arab
+ {0xB28A0000u, 15u}, // kum -> Cyrl
+ {0x6B760000u, 15u}, // kv -> Cyrl
+ {0xC6AA0000u, 41u}, // kvr -> Latn
+ {0xDEAA0000u, 1u}, // kvx -> Arab
+ {0x6B770000u, 41u}, // kw -> Latn
+ {0xB2EA0000u, 79u}, // kxm -> Thai
+ {0xBEEA0000u, 1u}, // kxp -> Arab
+ {0x6B790000u, 15u}, // ky -> Cyrl
+ {0x6B79434Eu, 1u}, // ky-CN -> Arab
+ {0x6B795452u, 41u}, // ky-TR -> Latn
+ {0x6C610000u, 41u}, // la -> Latn
+ {0x840B0000u, 43u}, // lab -> Lina
+ {0x8C0B0000u, 27u}, // lad -> Hebr
+ {0x980B0000u, 41u}, // lag -> Latn
+ {0x9C0B0000u, 1u}, // lah -> Arab
+ {0xA40B0000u, 41u}, // laj -> Latn
+ {0x6C620000u, 41u}, // lb -> Latn
+ {0x902B0000u, 15u}, // lbe -> Cyrl
+ {0xD82B0000u, 41u}, // lbw -> Latn
+ {0xBC4B0000u, 79u}, // lcp -> Thai
+ {0xBC8B0000u, 42u}, // lep -> Lepc
+ {0xE48B0000u, 15u}, // lez -> Cyrl
+ {0x6C670000u, 41u}, // lg -> Latn
+ {0x6C690000u, 41u}, // li -> Latn
+ {0x950B0000u, 16u}, // lif -> Deva
+ {0xA50B0000u, 41u}, // lij -> Latn
+ {0xC90B0000u, 44u}, // lis -> Lisu
+ {0xBD2B0000u, 41u}, // ljp -> Latn
+ {0xA14B0000u, 1u}, // lki -> Arab
+ {0xCD4B0000u, 41u}, // lkt -> Latn
+ {0xB58B0000u, 76u}, // lmn -> Telu
+ {0xB98B0000u, 41u}, // lmo -> Latn
+ {0x6C6E0000u, 41u}, // ln -> Latn
+ {0x6C6F0000u, 40u}, // lo -> Laoo
+ {0xADCB0000u, 41u}, // lol -> Latn
+ {0xE5CB0000u, 41u}, // loz -> Latn
+ {0x8A2B0000u, 1u}, // lrc -> Arab
+ {0x6C740000u, 41u}, // lt -> Latn
+ {0x9A6B0000u, 41u}, // ltg -> Latn
+ {0x6C750000u, 41u}, // lu -> Latn
+ {0x828B0000u, 41u}, // lua -> Latn
+ {0xBA8B0000u, 41u}, // luo -> Latn
+ {0xE28B0000u, 41u}, // luy -> Latn
+ {0xE68B0000u, 1u}, // luz -> Arab
+ {0x6C760000u, 41u}, // lv -> Latn
+ {0xAECB0000u, 79u}, // lwl -> Thai
+ {0x9F2B0000u, 24u}, // lzh -> Hans
+ {0xE72B0000u, 41u}, // lzz -> Latn
+ {0x8C0C0000u, 41u}, // mad -> Latn
+ {0x940C0000u, 41u}, // maf -> Latn
+ {0x980C0000u, 16u}, // mag -> Deva
+ {0xA00C0000u, 16u}, // mai -> Deva
+ {0xA80C0000u, 41u}, // mak -> Latn
+ {0xB40C0000u, 41u}, // man -> Latn
+ {0xB40C474Eu, 55u}, // man-GN -> Nkoo
+ {0xC80C0000u, 41u}, // mas -> Latn
+ {0xE40C0000u, 41u}, // maz -> Latn
+ {0x946C0000u, 15u}, // mdf -> Cyrl
+ {0x9C6C0000u, 41u}, // mdh -> Latn
+ {0xC46C0000u, 41u}, // mdr -> Latn
+ {0xB48C0000u, 41u}, // men -> Latn
+ {0xC48C0000u, 41u}, // mer -> Latn
+ {0x80AC0000u, 1u}, // mfa -> Arab
+ {0x90AC0000u, 41u}, // mfe -> Latn
+ {0x6D670000u, 41u}, // mg -> Latn
+ {0x9CCC0000u, 41u}, // mgh -> Latn
+ {0xB8CC0000u, 41u}, // mgo -> Latn
+ {0xBCCC0000u, 16u}, // mgp -> Deva
+ {0xE0CC0000u, 41u}, // mgy -> Latn
+ {0x6D680000u, 41u}, // mh -> Latn
+ {0x6D690000u, 41u}, // mi -> Latn
+ {0xB50C0000u, 41u}, // min -> Latn
+ {0xC90C0000u, 26u}, // mis -> Hatr
+ {0x6D6B0000u, 15u}, // mk -> Cyrl
+ {0x6D6C0000u, 50u}, // ml -> Mlym
+ {0xC96C0000u, 41u}, // mls -> Latn
+ {0x6D6E0000u, 15u}, // mn -> Cyrl
+ {0x6D6E434Eu, 51u}, // mn-CN -> Mong
+ {0xA1AC0000u, 7u}, // mni -> Beng
+ {0xD9AC0000u, 53u}, // mnw -> Mymr
+ {0x91CC0000u, 41u}, // moe -> Latn
+ {0x9DCC0000u, 41u}, // moh -> Latn
+ {0xC9CC0000u, 41u}, // mos -> Latn
+ {0x6D720000u, 16u}, // mr -> Deva
+ {0x8E2C0000u, 16u}, // mrd -> Deva
+ {0xA62C0000u, 15u}, // mrj -> Cyrl
+ {0xD22C0000u, 52u}, // mru -> Mroo
+ {0x6D730000u, 41u}, // ms -> Latn
+ {0x6D734343u, 1u}, // ms-CC -> Arab
+ {0x6D734944u, 1u}, // ms-ID -> Arab
+ {0x6D740000u, 41u}, // mt -> Latn
+ {0xC66C0000u, 16u}, // mtr -> Deva
+ {0x828C0000u, 41u}, // mua -> Latn
+ {0xCA8C0000u, 41u}, // mus -> Latn
+ {0xE2AC0000u, 1u}, // mvy -> Arab
+ {0xAACC0000u, 41u}, // mwk -> Latn
+ {0xC6CC0000u, 16u}, // mwr -> Deva
+ {0xD6CC0000u, 41u}, // mwv -> Latn
+ {0x8AEC0000u, 41u}, // mxc -> Latn
+ {0x6D790000u, 53u}, // my -> Mymr
+ {0xD70C0000u, 15u}, // myv -> Cyrl
+ {0xDF0C0000u, 41u}, // myx -> Latn
+ {0xE70C0000u, 47u}, // myz -> Mand
+ {0xB72C0000u, 1u}, // mzn -> Arab
+ {0x6E610000u, 41u}, // na -> Latn
+ {0xB40D0000u, 24u}, // nan -> Hans
+ {0xBC0D0000u, 41u}, // nap -> Latn
+ {0xC00D0000u, 41u}, // naq -> Latn
+ {0x6E620000u, 41u}, // nb -> Latn
+ {0x9C4D0000u, 41u}, // nch -> Latn
+ {0x6E640000u, 41u}, // nd -> Latn
+ {0x886D0000u, 41u}, // ndc -> Latn
+ {0xC86D0000u, 41u}, // nds -> Latn
+ {0x6E650000u, 16u}, // ne -> Deva
+ {0xD88D0000u, 16u}, // new -> Deva
+ {0x6E670000u, 41u}, // ng -> Latn
+ {0xACCD0000u, 41u}, // ngl -> Latn
+ {0x90ED0000u, 41u}, // nhe -> Latn
+ {0xD8ED0000u, 41u}, // nhw -> Latn
+ {0xA50D0000u, 41u}, // nij -> Latn
+ {0xD10D0000u, 41u}, // niu -> Latn
+ {0xB92D0000u, 41u}, // njo -> Latn
+ {0x6E6C0000u, 41u}, // nl -> Latn
+ {0x998D0000u, 41u}, // nmg -> Latn
+ {0x6E6E0000u, 41u}, // nn -> Latn
+ {0x9DAD0000u, 41u}, // nnh -> Latn
+ {0x6E6F0000u, 41u}, // no -> Latn
+ {0x8DCD0000u, 39u}, // nod -> Lana
+ {0x91CD0000u, 16u}, // noe -> Deva
+ {0xB5CD0000u, 64u}, // non -> Runr
+ {0xBA0D0000u, 55u}, // nqo -> Nkoo
+ {0x6E720000u, 41u}, // nr -> Latn
+ {0xAA4D0000u, 9u}, // nsk -> Cans
+ {0xBA4D0000u, 41u}, // nso -> Latn
+ {0xCA8D0000u, 41u}, // nus -> Latn
+ {0x6E760000u, 41u}, // nv -> Latn
+ {0xC2ED0000u, 41u}, // nxq -> Latn
+ {0x6E790000u, 41u}, // ny -> Latn
+ {0xB30D0000u, 41u}, // nym -> Latn
+ {0xB70D0000u, 41u}, // nyn -> Latn
+ {0xA32D0000u, 41u}, // nzi -> Latn
+ {0x6F630000u, 41u}, // oc -> Latn
+ {0x6F6D0000u, 41u}, // om -> Latn
+ {0x6F720000u, 58u}, // or -> Orya
+ {0x6F730000u, 15u}, // os -> Cyrl
+ {0xAA6E0000u, 57u}, // otk -> Orkh
+ {0x70610000u, 23u}, // pa -> Guru
+ {0x7061504Bu, 1u}, // pa-PK -> Arab
+ {0x980F0000u, 41u}, // pag -> Latn
+ {0xAC0F0000u, 60u}, // pal -> Phli
+ {0xB00F0000u, 41u}, // pam -> Latn
+ {0xBC0F0000u, 41u}, // pap -> Latn
+ {0xD00F0000u, 41u}, // pau -> Latn
+ {0x8C4F0000u, 41u}, // pcd -> Latn
+ {0xB04F0000u, 41u}, // pcm -> Latn
+ {0x886F0000u, 41u}, // pdc -> Latn
+ {0xCC6F0000u, 41u}, // pdt -> Latn
+ {0xB88F0000u, 83u}, // peo -> Xpeo
+ {0xACAF0000u, 41u}, // pfl -> Latn
+ {0xB4EF0000u, 61u}, // phn -> Phnx
+ {0x814F0000u, 8u}, // pka -> Brah
+ {0xB94F0000u, 41u}, // pko -> Latn
+ {0x706C0000u, 41u}, // pl -> Latn
+ {0xC98F0000u, 41u}, // pms -> Latn
+ {0xCDAF0000u, 21u}, // pnt -> Grek
+ {0xB5CF0000u, 41u}, // pon -> Latn
+ {0x822F0000u, 34u}, // pra -> Khar
+ {0x8E2F0000u, 1u}, // prd -> Arab
+ {0x9A2F0000u, 41u}, // prg -> Latn
+ {0x70730000u, 1u}, // ps -> Arab
+ {0x70740000u, 41u}, // pt -> Latn
+ {0xD28F0000u, 41u}, // puu -> Latn
+ {0x71750000u, 41u}, // qu -> Latn
+ {0x8A900000u, 41u}, // quc -> Latn
+ {0x9A900000u, 41u}, // qug -> Latn
+ {0xA4110000u, 16u}, // raj -> Deva
+ {0x94510000u, 41u}, // rcf -> Latn
+ {0xA4910000u, 41u}, // rej -> Latn
+ {0xB4D10000u, 41u}, // rgn -> Latn
+ {0x81110000u, 41u}, // ria -> Latn
+ {0x95110000u, 77u}, // rif -> Tfng
+ {0x95114E4Cu, 41u}, // rif-NL -> Latn
+ {0xC9310000u, 16u}, // rjs -> Deva
+ {0xCD510000u, 7u}, // rkt -> Beng
+ {0x726D0000u, 41u}, // rm -> Latn
+ {0x95910000u, 41u}, // rmf -> Latn
+ {0xB9910000u, 41u}, // rmo -> Latn
+ {0xCD910000u, 1u}, // rmt -> Arab
+ {0xD1910000u, 41u}, // rmu -> Latn
+ {0x726E0000u, 41u}, // rn -> Latn
+ {0x99B10000u, 41u}, // rng -> Latn
+ {0x726F0000u, 41u}, // ro -> Latn
+ {0x85D10000u, 41u}, // rob -> Latn
+ {0x95D10000u, 41u}, // rof -> Latn
+ {0xB2710000u, 41u}, // rtm -> Latn
+ {0x72750000u, 15u}, // ru -> Cyrl
+ {0x92910000u, 15u}, // rue -> Cyrl
+ {0x9A910000u, 41u}, // rug -> Latn
+ {0x72770000u, 41u}, // rw -> Latn
+ {0xAAD10000u, 41u}, // rwk -> Latn
+ {0xD3110000u, 33u}, // ryu -> Kana
+ {0x73610000u, 16u}, // sa -> Deva
+ {0x94120000u, 41u}, // saf -> Latn
+ {0x9C120000u, 15u}, // sah -> Cyrl
+ {0xC0120000u, 41u}, // saq -> Latn
+ {0xC8120000u, 41u}, // sas -> Latn
+ {0xCC120000u, 41u}, // sat -> Latn
+ {0xE4120000u, 67u}, // saz -> Saur
+ {0xBC320000u, 41u}, // sbp -> Latn
+ {0x73630000u, 41u}, // sc -> Latn
+ {0xA8520000u, 16u}, // sck -> Deva
+ {0xB4520000u, 41u}, // scn -> Latn
+ {0xB8520000u, 41u}, // sco -> Latn
+ {0xC8520000u, 41u}, // scs -> Latn
+ {0x73640000u, 1u}, // sd -> Arab
+ {0x88720000u, 41u}, // sdc -> Latn
+ {0x9C720000u, 1u}, // sdh -> Arab
+ {0x73650000u, 41u}, // se -> Latn
+ {0x94920000u, 41u}, // sef -> Latn
+ {0x9C920000u, 41u}, // seh -> Latn
+ {0xA0920000u, 41u}, // sei -> Latn
+ {0xC8920000u, 41u}, // ses -> Latn
+ {0x73670000u, 41u}, // sg -> Latn
+ {0x80D20000u, 56u}, // sga -> Ogam
+ {0xC8D20000u, 41u}, // sgs -> Latn
+ {0x73680000u, 41u}, // sh -> Latn
+ {0xA0F20000u, 77u}, // shi -> Tfng
+ {0xB4F20000u, 53u}, // shn -> Mymr
+ {0x73690000u, 69u}, // si -> Sinh
+ {0x8D120000u, 41u}, // sid -> Latn
+ {0x736B0000u, 41u}, // sk -> Latn
+ {0xC5520000u, 1u}, // skr -> Arab
+ {0x736C0000u, 41u}, // sl -> Latn
+ {0xA1720000u, 41u}, // sli -> Latn
+ {0xE1720000u, 41u}, // sly -> Latn
+ {0x736D0000u, 41u}, // sm -> Latn
+ {0x81920000u, 41u}, // sma -> Latn
+ {0xA5920000u, 41u}, // smj -> Latn
+ {0xB5920000u, 41u}, // smn -> Latn
+ {0xBD920000u, 65u}, // smp -> Samr
+ {0xC9920000u, 41u}, // sms -> Latn
+ {0x736E0000u, 41u}, // sn -> Latn
+ {0xA9B20000u, 41u}, // snk -> Latn
+ {0x736F0000u, 41u}, // so -> Latn
+ {0xD1D20000u, 79u}, // sou -> Thai
+ {0x73710000u, 41u}, // sq -> Latn
+ {0x73720000u, 15u}, // sr -> Cyrl
+ {0x73724D45u, 41u}, // sr-ME -> Latn
+ {0x7372524Fu, 41u}, // sr-RO -> Latn
+ {0x73725255u, 41u}, // sr-RU -> Latn
+ {0x73725452u, 41u}, // sr-TR -> Latn
+ {0x86320000u, 70u}, // srb -> Sora
+ {0xB6320000u, 41u}, // srn -> Latn
+ {0xC6320000u, 41u}, // srr -> Latn
+ {0xDE320000u, 16u}, // srx -> Deva
+ {0x73730000u, 41u}, // ss -> Latn
+ {0xE2520000u, 41u}, // ssy -> Latn
+ {0x73740000u, 41u}, // st -> Latn
+ {0xC2720000u, 41u}, // stq -> Latn
+ {0x73750000u, 41u}, // su -> Latn
+ {0xAA920000u, 41u}, // suk -> Latn
+ {0xCA920000u, 41u}, // sus -> Latn
+ {0x73760000u, 41u}, // sv -> Latn
+ {0x73770000u, 41u}, // sw -> Latn
+ {0x86D20000u, 1u}, // swb -> Arab
+ {0x8AD20000u, 41u}, // swc -> Latn
+ {0x9AD20000u, 41u}, // swg -> Latn
+ {0xD6D20000u, 16u}, // swv -> Deva
+ {0xB6F20000u, 41u}, // sxn -> Latn
+ {0xAF120000u, 7u}, // syl -> Beng
+ {0xC7120000u, 71u}, // syr -> Syrc
+ {0xAF320000u, 41u}, // szl -> Latn
+ {0x74610000u, 74u}, // ta -> Taml
+ {0xA4130000u, 16u}, // taj -> Deva
+ {0xD8330000u, 41u}, // tbw -> Latn
+ {0xE0530000u, 36u}, // tcy -> Knda
+ {0x8C730000u, 72u}, // tdd -> Tale
+ {0x98730000u, 16u}, // tdg -> Deva
+ {0x9C730000u, 16u}, // tdh -> Deva
+ {0x74650000u, 76u}, // te -> Telu
+ {0xB0930000u, 41u}, // tem -> Latn
+ {0xB8930000u, 41u}, // teo -> Latn
+ {0xCC930000u, 41u}, // tet -> Latn
+ {0x74670000u, 15u}, // tg -> Cyrl
+ {0x7467504Bu, 1u}, // tg-PK -> Arab
+ {0x74680000u, 79u}, // th -> Thai
+ {0xACF30000u, 16u}, // thl -> Deva
+ {0xC0F30000u, 16u}, // thq -> Deva
+ {0xC4F30000u, 16u}, // thr -> Deva
+ {0x74690000u, 18u}, // ti -> Ethi
+ {0x99130000u, 18u}, // tig -> Ethi
+ {0xD5130000u, 41u}, // tiv -> Latn
+ {0x746B0000u, 41u}, // tk -> Latn
+ {0xAD530000u, 41u}, // tkl -> Latn
+ {0xC5530000u, 41u}, // tkr -> Latn
+ {0xCD530000u, 16u}, // tkt -> Deva
+ {0x746C0000u, 41u}, // tl -> Latn
+ {0xE1730000u, 41u}, // tly -> Latn
+ {0x9D930000u, 41u}, // tmh -> Latn
+ {0x746E0000u, 41u}, // tn -> Latn
+ {0x746F0000u, 41u}, // to -> Latn
+ {0x99D30000u, 41u}, // tog -> Latn
+ {0xA1F30000u, 41u}, // tpi -> Latn
+ {0x74720000u, 41u}, // tr -> Latn
+ {0xD2330000u, 41u}, // tru -> Latn
+ {0xD6330000u, 41u}, // trv -> Latn
+ {0x74730000u, 41u}, // ts -> Latn
+ {0x8E530000u, 21u}, // tsd -> Grek
+ {0x96530000u, 16u}, // tsf -> Deva
+ {0x9A530000u, 41u}, // tsg -> Latn
+ {0xA6530000u, 80u}, // tsj -> Tibt
+ {0x74740000u, 15u}, // tt -> Cyrl
+ {0xA6730000u, 41u}, // ttj -> Latn
+ {0xCA730000u, 79u}, // tts -> Thai
+ {0xCE730000u, 41u}, // ttt -> Latn
+ {0xB2930000u, 41u}, // tum -> Latn
+ {0xAEB30000u, 41u}, // tvl -> Latn
+ {0xC2D30000u, 41u}, // twq -> Latn
+ {0x74790000u, 41u}, // ty -> Latn
+ {0xD7130000u, 15u}, // tyv -> Cyrl
+ {0xB3330000u, 41u}, // tzm -> Latn
+ {0xB0740000u, 15u}, // udm -> Cyrl
+ {0x75670000u, 1u}, // ug -> Arab
+ {0x75674B5Au, 15u}, // ug-KZ -> Cyrl
+ {0x75674D4Eu, 15u}, // ug-MN -> Cyrl
+ {0x80D40000u, 81u}, // uga -> Ugar
+ {0x756B0000u, 15u}, // uk -> Cyrl
+ {0xA1740000u, 41u}, // uli -> Latn
+ {0x85940000u, 41u}, // umb -> Latn
+ {0xC5B40000u, 7u}, // unr -> Beng
+ {0xC5B44E50u, 16u}, // unr-NP -> Deva
+ {0xDDB40000u, 7u}, // unx -> Beng
+ {0x75720000u, 1u}, // ur -> Arab
+ {0x757A0000u, 41u}, // uz -> Latn
+ {0x757A4146u, 1u}, // uz-AF -> Arab
+ {0x757A434Eu, 15u}, // uz-CN -> Cyrl
+ {0xA0150000u, 82u}, // vai -> Vaii
+ {0x76650000u, 41u}, // ve -> Latn
+ {0x88950000u, 41u}, // vec -> Latn
+ {0xBC950000u, 41u}, // vep -> Latn
+ {0x76690000u, 41u}, // vi -> Latn
+ {0x89150000u, 41u}, // vic -> Latn
+ {0xC9750000u, 41u}, // vls -> Latn
+ {0x95950000u, 41u}, // vmf -> Latn
+ {0xD9950000u, 41u}, // vmw -> Latn
+ {0x766F0000u, 41u}, // vo -> Latn
+ {0xCDD50000u, 41u}, // vot -> Latn
+ {0xBA350000u, 41u}, // vro -> Latn
+ {0xB6950000u, 41u}, // vun -> Latn
+ {0x77610000u, 41u}, // wa -> Latn
+ {0x90160000u, 41u}, // wae -> Latn
+ {0xAC160000u, 18u}, // wal -> Ethi
+ {0xC4160000u, 41u}, // war -> Latn
+ {0xBC360000u, 41u}, // wbp -> Latn
+ {0xC0360000u, 76u}, // wbq -> Telu
+ {0xC4360000u, 16u}, // wbr -> Deva
+ {0xC9760000u, 41u}, // wls -> Latn
+ {0xA1B60000u, 1u}, // wni -> Arab
+ {0x776F0000u, 41u}, // wo -> Latn
+ {0xB2760000u, 16u}, // wtm -> Deva
+ {0xD2960000u, 24u}, // wuu -> Hans
+ {0xD4170000u, 41u}, // xav -> Latn
+ {0xC4570000u, 10u}, // xcr -> Cari
+ {0x78680000u, 41u}, // xh -> Latn
+ {0x89770000u, 45u}, // xlc -> Lyci
+ {0x8D770000u, 46u}, // xld -> Lydi
+ {0x95970000u, 19u}, // xmf -> Geor
+ {0xB5970000u, 48u}, // xmn -> Mani
+ {0xC5970000u, 49u}, // xmr -> Merc
+ {0x81B70000u, 54u}, // xna -> Narb
+ {0xC5B70000u, 16u}, // xnr -> Deva
+ {0x99D70000u, 41u}, // xog -> Latn
+ {0xC5F70000u, 63u}, // xpr -> Prti
+ {0x82570000u, 66u}, // xsa -> Sarb
+ {0xC6570000u, 16u}, // xsr -> Deva
+ {0xB8180000u, 41u}, // yao -> Latn
+ {0xBC180000u, 41u}, // yap -> Latn
+ {0xD4180000u, 41u}, // yav -> Latn
+ {0x84380000u, 41u}, // ybb -> Latn
+ {0x79690000u, 27u}, // yi -> Hebr
+ {0x796F0000u, 41u}, // yo -> Latn
+ {0xAE380000u, 41u}, // yrl -> Latn
+ {0x82980000u, 41u}, // yua -> Latn
+ {0x7A610000u, 41u}, // za -> Latn
+ {0x98190000u, 41u}, // zag -> Latn
+ {0xA4790000u, 1u}, // zdj -> Arab
+ {0x80990000u, 41u}, // zea -> Latn
+ {0x9CD90000u, 77u}, // zgh -> Tfng
+ {0x7A680000u, 24u}, // zh -> Hans
+ {0x7A684155u, 25u}, // zh-AU -> Hant
+ {0x7A68424Eu, 25u}, // zh-BN -> Hant
+ {0x7A684742u, 25u}, // zh-GB -> Hant
+ {0x7A684746u, 25u}, // zh-GF -> Hant
+ {0x7A68484Bu, 25u}, // zh-HK -> Hant
+ {0x7A684944u, 25u}, // zh-ID -> Hant
+ {0x7A684D4Fu, 25u}, // zh-MO -> Hant
+ {0x7A684D59u, 25u}, // zh-MY -> Hant
+ {0x7A685041u, 25u}, // zh-PA -> Hant
+ {0x7A685046u, 25u}, // zh-PF -> Hant
+ {0x7A685048u, 25u}, // zh-PH -> Hant
+ {0x7A685352u, 25u}, // zh-SR -> Hant
+ {0x7A685448u, 25u}, // zh-TH -> Hant
+ {0x7A685457u, 25u}, // zh-TW -> Hant
+ {0x7A685553u, 25u}, // zh-US -> Hant
+ {0x7A68564Eu, 25u}, // zh-VN -> Hant
+ {0xA1990000u, 41u}, // zmi -> Latn
+ {0x7A750000u, 41u}, // zu -> Latn
+ {0x83390000u, 41u}, // zza -> Latn
+});
+
+std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
+ 0x616145544C61746Ellu, // aa_Latn_ET
+ 0x616247454379726Cllu, // ab_Cyrl_GE
+ 0xC42047484C61746Ellu, // abr_Latn_GH
+ 0x904049444C61746Ellu, // ace_Latn_ID
+ 0x9C4055474C61746Ellu, // ach_Latn_UG
+ 0x806047484C61746Ellu, // ada_Latn_GH
+ 0xE06052554379726Cllu, // ady_Cyrl_RU
+ 0x6165495241767374llu, // ae_Avst_IR
+ 0x8480544E41726162llu, // aeb_Arab_TN
+ 0x61665A414C61746Ellu, // af_Latn_ZA
+ 0xC0C0434D4C61746Ellu, // agq_Latn_CM
+ 0xB8E0494E41686F6Dllu, // aho_Ahom_IN
+ 0x616B47484C61746Ellu, // ak_Latn_GH
+ 0xA940495158737578llu, // akk_Xsux_IQ
+ 0xB560584B4C61746Ellu, // aln_Latn_XK
+ 0xCD6052554379726Cllu, // alt_Cyrl_RU
+ 0x616D455445746869llu, // am_Ethi_ET
+ 0xB9804E474C61746Ellu, // amo_Latn_NG
+ 0xE5C049444C61746Ellu, // aoz_Latn_ID
+ 0x6172454741726162llu, // ar_Arab_EG
+ 0x8A20495241726D69llu, // arc_Armi_IR
+ 0x8A204A4F4E626174llu, // arc_Nbat_JO
+ 0x8A20535950616C6Dllu, // arc_Palm_SY
+ 0xB620434C4C61746Ellu, // arn_Latn_CL
+ 0xBA20424F4C61746Ellu, // aro_Latn_BO
+ 0xC220445A41726162llu, // arq_Arab_DZ
+ 0xE2204D4141726162llu, // ary_Arab_MA
+ 0xE620454741726162llu, // arz_Arab_EG
+ 0x6173494E42656E67llu, // as_Beng_IN
+ 0x8240545A4C61746Ellu, // asa_Latn_TZ
+ 0x9240555353676E77llu, // ase_Sgnw_US
+ 0xCE4045534C61746Ellu, // ast_Latn_ES
+ 0xA66043414C61746Ellu, // atj_Latn_CA
+ 0x617652554379726Cllu, // av_Cyrl_RU
+ 0x82C0494E44657661llu, // awa_Deva_IN
+ 0x6179424F4C61746Ellu, // ay_Latn_BO
+ 0x617A495241726162llu, // az_Arab_IR
+ 0x617A415A4C61746Ellu, // az_Latn_AZ
+ 0x626152554379726Cllu, // ba_Cyrl_RU
+ 0xAC01504B41726162llu, // bal_Arab_PK
+ 0xB40149444C61746Ellu, // ban_Latn_ID
+ 0xBC014E5044657661llu, // bap_Deva_NP
+ 0xC40141544C61746Ellu, // bar_Latn_AT
+ 0xC801434D4C61746Ellu, // bas_Latn_CM
+ 0xDC01434D42616D75llu, // bax_Bamu_CM
+ 0x882149444C61746Ellu, // bbc_Latn_ID
+ 0xA421434D4C61746Ellu, // bbj_Latn_CM
+ 0xA04143494C61746Ellu, // bci_Latn_CI
+ 0x626542594379726Cllu, // be_Cyrl_BY
+ 0xA481534441726162llu, // bej_Arab_SD
+ 0xB0815A4D4C61746Ellu, // bem_Latn_ZM
+ 0xD88149444C61746Ellu, // bew_Latn_ID
+ 0xE481545A4C61746Ellu, // bez_Latn_TZ
+ 0x8CA1434D4C61746Ellu, // bfd_Latn_CM
+ 0xC0A1494E54616D6Cllu, // bfq_Taml_IN
+ 0xCCA1504B41726162llu, // bft_Arab_PK
+ 0xE0A1494E44657661llu, // bfy_Deva_IN
+ 0x626742474379726Cllu, // bg_Cyrl_BG
+ 0x88C1494E44657661llu, // bgc_Deva_IN
+ 0xB4C1504B41726162llu, // bgn_Arab_PK
+ 0xDCC154524772656Bllu, // bgx_Grek_TR
+ 0x6268494E4B746869llu, // bh_Kthi_IN
+ 0x84E1494E44657661llu, // bhb_Deva_IN
+ 0xA0E1494E44657661llu, // bhi_Deva_IN
+ 0xA8E150484C61746Ellu, // bhk_Latn_PH
+ 0xB8E1494E44657661llu, // bho_Deva_IN
+ 0x626956554C61746Ellu, // bi_Latn_VU
+ 0xA90150484C61746Ellu, // bik_Latn_PH
+ 0xB5014E474C61746Ellu, // bin_Latn_NG
+ 0xA521494E44657661llu, // bjj_Deva_IN
+ 0xB52149444C61746Ellu, // bjn_Latn_ID
+ 0xB141434D4C61746Ellu, // bkm_Latn_CM
+ 0xD14150484C61746Ellu, // bku_Latn_PH
+ 0xCD61564E54617674llu, // blt_Tavt_VN
+ 0x626D4D4C4C61746Ellu, // bm_Latn_ML
+ 0xC1814D4C4C61746Ellu, // bmq_Latn_ML
+ 0x626E424442656E67llu, // bn_Beng_BD
+ 0x626F434E54696274llu, // bo_Tibt_CN
+ 0xE1E1494E42656E67llu, // bpy_Beng_IN
+ 0xA201495241726162llu, // bqi_Arab_IR
+ 0xD60143494C61746Ellu, // bqv_Latn_CI
+ 0x627246524C61746Ellu, // br_Latn_FR
+ 0x8221494E44657661llu, // bra_Deva_IN
+ 0x9E21504B41726162llu, // brh_Arab_PK
+ 0xDE21494E44657661llu, // brx_Deva_IN
+ 0x627342414C61746Ellu, // bs_Latn_BA
+ 0xC2414C5242617373llu, // bsq_Bass_LR
+ 0xCA41434D4C61746Ellu, // bss_Latn_CM
+ 0xBA6150484C61746Ellu, // bto_Latn_PH
+ 0xD661504B44657661llu, // btv_Deva_PK
+ 0x828152554379726Cllu, // bua_Cyrl_RU
+ 0x8A8159544C61746Ellu, // buc_Latn_YT
+ 0x9A8149444C61746Ellu, // bug_Latn_ID
+ 0xB281434D4C61746Ellu, // bum_Latn_CM
+ 0x86A147514C61746Ellu, // bvb_Latn_GQ
+ 0xB701455245746869llu, // byn_Ethi_ER
+ 0xD701434D4C61746Ellu, // byv_Latn_CM
+ 0x93214D4C4C61746Ellu, // bze_Latn_ML
+ 0x636145534C61746Ellu, // ca_Latn_ES
+ 0x9C424E474C61746Ellu, // cch_Latn_NG
+ 0xBC42494E42656E67llu, // ccp_Beng_IN
+ 0xBC42424443616B6Dllu, // ccp_Cakm_BD
+ 0x636552554379726Cllu, // ce_Cyrl_RU
+ 0x848250484C61746Ellu, // ceb_Latn_PH
+ 0x98C255474C61746Ellu, // cgg_Latn_UG
+ 0x636847554C61746Ellu, // ch_Latn_GU
+ 0xA8E2464D4C61746Ellu, // chk_Latn_FM
+ 0xB0E252554379726Cllu, // chm_Cyrl_RU
+ 0xB8E255534C61746Ellu, // cho_Latn_US
+ 0xBCE243414C61746Ellu, // chp_Latn_CA
+ 0xC4E2555343686572llu, // chr_Cher_US
+ 0x81224B4841726162llu, // cja_Arab_KH
+ 0xB122564E4368616Dllu, // cjm_Cham_VN
+ 0x8542495141726162llu, // ckb_Arab_IQ
+ 0x636F46524C61746Ellu, // co_Latn_FR
+ 0xBDC24547436F7074llu, // cop_Copt_EG
+ 0xC9E250484C61746Ellu, // cps_Latn_PH
+ 0x6372434143616E73llu, // cr_Cans_CA
+ 0xA622434143616E73llu, // crj_Cans_CA
+ 0xAA22434143616E73llu, // crk_Cans_CA
+ 0xAE22434143616E73llu, // crl_Cans_CA
+ 0xB222434143616E73llu, // crm_Cans_CA
+ 0xCA2253434C61746Ellu, // crs_Latn_SC
+ 0x6373435A4C61746Ellu, // cs_Latn_CZ
+ 0x8642504C4C61746Ellu, // csb_Latn_PL
+ 0xDA42434143616E73llu, // csw_Cans_CA
+ 0x8E624D4D50617563llu, // ctd_Pauc_MM
+ 0x637552554379726Cllu, // cu_Cyrl_RU
+ 0x63754247476C6167llu, // cu_Glag_BG
+ 0x637652554379726Cllu, // cv_Cyrl_RU
+ 0x637947424C61746Ellu, // cy_Latn_GB
+ 0x6461444B4C61746Ellu, // da_Latn_DK
+ 0xA80355534C61746Ellu, // dak_Latn_US
+ 0xC40352554379726Cllu, // dar_Cyrl_RU
+ 0xD4034B454C61746Ellu, // dav_Latn_KE
+ 0x8843494E41726162llu, // dcc_Arab_IN
+ 0x646544454C61746Ellu, // de_Latn_DE
+ 0xB48343414C61746Ellu, // den_Latn_CA
+ 0xC4C343414C61746Ellu, // dgr_Latn_CA
+ 0x91234E454C61746Ellu, // dje_Latn_NE
+ 0xA5A343494C61746Ellu, // dnj_Latn_CI
+ 0xA1C3494E41726162llu, // doi_Arab_IN
+ 0x864344454C61746Ellu, // dsb_Latn_DE
+ 0xB2634D4C4C61746Ellu, // dtm_Latn_ML
+ 0xBE634D594C61746Ellu, // dtp_Latn_MY
+ 0x8283434D4C61746Ellu, // dua_Latn_CM
+ 0x64764D5654686161llu, // dv_Thaa_MV
+ 0xBB03534E4C61746Ellu, // dyo_Latn_SN
+ 0xD30342464C61746Ellu, // dyu_Latn_BF
+ 0x647A425454696274llu, // dz_Tibt_BT
+ 0xD0244B454C61746Ellu, // ebu_Latn_KE
+ 0x656547484C61746Ellu, // ee_Latn_GH
+ 0xA0A44E474C61746Ellu, // efi_Latn_NG
+ 0xACC449544C61746Ellu, // egl_Latn_IT
+ 0xE0C4454745677970llu, // egy_Egyp_EG
+ 0xE1444D4D4B616C69llu, // eky_Kali_MM
+ 0x656C47524772656Bllu, // el_Grek_GR
+ 0x656E47424C61746Ellu, // en_Latn_GB
+ 0x656E55534C61746Ellu, // en_Latn_US
+ 0x656E474253686177llu, // en_Shaw_GB
+ 0x657345534C61746Ellu, // es_Latn_ES
+ 0x65734D584C61746Ellu, // es_Latn_MX
+ 0x657355534C61746Ellu, // es_Latn_US
+ 0xD24455534C61746Ellu, // esu_Latn_US
+ 0x657445454C61746Ellu, // et_Latn_EE
+ 0xCE6449544974616Cllu, // ett_Ital_IT
+ 0x657545534C61746Ellu, // eu_Latn_ES
+ 0xBAC4434D4C61746Ellu, // ewo_Latn_CM
+ 0xCEE445534C61746Ellu, // ext_Latn_ES
+ 0x6661495241726162llu, // fa_Arab_IR
+ 0xB40547514C61746Ellu, // fan_Latn_GQ
+ 0x6666534E4C61746Ellu, // ff_Latn_SN
+ 0xB0A54D4C4C61746Ellu, // ffm_Latn_ML
+ 0x666946494C61746Ellu, // fi_Latn_FI
+ 0x8105534441726162llu, // fia_Arab_SD
+ 0xAD0550484C61746Ellu, // fil_Latn_PH
+ 0xCD0553454C61746Ellu, // fit_Latn_SE
+ 0x666A464A4C61746Ellu, // fj_Latn_FJ
+ 0x666F464F4C61746Ellu, // fo_Latn_FO
+ 0xB5C5424A4C61746Ellu, // fon_Latn_BJ
+ 0x667246524C61746Ellu, // fr_Latn_FR
+ 0x8A2555534C61746Ellu, // frc_Latn_US
+ 0xBE2546524C61746Ellu, // frp_Latn_FR
+ 0xC62544454C61746Ellu, // frr_Latn_DE
+ 0xCA2544454C61746Ellu, // frs_Latn_DE
+ 0x8E8557464C61746Ellu, // fud_Latn_WF
+ 0xC2854E454C61746Ellu, // fuq_Latn_NE
+ 0xC68549544C61746Ellu, // fur_Latn_IT
+ 0xD6854E474C61746Ellu, // fuv_Latn_NG
+ 0xC6A553444C61746Ellu, // fvr_Latn_SD
+ 0x66794E4C4C61746Ellu, // fy_Latn_NL
+ 0x676149454C61746Ellu, // ga_Latn_IE
+ 0x800647484C61746Ellu, // gaa_Latn_GH
+ 0x98064D444C61746Ellu, // gag_Latn_MD
+ 0xB406434E48616E73llu, // gan_Hans_CN
+ 0xE00649444C61746Ellu, // gay_Latn_ID
+ 0xB026494E44657661llu, // gbm_Deva_IN
+ 0xE426495241726162llu, // gbz_Arab_IR
+ 0xC44647464C61746Ellu, // gcr_Latn_GF
+ 0x676447424C61746Ellu, // gd_Latn_GB
+ 0xE486455445746869llu, // gez_Ethi_ET
+ 0xB4C64E5044657661llu, // ggn_Deva_NP
+ 0xAD064B494C61746Ellu, // gil_Latn_KI
+ 0xA926504B41726162llu, // gjk_Arab_PK
+ 0xD126504B41726162llu, // gju_Arab_PK
+ 0x676C45534C61746Ellu, // gl_Latn_ES
+ 0xA966495241726162llu, // glk_Arab_IR
+ 0x676E50594C61746Ellu, // gn_Latn_PY
+ 0xB1C6494E44657661llu, // gom_Deva_IN
+ 0xB5C6494E54656C75llu, // gon_Telu_IN
+ 0xC5C649444C61746Ellu, // gor_Latn_ID
+ 0xC9C64E4C4C61746Ellu, // gos_Latn_NL
+ 0xCDC65541476F7468llu, // got_Goth_UA
+ 0x8A26435943707274llu, // grc_Cprt_CY
+ 0x8A2647524C696E62llu, // grc_Linb_GR
+ 0xCE26494E42656E67llu, // grt_Beng_IN
+ 0xDA4643484C61746Ellu, // gsw_Latn_CH
+ 0x6775494E47756A72llu, // gu_Gujr_IN
+ 0x868642524C61746Ellu, // gub_Latn_BR
+ 0x8A86434F4C61746Ellu, // guc_Latn_CO
+ 0xC68647484C61746Ellu, // gur_Latn_GH
+ 0xE6864B454C61746Ellu, // guz_Latn_KE
+ 0x6776494D4C61746Ellu, // gv_Latn_IM
+ 0xC6A64E5044657661llu, // gvr_Deva_NP
+ 0xA2C643414C61746Ellu, // gwi_Latn_CA
+ 0x68614E474C61746Ellu, // ha_Latn_NG
+ 0xA807434E48616E73llu, // hak_Hans_CN
+ 0xD80755534C61746Ellu, // haw_Latn_US
+ 0xE407414641726162llu, // haz_Arab_AF
+ 0x6865494C48656272llu, // he_Hebr_IL
+ 0x6869494E44657661llu, // hi_Deva_IN
+ 0x9507464A4C61746Ellu, // hif_Latn_FJ
+ 0xAD0750484C61746Ellu, // hil_Latn_PH
+ 0xD1675452486C7577llu, // hlu_Hluw_TR
+ 0x8D87434E506C7264llu, // hmd_Plrd_CN
+ 0x8DA7504B41726162llu, // hnd_Arab_PK
+ 0x91A7494E44657661llu, // hne_Deva_IN
+ 0xA5A74C41486D6E67llu, // hnj_Hmng_LA
+ 0xB5A750484C61746Ellu, // hnn_Latn_PH
+ 0xB9A7504B41726162llu, // hno_Arab_PK
+ 0x686F50474C61746Ellu, // ho_Latn_PG
+ 0x89C7494E44657661llu, // hoc_Deva_IN
+ 0xA5C7494E44657661llu, // hoj_Deva_IN
+ 0x687248524C61746Ellu, // hr_Latn_HR
+ 0x864744454C61746Ellu, // hsb_Latn_DE
+ 0xB647434E48616E73llu, // hsn_Hans_CN
+ 0x687448544C61746Ellu, // ht_Latn_HT
+ 0x687548554C61746Ellu, // hu_Latn_HU
+ 0x6879414D41726D6Ellu, // hy_Armn_AM
+ 0x687A4E414C61746Ellu, // hz_Latn_NA
+ 0x696146524C61746Ellu, // ia_Latn_FR
+ 0x80284D594C61746Ellu, // iba_Latn_MY
+ 0x84284E474C61746Ellu, // ibb_Latn_NG
+ 0x696449444C61746Ellu, // id_Latn_ID
+ 0x69674E474C61746Ellu, // ig_Latn_NG
+ 0x6969434E59696969llu, // ii_Yiii_CN
+ 0x696B55534C61746Ellu, // ik_Latn_US
+ 0xCD4843414C61746Ellu, // ikt_Latn_CA
+ 0xB96850484C61746Ellu, // ilo_Latn_PH
+ 0x696E49444C61746Ellu, // in_Latn_ID
+ 0x9DA852554379726Cllu, // inh_Cyrl_RU
+ 0x697349534C61746Ellu, // is_Latn_IS
+ 0x697449544C61746Ellu, // it_Latn_IT
+ 0x6975434143616E73llu, // iu_Cans_CA
+ 0x6977494C48656272llu, // iw_Hebr_IL
+ 0x9F2852554C61746Ellu, // izh_Latn_RU
+ 0x6A614A504A70616Ellu, // ja_Jpan_JP
+ 0xB0094A4D4C61746Ellu, // jam_Latn_JM
+ 0xB8C9434D4C61746Ellu, // jgo_Latn_CM
+ 0x6A69554148656272llu, // ji_Hebr_UA
+ 0x8989545A4C61746Ellu, // jmc_Latn_TZ
+ 0xAD894E5044657661llu, // jml_Deva_NP
+ 0xCE89444B4C61746Ellu, // jut_Latn_DK
+ 0x6A7649444C61746Ellu, // jv_Latn_ID
+ 0x6A7749444C61746Ellu, // jw_Latn_ID
+ 0x6B61474547656F72llu, // ka_Geor_GE
+ 0x800A555A4379726Cllu, // kaa_Cyrl_UZ
+ 0x840A445A4C61746Ellu, // kab_Latn_DZ
+ 0x880A4D4D4C61746Ellu, // kac_Latn_MM
+ 0xA40A4E474C61746Ellu, // kaj_Latn_NG
+ 0xB00A4B454C61746Ellu, // kam_Latn_KE
+ 0xB80A4D4C4C61746Ellu, // kao_Latn_ML
+ 0x8C2A52554379726Cllu, // kbd_Cyrl_RU
+ 0x984A4E474C61746Ellu, // kcg_Latn_NG
+ 0xA84A5A574C61746Ellu, // kck_Latn_ZW
+ 0x906A545A4C61746Ellu, // kde_Latn_TZ
+ 0xCC6A544854686169llu, // kdt_Thai_TH
+ 0x808A43564C61746Ellu, // kea_Latn_CV
+ 0xB48A434D4C61746Ellu, // ken_Latn_CM
+ 0xB8AA43494C61746Ellu, // kfo_Latn_CI
+ 0xC4AA494E44657661llu, // kfr_Deva_IN
+ 0xE0AA494E44657661llu, // kfy_Deva_IN
+ 0x6B6743444C61746Ellu, // kg_Latn_CD
+ 0x90CA49444C61746Ellu, // kge_Latn_ID
+ 0xBCCA42524C61746Ellu, // kgp_Latn_BR
+ 0x80EA494E4C61746Ellu, // kha_Latn_IN
+ 0x84EA434E54616C75llu, // khb_Talu_CN
+ 0xB4EA494E44657661llu, // khn_Deva_IN
+ 0xC0EA4D4C4C61746Ellu, // khq_Latn_ML
+ 0xCCEA494E4D796D72llu, // kht_Mymr_IN
+ 0xD8EA504B41726162llu, // khw_Arab_PK
+ 0x6B694B454C61746Ellu, // ki_Latn_KE
+ 0xD10A54524C61746Ellu, // kiu_Latn_TR
+ 0x6B6A4E414C61746Ellu, // kj_Latn_NA
+ 0x992A4C414C616F6Fllu, // kjg_Laoo_LA
+ 0x6B6B434E41726162llu, // kk_Arab_CN
+ 0x6B6B4B5A4379726Cllu, // kk_Cyrl_KZ
+ 0xA54A434D4C61746Ellu, // kkj_Latn_CM
+ 0x6B6C474C4C61746Ellu, // kl_Latn_GL
+ 0xB56A4B454C61746Ellu, // kln_Latn_KE
+ 0x6B6D4B484B686D72llu, // km_Khmr_KH
+ 0x858A414F4C61746Ellu, // kmb_Latn_AO
+ 0x6B6E494E4B6E6461llu, // kn_Knda_IN
+ 0x6B6F4B524B6F7265llu, // ko_Kore_KR
+ 0xA1CA52554379726Cllu, // koi_Cyrl_RU
+ 0xA9CA494E44657661llu, // kok_Deva_IN
+ 0xC9CA464D4C61746Ellu, // kos_Latn_FM
+ 0x91EA4C524C61746Ellu, // kpe_Latn_LR
+ 0x8A2A52554379726Cllu, // krc_Cyrl_RU
+ 0xA22A534C4C61746Ellu, // kri_Latn_SL
+ 0xA62A50484C61746Ellu, // krj_Latn_PH
+ 0xAE2A52554C61746Ellu, // krl_Latn_RU
+ 0xD22A494E44657661llu, // kru_Deva_IN
+ 0x6B73494E41726162llu, // ks_Arab_IN
+ 0x864A545A4C61746Ellu, // ksb_Latn_TZ
+ 0x964A434D4C61746Ellu, // ksf_Latn_CM
+ 0x9E4A44454C61746Ellu, // ksh_Latn_DE
+ 0x6B75495141726162llu, // ku_Arab_IQ
+ 0x6B7554524C61746Ellu, // ku_Latn_TR
+ 0xB28A52554379726Cllu, // kum_Cyrl_RU
+ 0x6B7652554379726Cllu, // kv_Cyrl_RU
+ 0xC6AA49444C61746Ellu, // kvr_Latn_ID
+ 0xDEAA504B41726162llu, // kvx_Arab_PK
+ 0x6B7747424C61746Ellu, // kw_Latn_GB
+ 0xB2EA544854686169llu, // kxm_Thai_TH
+ 0xBEEA504B41726162llu, // kxp_Arab_PK
+ 0x6B79434E41726162llu, // ky_Arab_CN
+ 0x6B794B474379726Cllu, // ky_Cyrl_KG
+ 0x6B7954524C61746Ellu, // ky_Latn_TR
+ 0x6C6156414C61746Ellu, // la_Latn_VA
+ 0x840B47524C696E61llu, // lab_Lina_GR
+ 0x8C0B494C48656272llu, // lad_Hebr_IL
+ 0x980B545A4C61746Ellu, // lag_Latn_TZ
+ 0x9C0B504B41726162llu, // lah_Arab_PK
+ 0xA40B55474C61746Ellu, // laj_Latn_UG
+ 0x6C624C554C61746Ellu, // lb_Latn_LU
+ 0x902B52554379726Cllu, // lbe_Cyrl_RU
+ 0xD82B49444C61746Ellu, // lbw_Latn_ID
+ 0xBC4B434E54686169llu, // lcp_Thai_CN
+ 0xBC8B494E4C657063llu, // lep_Lepc_IN
+ 0xE48B52554379726Cllu, // lez_Cyrl_RU
+ 0x6C6755474C61746Ellu, // lg_Latn_UG
+ 0x6C694E4C4C61746Ellu, // li_Latn_NL
+ 0x950B4E5044657661llu, // lif_Deva_NP
+ 0x950B494E4C696D62llu, // lif_Limb_IN
+ 0xA50B49544C61746Ellu, // lij_Latn_IT
+ 0xC90B434E4C697375llu, // lis_Lisu_CN
+ 0xBD2B49444C61746Ellu, // ljp_Latn_ID
+ 0xA14B495241726162llu, // lki_Arab_IR
+ 0xCD4B55534C61746Ellu, // lkt_Latn_US
+ 0xB58B494E54656C75llu, // lmn_Telu_IN
+ 0xB98B49544C61746Ellu, // lmo_Latn_IT
+ 0x6C6E43444C61746Ellu, // ln_Latn_CD
+ 0x6C6F4C414C616F6Fllu, // lo_Laoo_LA
+ 0xADCB43444C61746Ellu, // lol_Latn_CD
+ 0xE5CB5A4D4C61746Ellu, // loz_Latn_ZM
+ 0x8A2B495241726162llu, // lrc_Arab_IR
+ 0x6C744C544C61746Ellu, // lt_Latn_LT
+ 0x9A6B4C564C61746Ellu, // ltg_Latn_LV
+ 0x6C7543444C61746Ellu, // lu_Latn_CD
+ 0x828B43444C61746Ellu, // lua_Latn_CD
+ 0xBA8B4B454C61746Ellu, // luo_Latn_KE
+ 0xE28B4B454C61746Ellu, // luy_Latn_KE
+ 0xE68B495241726162llu, // luz_Arab_IR
+ 0x6C764C564C61746Ellu, // lv_Latn_LV
+ 0xAECB544854686169llu, // lwl_Thai_TH
+ 0x9F2B434E48616E73llu, // lzh_Hans_CN
+ 0xE72B54524C61746Ellu, // lzz_Latn_TR
+ 0x8C0C49444C61746Ellu, // mad_Latn_ID
+ 0x940C434D4C61746Ellu, // maf_Latn_CM
+ 0x980C494E44657661llu, // mag_Deva_IN
+ 0xA00C494E44657661llu, // mai_Deva_IN
+ 0xA80C49444C61746Ellu, // mak_Latn_ID
+ 0xB40C474D4C61746Ellu, // man_Latn_GM
+ 0xB40C474E4E6B6F6Fllu, // man_Nkoo_GN
+ 0xC80C4B454C61746Ellu, // mas_Latn_KE
+ 0xE40C4D584C61746Ellu, // maz_Latn_MX
+ 0x946C52554379726Cllu, // mdf_Cyrl_RU
+ 0x9C6C50484C61746Ellu, // mdh_Latn_PH
+ 0xC46C49444C61746Ellu, // mdr_Latn_ID
+ 0xB48C534C4C61746Ellu, // men_Latn_SL
+ 0xC48C4B454C61746Ellu, // mer_Latn_KE
+ 0x80AC544841726162llu, // mfa_Arab_TH
+ 0x90AC4D554C61746Ellu, // mfe_Latn_MU
+ 0x6D674D474C61746Ellu, // mg_Latn_MG
+ 0x9CCC4D5A4C61746Ellu, // mgh_Latn_MZ
+ 0xB8CC434D4C61746Ellu, // mgo_Latn_CM
+ 0xBCCC4E5044657661llu, // mgp_Deva_NP
+ 0xE0CC545A4C61746Ellu, // mgy_Latn_TZ
+ 0x6D684D484C61746Ellu, // mh_Latn_MH
+ 0x6D694E5A4C61746Ellu, // mi_Latn_NZ
+ 0xB50C49444C61746Ellu, // min_Latn_ID
+ 0xC90C495148617472llu, // mis_Hatr_IQ
+ 0x6D6B4D4B4379726Cllu, // mk_Cyrl_MK
+ 0x6D6C494E4D6C796Dllu, // ml_Mlym_IN
+ 0xC96C53444C61746Ellu, // mls_Latn_SD
+ 0x6D6E4D4E4379726Cllu, // mn_Cyrl_MN
+ 0x6D6E434E4D6F6E67llu, // mn_Mong_CN
+ 0xA1AC494E42656E67llu, // mni_Beng_IN
+ 0xD9AC4D4D4D796D72llu, // mnw_Mymr_MM
+ 0x91CC43414C61746Ellu, // moe_Latn_CA
+ 0x9DCC43414C61746Ellu, // moh_Latn_CA
+ 0xC9CC42464C61746Ellu, // mos_Latn_BF
+ 0x6D72494E44657661llu, // mr_Deva_IN
+ 0x8E2C4E5044657661llu, // mrd_Deva_NP
+ 0xA62C52554379726Cllu, // mrj_Cyrl_RU
+ 0xD22C42444D726F6Fllu, // mru_Mroo_BD
+ 0x6D734D594C61746Ellu, // ms_Latn_MY
+ 0x6D744D544C61746Ellu, // mt_Latn_MT
+ 0xC66C494E44657661llu, // mtr_Deva_IN
+ 0x828C434D4C61746Ellu, // mua_Latn_CM
+ 0xCA8C55534C61746Ellu, // mus_Latn_US
+ 0xE2AC504B41726162llu, // mvy_Arab_PK
+ 0xAACC4D4C4C61746Ellu, // mwk_Latn_ML
+ 0xC6CC494E44657661llu, // mwr_Deva_IN
+ 0xD6CC49444C61746Ellu, // mwv_Latn_ID
+ 0x8AEC5A574C61746Ellu, // mxc_Latn_ZW
+ 0x6D794D4D4D796D72llu, // my_Mymr_MM
+ 0xD70C52554379726Cllu, // myv_Cyrl_RU
+ 0xDF0C55474C61746Ellu, // myx_Latn_UG
+ 0xE70C49524D616E64llu, // myz_Mand_IR
+ 0xB72C495241726162llu, // mzn_Arab_IR
+ 0x6E614E524C61746Ellu, // na_Latn_NR
+ 0xB40D434E48616E73llu, // nan_Hans_CN
+ 0xBC0D49544C61746Ellu, // nap_Latn_IT
+ 0xC00D4E414C61746Ellu, // naq_Latn_NA
+ 0x6E624E4F4C61746Ellu, // nb_Latn_NO
+ 0x9C4D4D584C61746Ellu, // nch_Latn_MX
+ 0x6E645A574C61746Ellu, // nd_Latn_ZW
+ 0x886D4D5A4C61746Ellu, // ndc_Latn_MZ
+ 0xC86D44454C61746Ellu, // nds_Latn_DE
+ 0x6E654E5044657661llu, // ne_Deva_NP
+ 0xD88D4E5044657661llu, // new_Deva_NP
+ 0x6E674E414C61746Ellu, // ng_Latn_NA
+ 0xACCD4D5A4C61746Ellu, // ngl_Latn_MZ
+ 0x90ED4D584C61746Ellu, // nhe_Latn_MX
+ 0xD8ED4D584C61746Ellu, // nhw_Latn_MX
+ 0xA50D49444C61746Ellu, // nij_Latn_ID
+ 0xD10D4E554C61746Ellu, // niu_Latn_NU
+ 0xB92D494E4C61746Ellu, // njo_Latn_IN
+ 0x6E6C4E4C4C61746Ellu, // nl_Latn_NL
+ 0x998D434D4C61746Ellu, // nmg_Latn_CM
+ 0x6E6E4E4F4C61746Ellu, // nn_Latn_NO
+ 0x9DAD434D4C61746Ellu, // nnh_Latn_CM
+ 0x6E6F4E4F4C61746Ellu, // no_Latn_NO
+ 0x8DCD54484C616E61llu, // nod_Lana_TH
+ 0x91CD494E44657661llu, // noe_Deva_IN
+ 0xB5CD534552756E72llu, // non_Runr_SE
+ 0xBA0D474E4E6B6F6Fllu, // nqo_Nkoo_GN
+ 0x6E725A414C61746Ellu, // nr_Latn_ZA
+ 0xAA4D434143616E73llu, // nsk_Cans_CA
+ 0xBA4D5A414C61746Ellu, // nso_Latn_ZA
+ 0xCA8D53534C61746Ellu, // nus_Latn_SS
+ 0x6E7655534C61746Ellu, // nv_Latn_US
+ 0xC2ED434E4C61746Ellu, // nxq_Latn_CN
+ 0x6E794D574C61746Ellu, // ny_Latn_MW
+ 0xB30D545A4C61746Ellu, // nym_Latn_TZ
+ 0xB70D55474C61746Ellu, // nyn_Latn_UG
+ 0xA32D47484C61746Ellu, // nzi_Latn_GH
+ 0x6F6346524C61746Ellu, // oc_Latn_FR
+ 0x6F6D45544C61746Ellu, // om_Latn_ET
+ 0x6F72494E4F727961llu, // or_Orya_IN
+ 0x6F7347454379726Cllu, // os_Cyrl_GE
+ 0xAA6E4D4E4F726B68llu, // otk_Orkh_MN
+ 0x7061504B41726162llu, // pa_Arab_PK
+ 0x7061494E47757275llu, // pa_Guru_IN
+ 0x980F50484C61746Ellu, // pag_Latn_PH
+ 0xAC0F495250686C69llu, // pal_Phli_IR
+ 0xAC0F434E50686C70llu, // pal_Phlp_CN
+ 0xB00F50484C61746Ellu, // pam_Latn_PH
+ 0xBC0F41574C61746Ellu, // pap_Latn_AW
+ 0xD00F50574C61746Ellu, // pau_Latn_PW
+ 0x8C4F46524C61746Ellu, // pcd_Latn_FR
+ 0xB04F4E474C61746Ellu, // pcm_Latn_NG
+ 0x886F55534C61746Ellu, // pdc_Latn_US
+ 0xCC6F43414C61746Ellu, // pdt_Latn_CA
+ 0xB88F49525870656Fllu, // peo_Xpeo_IR
+ 0xACAF44454C61746Ellu, // pfl_Latn_DE
+ 0xB4EF4C4250686E78llu, // phn_Phnx_LB
+ 0x814F494E42726168llu, // pka_Brah_IN
+ 0xB94F4B454C61746Ellu, // pko_Latn_KE
+ 0x706C504C4C61746Ellu, // pl_Latn_PL
+ 0xC98F49544C61746Ellu, // pms_Latn_IT
+ 0xCDAF47524772656Bllu, // pnt_Grek_GR
+ 0xB5CF464D4C61746Ellu, // pon_Latn_FM
+ 0x822F504B4B686172llu, // pra_Khar_PK
+ 0x8E2F495241726162llu, // prd_Arab_IR
+ 0x7073414641726162llu, // ps_Arab_AF
+ 0x707442524C61746Ellu, // pt_Latn_BR
+ 0xD28F47414C61746Ellu, // puu_Latn_GA
+ 0x717550454C61746Ellu, // qu_Latn_PE
+ 0x8A9047544C61746Ellu, // quc_Latn_GT
+ 0x9A9045434C61746Ellu, // qug_Latn_EC
+ 0xA411494E44657661llu, // raj_Deva_IN
+ 0x945152454C61746Ellu, // rcf_Latn_RE
+ 0xA49149444C61746Ellu, // rej_Latn_ID
+ 0xB4D149544C61746Ellu, // rgn_Latn_IT
+ 0x8111494E4C61746Ellu, // ria_Latn_IN
+ 0x95114D4154666E67llu, // rif_Tfng_MA
+ 0xC9314E5044657661llu, // rjs_Deva_NP
+ 0xCD51424442656E67llu, // rkt_Beng_BD
+ 0x726D43484C61746Ellu, // rm_Latn_CH
+ 0x959146494C61746Ellu, // rmf_Latn_FI
+ 0xB99143484C61746Ellu, // rmo_Latn_CH
+ 0xCD91495241726162llu, // rmt_Arab_IR
+ 0xD19153454C61746Ellu, // rmu_Latn_SE
+ 0x726E42494C61746Ellu, // rn_Latn_BI
+ 0x99B14D5A4C61746Ellu, // rng_Latn_MZ
+ 0x726F524F4C61746Ellu, // ro_Latn_RO
+ 0x85D149444C61746Ellu, // rob_Latn_ID
+ 0x95D1545A4C61746Ellu, // rof_Latn_TZ
+ 0xB271464A4C61746Ellu, // rtm_Latn_FJ
+ 0x727552554379726Cllu, // ru_Cyrl_RU
+ 0x929155414379726Cllu, // rue_Cyrl_UA
+ 0x9A9153424C61746Ellu, // rug_Latn_SB
+ 0x727752574C61746Ellu, // rw_Latn_RW
+ 0xAAD1545A4C61746Ellu, // rwk_Latn_TZ
+ 0xD3114A504B616E61llu, // ryu_Kana_JP
+ 0x7361494E44657661llu, // sa_Deva_IN
+ 0x941247484C61746Ellu, // saf_Latn_GH
+ 0x9C1252554379726Cllu, // sah_Cyrl_RU
+ 0xC0124B454C61746Ellu, // saq_Latn_KE
+ 0xC81249444C61746Ellu, // sas_Latn_ID
+ 0xCC12494E4C61746Ellu, // sat_Latn_IN
+ 0xE412494E53617572llu, // saz_Saur_IN
+ 0xBC32545A4C61746Ellu, // sbp_Latn_TZ
+ 0x736349544C61746Ellu, // sc_Latn_IT
+ 0xA852494E44657661llu, // sck_Deva_IN
+ 0xB45249544C61746Ellu, // scn_Latn_IT
+ 0xB85247424C61746Ellu, // sco_Latn_GB
+ 0xC85243414C61746Ellu, // scs_Latn_CA
+ 0x7364504B41726162llu, // sd_Arab_PK
+ 0x7364494E44657661llu, // sd_Deva_IN
+ 0x7364494E4B686F6Allu, // sd_Khoj_IN
+ 0x7364494E53696E64llu, // sd_Sind_IN
+ 0x887249544C61746Ellu, // sdc_Latn_IT
+ 0x9C72495241726162llu, // sdh_Arab_IR
+ 0x73654E4F4C61746Ellu, // se_Latn_NO
+ 0x949243494C61746Ellu, // sef_Latn_CI
+ 0x9C924D5A4C61746Ellu, // seh_Latn_MZ
+ 0xA0924D584C61746Ellu, // sei_Latn_MX
+ 0xC8924D4C4C61746Ellu, // ses_Latn_ML
+ 0x736743464C61746Ellu, // sg_Latn_CF
+ 0x80D249454F67616Dllu, // sga_Ogam_IE
+ 0xC8D24C544C61746Ellu, // sgs_Latn_LT
+ 0xA0F24D4154666E67llu, // shi_Tfng_MA
+ 0xB4F24D4D4D796D72llu, // shn_Mymr_MM
+ 0x73694C4B53696E68llu, // si_Sinh_LK
+ 0x8D1245544C61746Ellu, // sid_Latn_ET
+ 0x736B534B4C61746Ellu, // sk_Latn_SK
+ 0xC552504B41726162llu, // skr_Arab_PK
+ 0x736C53494C61746Ellu, // sl_Latn_SI
+ 0xA172504C4C61746Ellu, // sli_Latn_PL
+ 0xE17249444C61746Ellu, // sly_Latn_ID
+ 0x736D57534C61746Ellu, // sm_Latn_WS
+ 0x819253454C61746Ellu, // sma_Latn_SE
+ 0xA59253454C61746Ellu, // smj_Latn_SE
+ 0xB59246494C61746Ellu, // smn_Latn_FI
+ 0xBD92494C53616D72llu, // smp_Samr_IL
+ 0xC99246494C61746Ellu, // sms_Latn_FI
+ 0x736E5A574C61746Ellu, // sn_Latn_ZW
+ 0xA9B24D4C4C61746Ellu, // snk_Latn_ML
+ 0x736F534F4C61746Ellu, // so_Latn_SO
+ 0xD1D2544854686169llu, // sou_Thai_TH
+ 0x7371414C4C61746Ellu, // sq_Latn_AL
+ 0x737252534379726Cllu, // sr_Cyrl_RS
+ 0x737252534C61746Ellu, // sr_Latn_RS
+ 0x8632494E536F7261llu, // srb_Sora_IN
+ 0xB63253524C61746Ellu, // srn_Latn_SR
+ 0xC632534E4C61746Ellu, // srr_Latn_SN
+ 0xDE32494E44657661llu, // srx_Deva_IN
+ 0x73735A414C61746Ellu, // ss_Latn_ZA
+ 0xE25245524C61746Ellu, // ssy_Latn_ER
+ 0x73745A414C61746Ellu, // st_Latn_ZA
+ 0xC27244454C61746Ellu, // stq_Latn_DE
+ 0x737549444C61746Ellu, // su_Latn_ID
+ 0xAA92545A4C61746Ellu, // suk_Latn_TZ
+ 0xCA92474E4C61746Ellu, // sus_Latn_GN
+ 0x737653454C61746Ellu, // sv_Latn_SE
+ 0x7377545A4C61746Ellu, // sw_Latn_TZ
+ 0x86D2595441726162llu, // swb_Arab_YT
+ 0x8AD243444C61746Ellu, // swc_Latn_CD
+ 0x9AD244454C61746Ellu, // swg_Latn_DE
+ 0xD6D2494E44657661llu, // swv_Deva_IN
+ 0xB6F249444C61746Ellu, // sxn_Latn_ID
+ 0xAF12424442656E67llu, // syl_Beng_BD
+ 0xC712495153797263llu, // syr_Syrc_IQ
+ 0xAF32504C4C61746Ellu, // szl_Latn_PL
+ 0x7461494E54616D6Cllu, // ta_Taml_IN
+ 0xA4134E5044657661llu, // taj_Deva_NP
+ 0xD83350484C61746Ellu, // tbw_Latn_PH
+ 0xE053494E4B6E6461llu, // tcy_Knda_IN
+ 0x8C73434E54616C65llu, // tdd_Tale_CN
+ 0x98734E5044657661llu, // tdg_Deva_NP
+ 0x9C734E5044657661llu, // tdh_Deva_NP
+ 0x7465494E54656C75llu, // te_Telu_IN
+ 0xB093534C4C61746Ellu, // tem_Latn_SL
+ 0xB89355474C61746Ellu, // teo_Latn_UG
+ 0xCC93544C4C61746Ellu, // tet_Latn_TL
+ 0x7467504B41726162llu, // tg_Arab_PK
+ 0x7467544A4379726Cllu, // tg_Cyrl_TJ
+ 0x7468544854686169llu, // th_Thai_TH
+ 0xACF34E5044657661llu, // thl_Deva_NP
+ 0xC0F34E5044657661llu, // thq_Deva_NP
+ 0xC4F34E5044657661llu, // thr_Deva_NP
+ 0x7469455445746869llu, // ti_Ethi_ET
+ 0x9913455245746869llu, // tig_Ethi_ER
+ 0xD5134E474C61746Ellu, // tiv_Latn_NG
+ 0x746B544D4C61746Ellu, // tk_Latn_TM
+ 0xAD53544B4C61746Ellu, // tkl_Latn_TK
+ 0xC553415A4C61746Ellu, // tkr_Latn_AZ
+ 0xCD534E5044657661llu, // tkt_Deva_NP
+ 0x746C50484C61746Ellu, // tl_Latn_PH
+ 0xE173415A4C61746Ellu, // tly_Latn_AZ
+ 0x9D934E454C61746Ellu, // tmh_Latn_NE
+ 0x746E5A414C61746Ellu, // tn_Latn_ZA
+ 0x746F544F4C61746Ellu, // to_Latn_TO
+ 0x99D34D574C61746Ellu, // tog_Latn_MW
+ 0xA1F350474C61746Ellu, // tpi_Latn_PG
+ 0x747254524C61746Ellu, // tr_Latn_TR
+ 0xD23354524C61746Ellu, // tru_Latn_TR
+ 0xD63354574C61746Ellu, // trv_Latn_TW
+ 0x74735A414C61746Ellu, // ts_Latn_ZA
+ 0x8E5347524772656Bllu, // tsd_Grek_GR
+ 0x96534E5044657661llu, // tsf_Deva_NP
+ 0x9A5350484C61746Ellu, // tsg_Latn_PH
+ 0xA653425454696274llu, // tsj_Tibt_BT
+ 0x747452554379726Cllu, // tt_Cyrl_RU
+ 0xA67355474C61746Ellu, // ttj_Latn_UG
+ 0xCA73544854686169llu, // tts_Thai_TH
+ 0xCE73415A4C61746Ellu, // ttt_Latn_AZ
+ 0xB2934D574C61746Ellu, // tum_Latn_MW
+ 0xAEB354564C61746Ellu, // tvl_Latn_TV
+ 0xC2D34E454C61746Ellu, // twq_Latn_NE
+ 0x747950464C61746Ellu, // ty_Latn_PF
+ 0xD71352554379726Cllu, // tyv_Cyrl_RU
+ 0xB3334D414C61746Ellu, // tzm_Latn_MA
+ 0xB07452554379726Cllu, // udm_Cyrl_RU
+ 0x7567434E41726162llu, // ug_Arab_CN
+ 0x75674B5A4379726Cllu, // ug_Cyrl_KZ
+ 0x80D4535955676172llu, // uga_Ugar_SY
+ 0x756B55414379726Cllu, // uk_Cyrl_UA
+ 0xA174464D4C61746Ellu, // uli_Latn_FM
+ 0x8594414F4C61746Ellu, // umb_Latn_AO
+ 0xC5B4494E42656E67llu, // unr_Beng_IN
+ 0xC5B44E5044657661llu, // unr_Deva_NP
+ 0xDDB4494E42656E67llu, // unx_Beng_IN
+ 0x7572504B41726162llu, // ur_Arab_PK
+ 0x757A414641726162llu, // uz_Arab_AF
+ 0x757A555A4C61746Ellu, // uz_Latn_UZ
+ 0xA0154C5256616969llu, // vai_Vaii_LR
+ 0x76655A414C61746Ellu, // ve_Latn_ZA
+ 0x889549544C61746Ellu, // vec_Latn_IT
+ 0xBC9552554C61746Ellu, // vep_Latn_RU
+ 0x7669564E4C61746Ellu, // vi_Latn_VN
+ 0x891553584C61746Ellu, // vic_Latn_SX
+ 0xC97542454C61746Ellu, // vls_Latn_BE
+ 0x959544454C61746Ellu, // vmf_Latn_DE
+ 0xD9954D5A4C61746Ellu, // vmw_Latn_MZ
+ 0xCDD552554C61746Ellu, // vot_Latn_RU
+ 0xBA3545454C61746Ellu, // vro_Latn_EE
+ 0xB695545A4C61746Ellu, // vun_Latn_TZ
+ 0x776142454C61746Ellu, // wa_Latn_BE
+ 0x901643484C61746Ellu, // wae_Latn_CH
+ 0xAC16455445746869llu, // wal_Ethi_ET
+ 0xC41650484C61746Ellu, // war_Latn_PH
+ 0xBC3641554C61746Ellu, // wbp_Latn_AU
+ 0xC036494E54656C75llu, // wbq_Telu_IN
+ 0xC436494E44657661llu, // wbr_Deva_IN
+ 0xC97657464C61746Ellu, // wls_Latn_WF
+ 0xA1B64B4D41726162llu, // wni_Arab_KM
+ 0x776F534E4C61746Ellu, // wo_Latn_SN
+ 0xB276494E44657661llu, // wtm_Deva_IN
+ 0xD296434E48616E73llu, // wuu_Hans_CN
+ 0xD41742524C61746Ellu, // xav_Latn_BR
+ 0xC457545243617269llu, // xcr_Cari_TR
+ 0x78685A414C61746Ellu, // xh_Latn_ZA
+ 0x897754524C796369llu, // xlc_Lyci_TR
+ 0x8D7754524C796469llu, // xld_Lydi_TR
+ 0x9597474547656F72llu, // xmf_Geor_GE
+ 0xB597434E4D616E69llu, // xmn_Mani_CN
+ 0xC59753444D657263llu, // xmr_Merc_SD
+ 0x81B753414E617262llu, // xna_Narb_SA
+ 0xC5B7494E44657661llu, // xnr_Deva_IN
+ 0x99D755474C61746Ellu, // xog_Latn_UG
+ 0xC5F7495250727469llu, // xpr_Prti_IR
+ 0x8257594553617262llu, // xsa_Sarb_YE
+ 0xC6574E5044657661llu, // xsr_Deva_NP
+ 0xB8184D5A4C61746Ellu, // yao_Latn_MZ
+ 0xBC18464D4C61746Ellu, // yap_Latn_FM
+ 0xD418434D4C61746Ellu, // yav_Latn_CM
+ 0x8438434D4C61746Ellu, // ybb_Latn_CM
+ 0x796F4E474C61746Ellu, // yo_Latn_NG
+ 0xAE3842524C61746Ellu, // yrl_Latn_BR
+ 0x82984D584C61746Ellu, // yua_Latn_MX
+ 0x7A61434E4C61746Ellu, // za_Latn_CN
+ 0x981953444C61746Ellu, // zag_Latn_SD
+ 0xA4794B4D41726162llu, // zdj_Arab_KM
+ 0x80994E4C4C61746Ellu, // zea_Latn_NL
+ 0x9CD94D4154666E67llu, // zgh_Tfng_MA
+ 0x7A685457426F706Fllu, // zh_Bopo_TW
+ 0x7A68434E48616E73llu, // zh_Hans_CN
+ 0x7A68545748616E74llu, // zh_Hant_TW
+ 0xA1994D594C61746Ellu, // zmi_Latn_MY
+ 0x7A755A414C61746Ellu, // zu_Latn_ZA
+ 0x833954524C61746Ellu, // zza_Latn_TR
+});
+
+const std::unordered_map<uint32_t, uint32_t> ARAB_PARENTS({
+ {0x6172445Au, 0x61729420u}, // ar-DZ -> ar-015
+ {0x61724548u, 0x61729420u}, // ar-EH -> ar-015
+ {0x61724C59u, 0x61729420u}, // ar-LY -> ar-015
+ {0x61724D41u, 0x61729420u}, // ar-MA -> ar-015
+ {0x6172544Eu, 0x61729420u}, // ar-TN -> ar-015
+});
+
+const std::unordered_map<uint32_t, uint32_t> HANT_PARENTS({
+ {0x7A684D4Fu, 0x7A68484Bu}, // zh-Hant-MO -> zh-Hant-HK
+});
+
+const std::unordered_map<uint32_t, uint32_t> LATN_PARENTS({
+ {0x656E80A1u, 0x656E8400u}, // en-150 -> en-001
+ {0x656E4147u, 0x656E8400u}, // en-AG -> en-001
+ {0x656E4149u, 0x656E8400u}, // en-AI -> en-001
+ {0x656E4154u, 0x656E80A1u}, // en-AT -> en-150
+ {0x656E4155u, 0x656E8400u}, // en-AU -> en-001
+ {0x656E4242u, 0x656E8400u}, // en-BB -> en-001
+ {0x656E4245u, 0x656E8400u}, // en-BE -> en-001
+ {0x656E424Du, 0x656E8400u}, // en-BM -> en-001
+ {0x656E4253u, 0x656E8400u}, // en-BS -> en-001
+ {0x656E4257u, 0x656E8400u}, // en-BW -> en-001
+ {0x656E425Au, 0x656E8400u}, // en-BZ -> en-001
+ {0x656E4341u, 0x656E8400u}, // en-CA -> en-001
+ {0x656E4343u, 0x656E8400u}, // en-CC -> en-001
+ {0x656E4348u, 0x656E80A1u}, // en-CH -> en-150
+ {0x656E434Bu, 0x656E8400u}, // en-CK -> en-001
+ {0x656E434Du, 0x656E8400u}, // en-CM -> en-001
+ {0x656E4358u, 0x656E8400u}, // en-CX -> en-001
+ {0x656E4359u, 0x656E8400u}, // en-CY -> en-001
+ {0x656E4445u, 0x656E80A1u}, // en-DE -> en-150
+ {0x656E4447u, 0x656E8400u}, // en-DG -> en-001
+ {0x656E444Bu, 0x656E80A1u}, // en-DK -> en-150
+ {0x656E444Du, 0x656E8400u}, // en-DM -> en-001
+ {0x656E4552u, 0x656E8400u}, // en-ER -> en-001
+ {0x656E4649u, 0x656E80A1u}, // en-FI -> en-150
+ {0x656E464Au, 0x656E8400u}, // en-FJ -> en-001
+ {0x656E464Bu, 0x656E8400u}, // en-FK -> en-001
+ {0x656E464Du, 0x656E8400u}, // en-FM -> en-001
+ {0x656E4742u, 0x656E8400u}, // en-GB -> en-001
+ {0x656E4744u, 0x656E8400u}, // en-GD -> en-001
+ {0x656E4747u, 0x656E8400u}, // en-GG -> en-001
+ {0x656E4748u, 0x656E8400u}, // en-GH -> en-001
+ {0x656E4749u, 0x656E8400u}, // en-GI -> en-001
+ {0x656E474Du, 0x656E8400u}, // en-GM -> en-001
+ {0x656E4759u, 0x656E8400u}, // en-GY -> en-001
+ {0x656E484Bu, 0x656E8400u}, // en-HK -> en-001
+ {0x656E4945u, 0x656E8400u}, // en-IE -> en-001
+ {0x656E494Cu, 0x656E8400u}, // en-IL -> en-001
+ {0x656E494Du, 0x656E8400u}, // en-IM -> en-001
+ {0x656E494Eu, 0x656E8400u}, // en-IN -> en-001
+ {0x656E494Fu, 0x656E8400u}, // en-IO -> en-001
+ {0x656E4A45u, 0x656E8400u}, // en-JE -> en-001
+ {0x656E4A4Du, 0x656E8400u}, // en-JM -> en-001
+ {0x656E4B45u, 0x656E8400u}, // en-KE -> en-001
+ {0x656E4B49u, 0x656E8400u}, // en-KI -> en-001
+ {0x656E4B4Eu, 0x656E8400u}, // en-KN -> en-001
+ {0x656E4B59u, 0x656E8400u}, // en-KY -> en-001
+ {0x656E4C43u, 0x656E8400u}, // en-LC -> en-001
+ {0x656E4C52u, 0x656E8400u}, // en-LR -> en-001
+ {0x656E4C53u, 0x656E8400u}, // en-LS -> en-001
+ {0x656E4D47u, 0x656E8400u}, // en-MG -> en-001
+ {0x656E4D4Fu, 0x656E8400u}, // en-MO -> en-001
+ {0x656E4D53u, 0x656E8400u}, // en-MS -> en-001
+ {0x656E4D54u, 0x656E8400u}, // en-MT -> en-001
+ {0x656E4D55u, 0x656E8400u}, // en-MU -> en-001
+ {0x656E4D57u, 0x656E8400u}, // en-MW -> en-001
+ {0x656E4D59u, 0x656E8400u}, // en-MY -> en-001
+ {0x656E4E41u, 0x656E8400u}, // en-NA -> en-001
+ {0x656E4E46u, 0x656E8400u}, // en-NF -> en-001
+ {0x656E4E47u, 0x656E8400u}, // en-NG -> en-001
+ {0x656E4E4Cu, 0x656E80A1u}, // en-NL -> en-150
+ {0x656E4E52u, 0x656E8400u}, // en-NR -> en-001
+ {0x656E4E55u, 0x656E8400u}, // en-NU -> en-001
+ {0x656E4E5Au, 0x656E8400u}, // en-NZ -> en-001
+ {0x656E5047u, 0x656E8400u}, // en-PG -> en-001
+ {0x656E5048u, 0x656E8400u}, // en-PH -> en-001
+ {0x656E504Bu, 0x656E8400u}, // en-PK -> en-001
+ {0x656E504Eu, 0x656E8400u}, // en-PN -> en-001
+ {0x656E5057u, 0x656E8400u}, // en-PW -> en-001
+ {0x656E5257u, 0x656E8400u}, // en-RW -> en-001
+ {0x656E5342u, 0x656E8400u}, // en-SB -> en-001
+ {0x656E5343u, 0x656E8400u}, // en-SC -> en-001
+ {0x656E5344u, 0x656E8400u}, // en-SD -> en-001
+ {0x656E5345u, 0x656E80A1u}, // en-SE -> en-150
+ {0x656E5347u, 0x656E8400u}, // en-SG -> en-001
+ {0x656E5348u, 0x656E8400u}, // en-SH -> en-001
+ {0x656E5349u, 0x656E80A1u}, // en-SI -> en-150
+ {0x656E534Cu, 0x656E8400u}, // en-SL -> en-001
+ {0x656E5353u, 0x656E8400u}, // en-SS -> en-001
+ {0x656E5358u, 0x656E8400u}, // en-SX -> en-001
+ {0x656E535Au, 0x656E8400u}, // en-SZ -> en-001
+ {0x656E5443u, 0x656E8400u}, // en-TC -> en-001
+ {0x656E544Bu, 0x656E8400u}, // en-TK -> en-001
+ {0x656E544Fu, 0x656E8400u}, // en-TO -> en-001
+ {0x656E5454u, 0x656E8400u}, // en-TT -> en-001
+ {0x656E5456u, 0x656E8400u}, // en-TV -> en-001
+ {0x656E545Au, 0x656E8400u}, // en-TZ -> en-001
+ {0x656E5547u, 0x656E8400u}, // en-UG -> en-001
+ {0x656E5643u, 0x656E8400u}, // en-VC -> en-001
+ {0x656E5647u, 0x656E8400u}, // en-VG -> en-001
+ {0x656E5655u, 0x656E8400u}, // en-VU -> en-001
+ {0x656E5753u, 0x656E8400u}, // en-WS -> en-001
+ {0x656E5A41u, 0x656E8400u}, // en-ZA -> en-001
+ {0x656E5A4Du, 0x656E8400u}, // en-ZM -> en-001
+ {0x656E5A57u, 0x656E8400u}, // en-ZW -> en-001
+ {0x65734152u, 0x6573A424u}, // es-AR -> es-419
+ {0x6573424Fu, 0x6573A424u}, // es-BO -> es-419
+ {0x6573434Cu, 0x6573A424u}, // es-CL -> es-419
+ {0x6573434Fu, 0x6573A424u}, // es-CO -> es-419
+ {0x65734352u, 0x6573A424u}, // es-CR -> es-419
+ {0x65734355u, 0x6573A424u}, // es-CU -> es-419
+ {0x6573444Fu, 0x6573A424u}, // es-DO -> es-419
+ {0x65734543u, 0x6573A424u}, // es-EC -> es-419
+ {0x65734754u, 0x6573A424u}, // es-GT -> es-419
+ {0x6573484Eu, 0x6573A424u}, // es-HN -> es-419
+ {0x65734D58u, 0x6573A424u}, // es-MX -> es-419
+ {0x65734E49u, 0x6573A424u}, // es-NI -> es-419
+ {0x65735041u, 0x6573A424u}, // es-PA -> es-419
+ {0x65735045u, 0x6573A424u}, // es-PE -> es-419
+ {0x65735052u, 0x6573A424u}, // es-PR -> es-419
+ {0x65735059u, 0x6573A424u}, // es-PY -> es-419
+ {0x65735356u, 0x6573A424u}, // es-SV -> es-419
+ {0x65735553u, 0x6573A424u}, // es-US -> es-419
+ {0x65735559u, 0x6573A424u}, // es-UY -> es-419
+ {0x65735645u, 0x6573A424u}, // es-VE -> es-419
+ {0x7074414Fu, 0x70745054u}, // pt-AO -> pt-PT
+ {0x70744356u, 0x70745054u}, // pt-CV -> pt-PT
+ {0x70744757u, 0x70745054u}, // pt-GW -> pt-PT
+ {0x70744D4Fu, 0x70745054u}, // pt-MO -> pt-PT
+ {0x70744D5Au, 0x70745054u}, // pt-MZ -> pt-PT
+ {0x70745354u, 0x70745054u}, // pt-ST -> pt-PT
+ {0x7074544Cu, 0x70745054u}, // pt-TL -> pt-PT
+});
+
+const struct {
+ const char script[4];
+ const std::unordered_map<uint32_t, uint32_t>* map;
+} SCRIPT_PARENTS[] = {
+ {{'A', 'r', 'a', 'b'}, &ARAB_PARENTS},
+ {{'H', 'a', 'n', 't'}, &HANT_PARENTS},
+ {{'L', 'a', 't', 'n'}, &LATN_PARENTS},
+};
+
+const size_t MAX_PARENT_DEPTH = 3;
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 62aabb13858d..c3dfb89180de 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -24,7 +24,9 @@
#include <stdlib.h>
#include <string.h>
+#include <algorithm>
#include <limits>
+#include <memory>
#include <type_traits>
#include <androidfw/ByteBucketArray.h>
@@ -47,7 +49,7 @@
namespace android {
-#ifdef HAVE_WINSOCK
+#if defined(_WIN32)
#undef nhtol
#undef htonl
#define ntohl(x) ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) )
@@ -560,7 +562,7 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData)
if ((mHeader->flags&ResStringPool_header::UTF8_FLAG &&
((uint8_t*)mStrings)[mStringPoolSize-1] != 0) ||
- (!mHeader->flags&ResStringPool_header::UTF8_FLAG &&
+ (!(mHeader->flags&ResStringPool_header::UTF8_FLAG) &&
((uint16_t*)mStrings)[mStringPoolSize-1] != 0)) {
ALOGW("Bad string block: last string is not 0-terminated\n");
return (mError=BAD_TYPE);
@@ -727,7 +729,7 @@ const char16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const
AutoMutex lock(mDecodeLock);
if (mCache == NULL) {
-#ifndef HAVE_ANDROID_OS
+#ifndef __ANDROID__
if (kDebugStringPoolNoisy) {
ALOGI("CREATING STRING CACHE OF %zu bytes",
mHeader->stringCount*sizeof(char16_t**));
@@ -1868,7 +1870,10 @@ void ResTable_config::swapHtoD() {
}
// The language & region are equal, so compare the scripts and variants.
- int script = memcmp(l.localeScript, r.localeScript, sizeof(l.localeScript));
+ const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
+ const char *lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript;
+ const char *rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript;
+ int script = memcmp(lScript, rScript, sizeof(l.localeScript));
if (script) {
return script;
}
@@ -2012,11 +2017,11 @@ int ResTable_config::isLocaleMoreSpecificThan(const ResTable_config& o) const {
// scripts since it seems more useful to do so. We will consider
// "en-US-POSIX" to be more specific than "en-Latn-US".
- const int score = ((localeScript[0] != 0) ? 1 : 0) +
- ((localeVariant[0] != 0) ? 2 : 0);
+ const int score = ((localeScript[0] != '\0' && !localeScriptWasComputed) ? 1 : 0) +
+ ((localeVariant[0] != '\0') ? 2 : 0);
- const int oScore = ((o.localeScript[0] != 0) ? 1 : 0) +
- ((o.localeVariant[0] != 0) ? 2 : 0);
+ const int oScore = (o.localeScript[0] != '\0' && !o.localeScriptWasComputed ? 1 : 0) +
+ ((o.localeVariant[0] != '\0') ? 2 : 0);
return score - oScore;
@@ -2165,6 +2170,88 @@ bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const {
return false;
}
+bool ResTable_config::isLocaleBetterThan(const ResTable_config& o,
+ const ResTable_config* requested) const {
+ if (requested->locale == 0) {
+ // The request doesn't have a locale, so no resource is better
+ // than the other.
+ return false;
+ }
+
+ if (locale == 0 && o.locale == 0) {
+ // The locales parts of both resources are empty, so no one is better
+ // than the other.
+ return false;
+ }
+
+ // Non-matching locales have been filtered out, so both resources
+ // match the requested locale.
+ //
+ // Because of the locale-related checks in match() and the checks, we know
+ // that:
+ // 1) The resource languages are either empty or match the request;
+ // and
+ // 2) If the request's script is known, the resource scripts are either
+ // unknown or match the request.
+
+ if (language[0] != o.language[0]) {
+ // The languages of the two resources are not the same. We can only
+ // assume that one of the two resources matched the request because one
+ // doesn't have a language and the other has a matching language.
+ //
+ // We consider the one that has the language specified a better match.
+ //
+ // The exception is that we consider no-language resources a better match
+ // for US English and similar locales than locales that are a descendant
+ // of Internatinal English (en-001), since no-language resources are
+ // where the US English resource have traditionally lived for most apps.
+ if (requested->language[0] == 'e' && requested->language[1] == 'n') {
+ if (requested->country[0] == 'U' && requested->country[1] == 'S') {
+ // For US English itself, we consider a no-locale resource a
+ // better match if the other resource has a country other than
+ // US specified.
+ if (language[0] != '\0') {
+ return country[0] == '\0' || (country[0] == 'U' && country[1] == 'S');
+ } else {
+ return !(o.country[0] == '\0' || (o.country[0] == 'U' && o.country[1] == 'S'));
+ }
+ } else if (localeDataIsCloseToUsEnglish(requested->country)) {
+ if (language[0] != '\0') {
+ return localeDataIsCloseToUsEnglish(country);
+ } else {
+ return !localeDataIsCloseToUsEnglish(o.country);
+ }
+ }
+ }
+ return (language[0] != '\0');
+ }
+
+ // If we are here, both the resources have the same non-empty language as
+ // the request.
+ //
+ // Because the languages are the same, computeScript() always
+ // returns a non-empty script for languages it knows about, and we have passed
+ // the script checks in match(), the scripts are either all unknown or are
+ // all the same. So we can't gain anything by checking the scripts. We need
+ // to check the region and variant.
+
+ // See if any of the regions is better than the other
+ const int region_comparison = localeDataCompareRegions(
+ country, o.country,
+ language, requested->localeScript, requested->country);
+ if (region_comparison != 0) {
+ return (region_comparison > 0);
+ }
+
+ // The regions are the same. Try the variant.
+ if (requested->localeVariant[0] != '\0'
+ && strncmp(localeVariant, requested->localeVariant, sizeof(localeVariant)) == 0) {
+ return (strncmp(o.localeVariant, requested->localeVariant, sizeof(localeVariant)) != 0);
+ }
+
+ return false;
+}
+
bool ResTable_config::isBetterThan(const ResTable_config& o,
const ResTable_config* requested) const {
if (requested) {
@@ -2178,26 +2265,8 @@ bool ResTable_config::isBetterThan(const ResTable_config& o,
}
}
- if (locale || o.locale) {
- if ((language[0] != o.language[0]) && requested->language[0]) {
- return (language[0]);
- }
-
- if ((country[0] != o.country[0]) && requested->country[0]) {
- return (country[0]);
- }
- }
-
- if (localeScript[0] || o.localeScript[0]) {
- if (localeScript[0] != o.localeScript[0] && requested->localeScript[0]) {
- return localeScript[0];
- }
- }
-
- if (localeVariant[0] || o.localeVariant[0]) {
- if (localeVariant[0] != o.localeVariant[0] && requested->localeVariant[0]) {
- return localeVariant[0];
- }
+ if (isLocaleBetterThan(o, requested)) {
+ return true;
}
if (screenLayout || o.screenLayout) {
@@ -2445,20 +2514,51 @@ bool ResTable_config::match(const ResTable_config& settings) const {
}
}
if (locale != 0) {
- // Don't consider the script & variants when deciding matches.
+ // Don't consider country and variants when deciding matches.
+ // (Theoretically, the variant can also affect the script. For
+ // example, "ar-alalc97" probably implies the Latin script, but since
+ // CLDR doesn't support getting likely scripts for that, we'll assume
+ // the variant doesn't change the script.)
//
- // If we two configs differ only in their script or language, they
- // can be weeded out in the isMoreSpecificThan test.
- if (language[0] != 0
- && (language[0] != settings.language[0]
- || language[1] != settings.language[1])) {
+ // If two configs differ only in their country and variant,
+ // they can be weeded out in the isMoreSpecificThan test.
+ if (language[0] != settings.language[0] || language[1] != settings.language[1]) {
return false;
}
- if (country[0] != 0
- && (country[0] != settings.country[0]
- || country[1] != settings.country[1])) {
- return false;
+ // For backward compatibility and supporting private-use locales, we
+ // fall back to old behavior if we couldn't determine the script for
+ // either of the desired locale or the provided locale. But if we could determine
+ // the scripts, they should be the same for the locales to match.
+ bool countriesMustMatch = false;
+ char computed_script[4];
+ const char* script;
+ if (settings.localeScript[0] == '\0') { // could not determine the request's script
+ countriesMustMatch = true;
+ } else {
+ if (localeScript[0] == '\0' && !localeScriptWasComputed) {
+ // script was not provided or computed, so we try to compute it
+ localeDataComputeScript(computed_script, language, country);
+ if (computed_script[0] == '\0') { // we could not compute the script
+ countriesMustMatch = true;
+ } else {
+ script = computed_script;
+ }
+ } else { // script was provided, so just use it
+ script = localeScript;
+ }
+ }
+
+ if (countriesMustMatch) {
+ if (country[0] != '\0'
+ && (country[0] != settings.country[0]
+ || country[1] != settings.country[1])) {
+ return false;
+ }
+ } else {
+ if (memcmp(script, settings.localeScript, sizeof(settings.localeScript)) != 0) {
+ return false;
+ }
}
}
@@ -2586,8 +2686,8 @@ void ResTable_config::appendDirLocale(String8& out) const {
if (!language[0]) {
return;
}
-
- if (!localeScript[0] && !localeVariant[0]) {
+ const bool scriptWasProvided = localeScript[0] != '\0' && !localeScriptWasComputed;
+ if (!scriptWasProvided && !localeVariant[0]) {
// Legacy format.
if (out.size() > 0) {
out.append("-");
@@ -2605,7 +2705,7 @@ void ResTable_config::appendDirLocale(String8& out) const {
return;
}
- // We are writing the modified bcp47 tag.
+ // We are writing the modified BCP 47 tag.
// It starts with 'b+' and uses '+' as a separator.
if (out.size() > 0) {
@@ -2617,7 +2717,7 @@ void ResTable_config::appendDirLocale(String8& out) const {
size_t len = unpackLanguage(buf);
out.append(buf, len);
- if (localeScript[0]) {
+ if (scriptWasProvided) {
out.append("+");
out.append(localeScript, sizeof(localeScript));
}
@@ -2630,7 +2730,7 @@ void ResTable_config::appendDirLocale(String8& out) const {
if (localeVariant[0]) {
out.append("+");
- out.append(localeVariant, sizeof(localeVariant));
+ out.append(localeVariant, strnlen(localeVariant, sizeof(localeVariant)));
}
}
@@ -2648,7 +2748,7 @@ void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN]) const {
charsWritten += unpackLanguage(str);
}
- if (localeScript[0]) {
+ if (localeScript[0] && !localeScriptWasComputed) {
if (charsWritten) {
str[charsWritten++] = '-';
}
@@ -2682,11 +2782,15 @@ void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN]) const {
config->language[0] ? config->packRegion(start) : config->packLanguage(start);
break;
case 4:
- config->localeScript[0] = toupper(start[0]);
- for (size_t i = 1; i < 4; ++i) {
- config->localeScript[i] = tolower(start[i]);
+ if ('0' <= start[0] && start[0] <= '9') {
+ // this is a variant, so fall through
+ } else {
+ config->localeScript[0] = toupper(start[0]);
+ for (size_t i = 1; i < 4; ++i) {
+ config->localeScript[i] = tolower(start[i]);
+ }
+ break;
}
- break;
case 5:
case 6:
case 7:
@@ -2720,6 +2824,10 @@ void ResTable_config::setBcp47Locale(const char* in) {
const size_t size = in + strlen(in) - start;
assignLocaleComponent(this, start, size);
+ localeScriptWasComputed = (localeScript[0] == '\0');
+ if (localeScriptWasComputed) {
+ computeScript();
+ }
}
String8 ResTable_config::toString() const {
@@ -3080,13 +3188,15 @@ struct ResTable::Package
// table that defined the package); the ones after are skins on top of it.
struct ResTable::PackageGroup
{
- PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id)
+ PackageGroup(
+ ResTable* _owner, const String16& _name, uint32_t _id,
+ bool appAsLib, bool _isSystemAsset)
: owner(_owner)
, name(_name)
, id(_id)
, largestTypeId(0)
- , bags(NULL)
- , dynamicRefTable(static_cast<uint8_t>(_id))
+ , dynamicRefTable(static_cast<uint8_t>(_id), appAsLib)
+ , isSystemAsset(_isSystemAsset)
{ }
~PackageGroup() {
@@ -3111,36 +3221,41 @@ struct ResTable::PackageGroup
}
}
+ /**
+ * Clear all cache related data that depends on parameters/configuration.
+ * This includes the bag caches and filtered types.
+ */
void clearBagCache() {
- if (bags) {
+ for (size_t i = 0; i < typeCacheEntries.size(); i++) {
if (kDebugTableNoisy) {
- printf("bags=%p\n", bags);
+ printf("type=%zu\n", i);
}
- for (size_t i = 0; i < bags->size(); i++) {
+ const TypeList& typeList = types[i];
+ if (!typeList.isEmpty()) {
+ TypeCacheEntry& cacheEntry = typeCacheEntries.editItemAt(i);
+
+ // Reset the filtered configurations.
+ cacheEntry.filteredConfigs.clear();
+
+ bag_set** typeBags = cacheEntry.cachedBags;
if (kDebugTableNoisy) {
- printf("type=%zu\n", i);
+ printf("typeBags=%p\n", typeBags);
}
- const TypeList& typeList = types[i];
- if (!typeList.isEmpty()) {
- bag_set** typeBags = bags->get(i);
+
+ if (typeBags) {
+ const size_t N = typeList[0]->entryCount;
if (kDebugTableNoisy) {
- printf("typeBags=%p\n", typeBags);
+ printf("type->entryCount=%zu\n", N);
}
- if (typeBags) {
- const size_t N = typeList[0]->entryCount;
- if (kDebugTableNoisy) {
- printf("type->entryCount=%zu\n", N);
+ for (size_t j = 0; j < N; j++) {
+ if (typeBags[j] && typeBags[j] != (bag_set*)0xFFFFFFFF) {
+ free(typeBags[j]);
}
- for (size_t j = 0; j < N; j++) {
- if (typeBags[j] && typeBags[j] != (bag_set*)0xFFFFFFFF)
- free(typeBags[j]);
- }
- free(typeBags);
}
+ free(typeBags);
+ cacheEntry.cachedBags = NULL;
}
}
- delete bags;
- bags = NULL;
}
}
@@ -3168,9 +3283,11 @@ struct ResTable::PackageGroup
uint8_t largestTypeId;
- // Computed attribute bags, first indexed by the type and second
- // by the entry in that type.
- ByteBucketArray<bag_set**>* bags;
+ // Cached objects dependent on the parameters/configuration of this ResTable.
+ // Gets cleared whenever the parameters/configuration changes.
+ // These are stored here in a parallel structure because the data in `types` may
+ // be shared by other ResTable's (framework resources are shared this way).
+ ByteBucketArray<TypeCacheEntry> typeCacheEntries;
// The table mapping dynamic references to resolved references for
// this package group.
@@ -3178,14 +3295,10 @@ struct ResTable::PackageGroup
// by having these tables in a per-package scope rather than
// per-package-group.
DynamicRefTable dynamicRefTable;
-};
-struct ResTable::bag_set
-{
- size_t numAttrs; // number in array
- size_t availAttrs; // total space in array
- uint32_t typeSpecFlags;
- // Followed by 'numAttr' bag_entry structures.
+ // If the package group comes from a system asset. Used in
+ // determining non-system locales.
+ const bool isSystemAsset;
};
ResTable::Theme::Theme(const ResTable& table)
@@ -3532,7 +3645,7 @@ ResTable::ResTable(const void* data, size_t size, const int32_t cookie, bool cop
{
memset(&mParams, 0, sizeof(mParams));
memset(mPackageMap, 0, sizeof(mPackageMap));
- addInternal(data, size, NULL, 0, cookie, copyData);
+ addInternal(data, size, NULL, 0, false, cookie, copyData);
LOG_FATAL_IF(mError != NO_ERROR, "Error parsing resource table");
if (kDebugTableSuperNoisy) {
ALOGI("Creating ResTable %p\n", this);
@@ -3553,12 +3666,12 @@ inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const
}
status_t ResTable::add(const void* data, size_t size, const int32_t cookie, bool copyData) {
- return addInternal(data, size, NULL, 0, cookie, copyData);
+ return addInternal(data, size, NULL, 0, false, cookie, copyData);
}
status_t ResTable::add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,
- const int32_t cookie, bool copyData) {
- return addInternal(data, size, idmapData, idmapDataSize, cookie, copyData);
+ const int32_t cookie, bool copyData, bool appAsLib) {
+ return addInternal(data, size, idmapData, idmapDataSize, appAsLib, cookie, copyData);
}
status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) {
@@ -3568,10 +3681,13 @@ status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) {
return UNKNOWN_ERROR;
}
- return addInternal(data, static_cast<size_t>(asset->getLength()), NULL, 0, cookie, copyData);
+ return addInternal(data, static_cast<size_t>(asset->getLength()), NULL, false, 0, cookie,
+ copyData);
}
-status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData) {
+status_t ResTable::add(
+ Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData,
+ bool appAsLib, bool isSystemAsset) {
const void* data = asset->getBuffer(true);
if (data == NULL) {
ALOGW("Unable to get buffer of resource asset file");
@@ -3590,20 +3706,21 @@ status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bo
}
return addInternal(data, static_cast<size_t>(asset->getLength()),
- idmapData, idmapSize, cookie, copyData);
+ idmapData, idmapSize, appAsLib, cookie, copyData, isSystemAsset);
}
-status_t ResTable::add(ResTable* src)
+status_t ResTable::add(ResTable* src, bool isSystemAsset)
{
mError = src->mError;
- for (size_t i=0; i<src->mHeaders.size(); i++) {
+ for (size_t i=0; i < src->mHeaders.size(); i++) {
mHeaders.add(src->mHeaders[i]);
}
- for (size_t i=0; i<src->mPackageGroups.size(); i++) {
+ for (size_t i=0; i < src->mPackageGroups.size(); i++) {
PackageGroup* srcPg = src->mPackageGroups[i];
- PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id);
+ PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id,
+ false /* appAsLib */, isSystemAsset || srcPg->isSystemAsset);
for (size_t j=0; j<srcPg->packages.size(); j++) {
pg->packages.add(srcPg->packages[j]);
}
@@ -3644,7 +3761,7 @@ status_t ResTable::addEmpty(const int32_t cookie) {
}
status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize,
- const int32_t cookie, bool copyData)
+ bool appAsLib, const int32_t cookie, bool copyData, bool isSystemAsset)
{
if (!data) {
return NO_ERROR;
@@ -3747,7 +3864,8 @@ status_t ResTable::addInternal(const void* data, size_t dataSize, const void* id
return (mError=BAD_TYPE);
}
- if (parsePackage((ResTable_package*)chunk, header) != NO_ERROR) {
+ if (parsePackage(
+ (ResTable_package*)chunk, header, appAsLib, isSystemAsset) != NO_ERROR) {
return mError;
}
curPackage++;
@@ -3818,7 +3936,9 @@ bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* ou
if (Res_GETPACKAGE(resID)+1 == 0) {
ALOGW("No package identifier when getting name for resource number 0x%08x", resID);
} else {
+#ifndef STATIC_ANDROIDFW_FOR_TOOLS
ALOGW("No known package when getting name for resource number 0x%08x", resID);
+#endif
}
return false;
}
@@ -4074,39 +4194,32 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
}
// First see if we've already computed this bag...
- if (grp->bags) {
- bag_set** typeSet = grp->bags->get(t);
- if (typeSet) {
- bag_set* set = typeSet[e];
- if (set) {
- if (set != (bag_set*)0xFFFFFFFF) {
- if (outTypeSpecFlags != NULL) {
- *outTypeSpecFlags = set->typeSpecFlags;
- }
- *outBag = (bag_entry*)(set+1);
- if (kDebugTableSuperNoisy) {
- ALOGI("Found existing bag for: 0x%x\n", resID);
- }
- return set->numAttrs;
+ TypeCacheEntry& cacheEntry = grp->typeCacheEntries.editItemAt(t);
+ bag_set** typeSet = cacheEntry.cachedBags;
+ if (typeSet) {
+ bag_set* set = typeSet[e];
+ if (set) {
+ if (set != (bag_set*)0xFFFFFFFF) {
+ if (outTypeSpecFlags != NULL) {
+ *outTypeSpecFlags = set->typeSpecFlags;
}
- ALOGW("Attempt to retrieve bag 0x%08x which is invalid or in a cycle.",
- resID);
- return BAD_INDEX;
+ *outBag = (bag_entry*)(set+1);
+ if (kDebugTableSuperNoisy) {
+ ALOGI("Found existing bag for: 0x%x\n", resID);
+ }
+ return set->numAttrs;
}
+ ALOGW("Attempt to retrieve bag 0x%08x which is invalid or in a cycle.",
+ resID);
+ return BAD_INDEX;
}
}
// Bag not found, we need to compute it!
- if (!grp->bags) {
- grp->bags = new ByteBucketArray<bag_set**>();
- if (!grp->bags) return NO_MEMORY;
- }
-
- bag_set** typeSet = grp->bags->get(t);
if (!typeSet) {
typeSet = (bag_set**)calloc(NENTRY, sizeof(bag_set*));
if (!typeSet) return NO_MEMORY;
- grp->bags->set(t, typeSet);
+ cacheEntry.cachedBags = typeSet;
}
// Mark that we are currently working on this one.
@@ -4312,18 +4425,56 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
void ResTable::setParameters(const ResTable_config* params)
{
- mLock.lock();
+ AutoMutex _lock(mLock);
+ AutoMutex _lock2(mFilteredConfigLock);
+
if (kDebugTableGetEntry) {
ALOGI("Setting parameters: %s\n", params->toString().string());
}
mParams = *params;
- for (size_t i=0; i<mPackageGroups.size(); i++) {
+ for (size_t p = 0; p < mPackageGroups.size(); p++) {
+ PackageGroup* packageGroup = mPackageGroups.editItemAt(p);
if (kDebugTableNoisy) {
- ALOGI("CLEARING BAGS FOR GROUP %zu!", i);
+ ALOGI("CLEARING BAGS FOR GROUP %zu!", p);
+ }
+ packageGroup->clearBagCache();
+
+ // Find which configurations match the set of parameters. This allows for a much
+ // faster lookup in getEntry() if the set of values is narrowed down.
+ for (size_t t = 0; t < packageGroup->types.size(); t++) {
+ if (packageGroup->types[t].isEmpty()) {
+ continue;
+ }
+
+ TypeList& typeList = packageGroup->types.editItemAt(t);
+
+ // Retrieve the cache entry for this type.
+ TypeCacheEntry& cacheEntry = packageGroup->typeCacheEntries.editItemAt(t);
+
+ for (size_t ts = 0; ts < typeList.size(); ts++) {
+ Type* type = typeList.editItemAt(ts);
+
+ std::shared_ptr<Vector<const ResTable_type*>> newFilteredConfigs =
+ std::make_shared<Vector<const ResTable_type*>>();
+
+ for (size_t ti = 0; ti < type->configs.size(); ti++) {
+ ResTable_config config;
+ config.copyFromDtoH(type->configs[ti]->config);
+
+ if (config.match(mParams)) {
+ newFilteredConfigs->add(type->configs[ti]);
+ }
+ }
+
+ if (kDebugTableNoisy) {
+ ALOGD("Updating pkg=%zu type=%zu with %zu filtered configs",
+ p, t, newFilteredConfigs->size());
+ }
+
+ cacheEntry.filteredConfigs.add(newFilteredConfigs);
+ }
}
- mPackageGroups[i]->clearBagCache();
}
- mLock.unlock();
}
void ResTable::getParameters(ResTable_config* params) const
@@ -5660,11 +5811,22 @@ const DynamicRefTable* ResTable::getDynamicRefTableForCookie(int32_t cookie) con
return NULL;
}
-void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMipmap) const
-{
+static bool compareResTableConfig(const ResTable_config& a, const ResTable_config& b) {
+ return a.compare(b) < 0;
+}
+
+void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMipmap,
+ bool ignoreAndroidPackage, bool includeSystemConfigs) const {
const size_t packageCount = mPackageGroups.size();
+ String16 android("android");
for (size_t i = 0; i < packageCount; i++) {
const PackageGroup* packageGroup = mPackageGroups[i];
+ if (ignoreAndroidPackage && android == packageGroup->name) {
+ continue;
+ }
+ if (!includeSystemConfigs && packageGroup->isSystemAsset) {
+ continue;
+ }
const size_t typeCount = packageGroup->types.size();
for (size_t j = 0; j < typeCount; j++) {
const TypeList& typeList = packageGroup->types[j];
@@ -5683,17 +5845,11 @@ void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMi
ResTable_config cfg;
memset(&cfg, 0, sizeof(ResTable_config));
cfg.copyFromDtoH(config->config);
- // only insert unique
- const size_t N = configs->size();
- size_t n;
- for (n = 0; n < N; n++) {
- if (0 == (*configs)[n].compare(cfg)) {
- break;
- }
- }
- // if we didn't find it
- if (n == N) {
- configs->add(cfg);
+
+ auto iter = std::lower_bound(configs->begin(), configs->end(), cfg,
+ compareResTableConfig);
+ if (iter == configs->end() || iter->compare(cfg) != 0) {
+ configs->insertAt(cfg, std::distance(configs->begin(), iter));
}
}
}
@@ -5701,26 +5857,29 @@ void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMi
}
}
-void ResTable::getLocales(Vector<String8>* locales) const
+static bool compareString8AndCString(const String8& str, const char* cStr) {
+ return strcmp(str.string(), cStr) < 0;
+}
+
+void ResTable::getLocales(Vector<String8>* locales, bool includeSystemLocales) const
{
Vector<ResTable_config> configs;
ALOGV("calling getConfigurations");
- getConfigurations(&configs);
+ getConfigurations(&configs,
+ false /* ignoreMipmap */,
+ false /* ignoreAndroidPackage */,
+ includeSystemLocales /* includeSystemConfigs */);
ALOGV("called getConfigurations size=%d", (int)configs.size());
const size_t I = configs.size();
char locale[RESTABLE_MAX_LOCALE_LEN];
for (size_t i=0; i<I; i++) {
configs[i].getBcp47Locale(locale);
- const size_t J = locales->size();
- size_t j;
- for (j=0; j<J; j++) {
- if (0 == strcmp(locale, (*locales)[j].string())) {
- break;
- }
- }
- if (j == J) {
- locales->add(String8(locale));
+
+ auto iter = std::lower_bound(locales->begin(), locales->end(), locale,
+ compareString8AndCString);
+ if (iter == locales->end() || strcmp(iter->string(), locale) != 0) {
+ locales->insertAt(String8(locale), std::distance(locales->begin(), iter));
}
}
}
@@ -5846,9 +6005,32 @@ status_t ResTable::getEntry(
specFlags = -1;
}
- const size_t numConfigs = typeSpec->configs.size();
+ const Vector<const ResTable_type*>* candidateConfigs = &typeSpec->configs;
+
+ std::shared_ptr<Vector<const ResTable_type*>> filteredConfigs;
+ if (config && memcmp(&mParams, config, sizeof(mParams)) == 0) {
+ // Grab the lock first so we can safely get the current filtered list.
+ AutoMutex _lock(mFilteredConfigLock);
+
+ // This configuration is equal to the one we have previously cached for,
+ // so use the filtered configs.
+
+ const TypeCacheEntry& cacheEntry = packageGroup->typeCacheEntries[typeIndex];
+ if (i < cacheEntry.filteredConfigs.size()) {
+ if (cacheEntry.filteredConfigs[i]) {
+ // Grab a reference to the shared_ptr so it doesn't get destroyed while
+ // going through this list.
+ filteredConfigs = cacheEntry.filteredConfigs[i];
+
+ // Use this filtered list.
+ candidateConfigs = filteredConfigs.get();
+ }
+ }
+ }
+
+ const size_t numConfigs = candidateConfigs->size();
for (size_t c = 0; c < numConfigs; c++) {
- const ResTable_type* const thisType = typeSpec->configs[c];
+ const ResTable_type* const thisType = candidateConfigs->itemAt(c);
if (thisType == NULL) {
continue;
}
@@ -5931,7 +6113,7 @@ status_t ResTable::getEntry(
}
status_t ResTable::parsePackage(const ResTable_package* const pkg,
- const Header* const header)
+ const Header* const header, bool appAsLib, bool isSystemAsset)
{
const uint8_t* base = (const uint8_t*)pkg;
status_t err = validate_chunk(&pkg->header, sizeof(*pkg) - sizeof(pkg->typeIdOffset),
@@ -5979,8 +6161,8 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
if (id >= 256) {
LOG_ALWAYS_FATAL("Package id out of range");
return NO_ERROR;
- } else if (id == 0) {
- // This is a library so assign an ID
+ } else if (id == 0 || appAsLib || isSystemAsset) {
+ // This is a library or a system asset, so assign an ID
id = mNextPackageId++;
}
@@ -6012,7 +6194,7 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
char16_t tmpName[sizeof(pkg->name)/sizeof(pkg->name[0])];
strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(pkg->name[0]));
- group = new PackageGroup(this, String16(tmpName), id);
+ group = new PackageGroup(this, String16(tmpName), id, appAsLib, isSystemAsset);
if (group == NULL) {
delete package;
return (mError=NO_MEMORY);
@@ -6224,8 +6406,9 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
return NO_ERROR;
}
-DynamicRefTable::DynamicRefTable(uint8_t packageId)
+DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib)
: mAssignedPackageId(packageId)
+ , mAppAsLib(appAsLib)
{
memset(mLookupTable, 0, sizeof(mLookupTable));
@@ -6310,16 +6493,18 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
uint32_t res = *resId;
size_t packageId = Res_GETPACKAGE(res) + 1;
- if (packageId == APP_PACKAGE_ID) {
+ if (packageId == APP_PACKAGE_ID && !mAppAsLib) {
// No lookup needs to be done, app package IDs are absolute.
return NO_ERROR;
}
- if (packageId == 0) {
+ if (packageId == 0 || (packageId == APP_PACKAGE_ID && mAppAsLib)) {
// The package ID is 0x00. That means that a shared library is accessing
- // its own local resource, so we fix up the resource with the calling
- // package ID.
- *resId |= ((uint32_t) mAssignedPackageId) << 24;
+ // its own local resource.
+ // Or if app resource is loaded as shared library, the resource which has
+ // app package Id is local resources.
+ // so we fix up those resources with the calling package ID.
+ *resId = (0xFFFFFF & (*resId)) | (((uint32_t) mAssignedPackageId) << 24);
return NO_ERROR;
}
@@ -6341,7 +6526,10 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
}
status_t DynamicRefTable::lookupResourceValue(Res_value* value) const {
- if (value->dataType != Res_value::TYPE_DYNAMIC_REFERENCE) {
+ if (value->dataType != Res_value::TYPE_DYNAMIC_REFERENCE &&
+ (value->dataType != Res_value::TYPE_REFERENCE || !mAppAsLib)) {
+ // If the package is loaded as shared library, the resource reference
+ // also need to be fixed.
return NO_ERROR;
}
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index a6f6d8c4ff16..49fe8a261178 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -39,7 +39,7 @@ using namespace android;
class _ZipEntryRO {
public:
ZipEntry entry;
- ZipEntryName name;
+ ZipString name;
void *cookie;
_ZipEntryRO() : cookie(NULL) {}
@@ -80,7 +80,7 @@ ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const
{
_ZipEntryRO* data = new _ZipEntryRO;
- data->name = ZipEntryName(entryName);
+ data->name = ZipString(entryName);
const int32_t error = FindEntry(mHandle, data->name, &(data->entry));
if (error) {
@@ -133,8 +133,8 @@ bool ZipFileRO::startIteration(void** cookie) {
bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* suffix)
{
_ZipEntryRO* ze = new _ZipEntryRO;
- ZipEntryName pe(prefix ? prefix : "");
- ZipEntryName se(suffix ? suffix : "");
+ ZipString pe(prefix ? prefix : "");
+ ZipString se(suffix ? suffix : "");
int32_t error = StartIteration(mHandle, &(ze->cookie),
prefix ? &pe : NULL,
suffix ? &se : NULL);
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index a353575b4073..2bc026b79ca2 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -21,6 +21,7 @@
LOCAL_PATH:= $(call my-dir)
testFiles := \
+ AppAsLib_test.cpp \
AttributeFinder_test.cpp \
ByteBucketArray_test.cpp \
Config_test.cpp \
diff --git a/libs/androidfw/tests/AppAsLib_test.cpp b/libs/androidfw/tests/AppAsLib_test.cpp
new file mode 100644
index 000000000000..8489acf6246f
--- /dev/null
+++ b/libs/androidfw/tests/AppAsLib_test.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 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 <androidfw/ResourceTypes.h>
+
+#include "data/appaslib/R.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace {
+
+#include "data/appaslib/appaslib_arsc.h"
+#include "data/appaslib/appaslib_lib_arsc.h"
+
+// This tests the app resources loaded as app.
+TEST(AppAsLibTest, loadedAsApp) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(appaslib_arsc, appaslib_arsc_len));
+
+ Res_value val;
+ ssize_t block = table.getResource(appaslib::R::app::integer::number1, &val);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
+ ASSERT_EQ(appaslib::R::app::array::integerArray1, val.data);
+}
+
+// This tests the app resources loaded as shared-lib.
+TEST(AppAsLibTest, loadedAsSharedLib) {
+ ResTable table;
+ // Load as shared library.
+ ASSERT_EQ(NO_ERROR, table.add(appaslib_arsc, appaslib_arsc_len, NULL, 0, -1, false, true));
+
+ Res_value val;
+ ssize_t block = table.getResource(appaslib::R::lib::integer::number1, &val);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
+ ASSERT_EQ(appaslib::R::lib::array::integerArray1, val.data);
+}
+
+// This tests the shared-lib loaded with appAsLib as true.
+TEST(AppAsLibTest, loadedSharedLib) {
+ ResTable table;
+ // Load shared library with appAsLib as true.
+ ASSERT_EQ(NO_ERROR, table.add(appaslib_lib_arsc, appaslib_lib_arsc_len, NULL, 0, -1, false, true));
+
+ Res_value val;
+ ssize_t block = table.getResource(appaslib::R::lib::integer::number1, &val);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
+ ASSERT_EQ(appaslib::R::lib::array::integerArray1, val.data);
+}
+
+}
diff --git a/libs/androidfw/tests/BackupData_test.cpp b/libs/androidfw/tests/BackupData_test.cpp
index 92af7fe790bd..e25b616dcbd9 100644
--- a/libs/androidfw/tests/BackupData_test.cpp
+++ b/libs/androidfw/tests/BackupData_test.cpp
@@ -108,7 +108,7 @@ TEST_F(BackupDataTest, WriteAndReadSingle) {
EXPECT_EQ(DATA1[i], dataBytes[i])
<< "data character " << i << " should be equal";
}
- delete dataBytes;
+ delete[] dataBytes;
delete writer;
delete reader;
}
diff --git a/libs/androidfw/tests/ConfigLocale_test.cpp b/libs/androidfw/tests/ConfigLocale_test.cpp
index 49995942a562..2bf9b12b6ce5 100644
--- a/libs/androidfw/tests/ConfigLocale_test.cpp
+++ b/libs/androidfw/tests/ConfigLocale_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <androidfw/LocaleData.h>
#include <androidfw/ResourceTypes.h>
#include <utils/Log.h>
#include <utils/String8.h>
@@ -28,7 +29,7 @@ TEST(ConfigLocaleTest, packAndUnpack2LetterLanguage) {
EXPECT_EQ('e', config.language[0]);
EXPECT_EQ('n', config.language[1]);
- char out[4] = { 1, 1, 1, 1};
+ char out[4] = {1, 1, 1, 1};
config.unpackLanguage(out);
EXPECT_EQ('e', out[0]);
EXPECT_EQ('n', out[1]);
@@ -51,7 +52,7 @@ TEST(ConfigLocaleTest, packAndUnpack2LetterRegion) {
EXPECT_EQ('U', config.country[0]);
EXPECT_EQ('S', config.country[1]);
- char out[4] = { 1, 1, 1, 1};
+ char out[4] = {1, 1, 1, 1};
config.unpackRegion(out);
EXPECT_EQ('U', out[0]);
EXPECT_EQ('S', out[1]);
@@ -67,7 +68,7 @@ TEST(ConfigLocaleTest, packAndUnpack3LetterLanguage) {
EXPECT_EQ('\x99', config.language[0]);
EXPECT_EQ('\xA4', config.language[1]);
- char out[4] = { 1, 1, 1, 1};
+ char out[4] = {1, 1, 1, 1};
config.unpackLanguage(out);
EXPECT_EQ('e', out[0]);
EXPECT_EQ('n', out[1]);
@@ -91,7 +92,7 @@ TEST(ConfigLocaleTest, packAndUnpack3LetterLanguageAtOffset16) {
EXPECT_EQ(char(0xbc), config.language[0]);
EXPECT_EQ(char(0xd3), config.language[1]);
- char out[4] = { 1, 1, 1, 1};
+ char out[4] = {1, 1, 1, 1};
config.unpackLanguage(out);
EXPECT_EQ('t', out[0]);
EXPECT_EQ('g', out[1]);
@@ -103,7 +104,7 @@ TEST(ConfigLocaleTest, packAndUnpack3LetterRegion) {
ResTable_config config;
config.packRegion("419");
- char out[4] = { 1, 1, 1, 1};
+ char out[4] = {1, 1, 1, 1};
config.unpackRegion(out);
EXPECT_EQ('4', out[0]);
@@ -124,6 +125,10 @@ TEST(ConfigLocaleTest, packAndUnpack3LetterRegion) {
if (script != NULL) {
memcpy(out->localeScript, script, 4);
+ out->localeScriptWasComputed = false;
+ } else {
+ out->computeScript();
+ out->localeScriptWasComputed = true;
}
if (variant != NULL) {
@@ -177,11 +182,12 @@ TEST(ConfigLocaleTest, setLocale) {
EXPECT_EQ('n', test.language[1]);
EXPECT_EQ('U', test.country[0]);
EXPECT_EQ('S', test.country[1]);
- EXPECT_EQ(0, test.localeScript[0]);
+ EXPECT_TRUE(test.localeScriptWasComputed);
+ EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
EXPECT_EQ(0, test.localeVariant[0]);
test.setBcp47Locale("eng-419");
- char out[4] = { 1, 1, 1, 1};
+ char out[4] = {1, 1, 1, 1};
test.unpackLanguage(out);
EXPECT_EQ('e', out[0]);
EXPECT_EQ('n', out[1]);
@@ -193,17 +199,458 @@ TEST(ConfigLocaleTest, setLocale) {
EXPECT_EQ('1', out[1]);
EXPECT_EQ('9', out[2]);
-
test.setBcp47Locale("en-Latn-419");
- memset(out, 1, 4);
EXPECT_EQ('e', test.language[0]);
EXPECT_EQ('n', test.language[1]);
-
EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
+ EXPECT_FALSE(test.localeScriptWasComputed);
+ memset(out, 1, 4);
test.unpackRegion(out);
EXPECT_EQ('4', out[0]);
EXPECT_EQ('1', out[1]);
EXPECT_EQ('9', out[2]);
+
+ test.setBcp47Locale("de-1901");
+ memset(out, 1, 4);
+ test.unpackLanguage(out);
+ EXPECT_EQ('d', out[0]);
+ EXPECT_EQ('e', out[1]);
+ EXPECT_EQ('\0', out[2]);
+ EXPECT_TRUE(test.localeScriptWasComputed);
+ EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
+ memset(out, 1, 4);
+ test.unpackRegion(out);
+ EXPECT_EQ('\0', out[0]);
+ EXPECT_EQ(0, strcmp("1901", test.localeVariant));
+
+ test.setBcp47Locale("de-Latn-1901");
+ memset(out, 1, 4);
+ test.unpackLanguage(out);
+ EXPECT_EQ('d', out[0]);
+ EXPECT_EQ('e', out[1]);
+ EXPECT_EQ('\0', out[2]);
+ EXPECT_FALSE(test.localeScriptWasComputed);
+ EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
+ memset(out, 1, 4);
+ test.unpackRegion(out);
+ EXPECT_EQ('\0', out[0]);
+ EXPECT_EQ(0, strcmp("1901", test.localeVariant));
+}
+
+TEST(ConfigLocaleTest, computeScript) {
+ ResTable_config config;
+
+ fillIn(NULL, NULL, NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("\0\0\0\0", config.localeScript, 4));
+
+ fillIn("zh", "TW", NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("Hant", config.localeScript, 4));
+
+ fillIn("zh", "CN", NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("Hans", config.localeScript, 4));
+
+ fillIn("az", NULL, NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("Latn", config.localeScript, 4));
+
+ fillIn("az", "AZ", NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("Latn", config.localeScript, 4));
+
+ fillIn("az", "IR", NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("Arab", config.localeScript, 4));
+
+ fillIn("peo", NULL, NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("Xpeo", config.localeScript, 4));
+
+ fillIn("qaa", NULL, NULL, NULL, &config);
+ EXPECT_EQ(0, memcmp("\0\0\0\0", config.localeScript, 4));
+}
+
+TEST(ConfigLocaleTest, getBcp47Locale_script) {
+ ResTable_config config;
+ fillIn("en", NULL, "Latn", NULL, &config);
+
+ char out[RESTABLE_MAX_LOCALE_LEN];
+ config.localeScriptWasComputed = false;
+ config.getBcp47Locale(out);
+ EXPECT_EQ(0, strcmp("en-Latn", out));
+
+ config.localeScriptWasComputed = true;
+ config.getBcp47Locale(out);
+ EXPECT_EQ(0, strcmp("en", out));
+}
+
+TEST(ConfigLocaleTest, match) {
+ ResTable_config supported, requested;
+
+ fillIn(NULL, NULL, NULL, NULL, &supported);
+ fillIn("fr", "CA", NULL, NULL, &requested);
+ // Empty locale matches everything (as a default).
+ EXPECT_TRUE(supported.match(requested));
+
+ fillIn("en", "CA", NULL, NULL, &supported);
+ fillIn("fr", "CA", NULL, NULL, &requested);
+ // Different languages don't match.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("qaa", "FR", NULL, NULL, &supported);
+ fillIn("qaa", "CA", NULL, NULL, &requested);
+ // If we can't infer the scripts, different regions don't match.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("qaa", "FR", "Latn", NULL, &supported);
+ fillIn("qaa", "CA", NULL, NULL, &requested);
+ // If we can't infer any of the scripts, different regions don't match.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("qaa", "FR", NULL, NULL, &supported);
+ fillIn("qaa", "CA", "Latn", NULL, &requested);
+ // If we can't infer any of the scripts, different regions don't match.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("qaa", NULL, NULL, NULL, &supported);
+ fillIn("qaa", "CA", NULL, NULL, &requested);
+ // language-only resources still support language+region requests, even if we can't infer the
+ // script.
+ EXPECT_TRUE(supported.match(requested));
+
+ fillIn("qaa", "CA", NULL, NULL, &supported);
+ fillIn("qaa", "CA", NULL, NULL, &requested);
+ // Even if we can't infer the scripts, exactly equal locales match.
+ EXPECT_TRUE(supported.match(requested));
+
+ fillIn("az", NULL, NULL, NULL, &supported);
+ fillIn("az", NULL, "Latn", NULL, &requested);
+ // If the resolved scripts are the same, it doesn't matter if they were explicitly provided
+ // or not, and they match.
+ EXPECT_TRUE(supported.match(requested));
+
+ fillIn("az", NULL, NULL, NULL, &supported);
+ fillIn("az", NULL, "Cyrl", NULL, &requested);
+ // If the resolved scripts are different, they don't match.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("az", NULL, NULL, NULL, &supported);
+ fillIn("az", "IR", NULL, NULL, &requested);
+ // If the resolved scripts are different, they don't match.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("az", "IR", NULL, NULL, &supported);
+ fillIn("az", NULL, "Arab", NULL, &requested);
+ // If the resolved scripts are the same, it doesn't matter if they were explicitly provided
+ // or not, and they match.
+ EXPECT_TRUE(supported.match(requested));
+
+ fillIn("en", NULL, NULL, NULL, &supported);
+ fillIn("en", "XA", NULL, NULL, &requested);
+ // en-XA is a pseudo-locale, and English resources are not a match for it.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("en", "XA", NULL, NULL, &supported);
+ fillIn("en", NULL, NULL, NULL, &requested);
+ // en-XA is a pseudo-locale, and its resources don't support English locales.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("en", "XA", NULL, NULL, &supported);
+ fillIn("en", "XA", NULL, NULL, &requested);
+ // Even if they are pseudo-locales, exactly equal locales match.
+ EXPECT_TRUE(supported.match(requested));
+
+ fillIn("ar", NULL, NULL, NULL, &supported);
+ fillIn("ar", "XB", NULL, NULL, &requested);
+ // ar-XB is a pseudo-locale, and Arabic resources are not a match for it.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("ar", "XB", NULL, NULL, &supported);
+ fillIn("ar", NULL, NULL, NULL, &requested);
+ // ar-XB is a pseudo-locale, and its resources don't support Arabic locales.
+ EXPECT_FALSE(supported.match(requested));
+
+ fillIn("ar", "XB", NULL, NULL, &supported);
+ fillIn("ar", "XB", NULL, NULL, &requested);
+ // Even if they are pseudo-locales, exactly equal locales match.
+ EXPECT_TRUE(supported.match(requested));
+}
+
+TEST(ConfigLocaleTest, match_emptyScript) {
+ ResTable_config supported, requested;
+
+ fillIn("fr", "FR", NULL, NULL, &supported);
+ fillIn("fr", "CA", NULL, NULL, &requested);
+
+ // emulate packages built with older AAPT
+ memset(supported.localeScript, '\0', 4);
+ supported.localeScriptWasComputed = false;
+
+ EXPECT_TRUE(supported.match(requested));
+}
+
+TEST(ConfigLocaleTest, isLocaleBetterThan_basics) {
+ ResTable_config config1, config2, request;
+
+ fillIn(NULL, NULL, NULL, NULL, &request);
+ fillIn("fr", "FR", NULL, NULL, &config1);
+ fillIn("fr", "CA", NULL, NULL, &config2);
+ EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("fr", "CA", NULL, NULL, &request);
+ fillIn(NULL, NULL, NULL, NULL, &config1);
+ fillIn(NULL, NULL, NULL, NULL, &config2);
+ EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("fr", "CA", NULL, NULL, &request);
+ fillIn("fr", "FR", NULL, NULL, &config1);
+ fillIn(NULL, NULL, NULL, NULL, &config2);
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("de", "DE", NULL, NULL, &request);
+ fillIn("de", "DE", NULL, "1901", &config1);
+ fillIn("de", "DE", NULL, "1996", &config2);
+ EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("de", "DE", NULL, "1901", &request);
+ fillIn("de", "DE", NULL, "1901", &config1);
+ fillIn("de", "DE", NULL, NULL, &config2);
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("de", "DE", NULL, "1901", &request);
+ fillIn("de", "DE", NULL, "1996", &config1);
+ fillIn("de", "DE", NULL, NULL, &config2);
+ EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+}
+
+TEST(ConfigLocaleTest, isLocaleBetterThan_regionComparison) {
+ ResTable_config config1, config2, request;
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "419", NULL, NULL, &config1);
+ fillIn("es", "419", NULL, NULL, &config2);
+ // Both supported locales are the same, so none is better than the other.
+ EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "AR", NULL, NULL, &config1);
+ fillIn("es", "419", NULL, NULL, &config2);
+ // An exact locale match is better than a parent.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "419", NULL, NULL, &config1);
+ fillIn("es", NULL, NULL, NULL, &config2);
+ // A closer parent is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "419", NULL, NULL, &config1);
+ fillIn("es", "ES", NULL, NULL, &config2);
+ // A parent is better than a non-parent representative locale.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", NULL, NULL, NULL, &config1);
+ fillIn("es", "ES", NULL, NULL, &config2);
+ // A parent is better than a non-parent representative locale.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "PE", NULL, NULL, &config1);
+ fillIn("es", "ES", NULL, NULL, &config2);
+ // A closer locale is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "MX", NULL, NULL, &config1);
+ fillIn("es", "BO", NULL, NULL, &config2);
+ // A representative locale is better if they are equidistant.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "US", NULL, NULL, &config1);
+ fillIn("es", "BO", NULL, NULL, &config2);
+ // A representative locale is better if they are equidistant.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "AR", NULL, NULL, &request);
+ fillIn("es", "MX", NULL, NULL, &config1);
+ fillIn("es", "US", NULL, NULL, &config2);
+ // If all is equal, the locale earlier in the dictionary is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("es", "GQ", NULL, NULL, &request);
+ fillIn("es", "IC", NULL, NULL, &config1);
+ fillIn("es", "419", NULL, NULL, &config2);
+ // If all is equal, the locale earlier in the dictionary is better and
+ // letters are better than numbers.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "GB", NULL, NULL, &request);
+ fillIn("en", "001", NULL, NULL, &config1);
+ fillIn("en", NULL, NULL, NULL, &config2);
+ // A closer parent is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "PR", NULL, NULL, &request);
+ fillIn("en", NULL, NULL, NULL, &config1);
+ fillIn("en", "001", NULL, NULL, &config2);
+ // A parent is better than a non-parent.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "DE", NULL, NULL, &request);
+ fillIn("en", "150", NULL, NULL, &config1);
+ fillIn("en", "001", NULL, NULL, &config2);
+ // A closer parent is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "IN", NULL, NULL, &request);
+ fillIn("en", "AU", NULL, NULL, &config1);
+ fillIn("en", "US", NULL, NULL, &config2);
+ // A closer locale is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "PR", NULL, NULL, &request);
+ fillIn("en", "001", NULL, NULL, &config1);
+ fillIn("en", "GB", NULL, NULL, &config2);
+ // A closer locale is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "IN", NULL, NULL, &request);
+ fillIn("en", "GB", NULL, NULL, &config1);
+ fillIn("en", "AU", NULL, NULL, &config2);
+ // A representative locale is better if they are equidistant.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "IN", NULL, NULL, &request);
+ fillIn("en", "AU", NULL, NULL, &config1);
+ fillIn("en", "CA", NULL, NULL, &config2);
+ // If all is equal, the locale earlier in the dictionary is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("pt", "MZ", NULL, NULL, &request);
+ fillIn("pt", "PT", NULL, NULL, &config1);
+ fillIn("pt", NULL, NULL, NULL, &config2);
+ // A closer parent is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("pt", "MZ", NULL, NULL, &request);
+ fillIn("pt", "PT", NULL, NULL, &config1);
+ fillIn("pt", "BR", NULL, NULL, &config2);
+ // A parent is better than a non-parent.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("zh", "MO", "Hant", NULL, &request);
+ fillIn("zh", "HK", "Hant", NULL, &config1);
+ fillIn("zh", "TW", "Hant", NULL, &config2);
+ // A parent is better than a non-parent.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("zh", "US", "Hant", NULL, &request);
+ fillIn("zh", "TW", "Hant", NULL, &config1);
+ fillIn("zh", "HK", "Hant", NULL, &config2);
+ // A representative locale is better if they are equidistant.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("ar", "DZ", NULL, NULL, &request);
+ fillIn("ar", "015", NULL, NULL, &config1);
+ fillIn("ar", NULL, NULL, NULL, &config2);
+ // A closer parent is better.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("ar", "EG", NULL, NULL, &request);
+ fillIn("ar", NULL, NULL, NULL, &config1);
+ fillIn("ar", "015", NULL, NULL, &config2);
+ // A parent is better than a non-parent.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("ar", "QA", NULL, NULL, &request);
+ fillIn("ar", "EG", NULL, NULL, &config1);
+ fillIn("ar", "BH", NULL, NULL, &config2);
+ // A representative locale is better if they are equidistant.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("ar", "QA", NULL, NULL, &request);
+ fillIn("ar", "SA", NULL, NULL, &config1);
+ fillIn("ar", "015", NULL, NULL, &config2);
+ // If all is equal, the locale earlier in the dictionary is better and
+ // letters are better than numbers.
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+}
+
+// Default resources are considered better matches for US English
+// and US-like English locales than International English locales
+TEST(ConfigLocaleTest, isLocaleBetterThan_UsEnglishIsSpecial) {
+ ResTable_config config1, config2, request;
+
+ fillIn("en", "US", NULL, NULL, &request);
+ fillIn(NULL, NULL, NULL, NULL, &config1);
+ fillIn("en", "001", NULL, NULL, &config2);
+ // default is better than International English
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "US", NULL, NULL, &request);
+ fillIn(NULL, NULL, NULL, NULL, &config1);
+ fillIn("en", "GB", NULL, NULL, &config2);
+ // default is better than British English
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "PR", NULL, NULL, &request);
+ fillIn(NULL, NULL, NULL, NULL, &config1);
+ fillIn("en", "001", NULL, NULL, &config2);
+ // Even for Puerto Rico, default is better than International English
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "US", NULL, NULL, &request);
+ fillIn("en", NULL, NULL, NULL, &config1);
+ fillIn(NULL, NULL, NULL, NULL, &config2);
+ // "English" is better than default, since it's a parent of US English
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "PR", NULL, NULL, &request);
+ fillIn("en", NULL, NULL, NULL, &config1);
+ fillIn(NULL, NULL, NULL, NULL, &config2);
+ // "English" is better than default, since it's a parent of Puerto Rico English
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("en", "US", NULL, NULL, &request);
+ fillIn(NULL, NULL, NULL, NULL, &config1);
+ fillIn("en", "PR", NULL, NULL, &config2);
+ // For US English itself, we prefer default to its siblings in the parent tree
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
}
-} // namespace android.
+} // namespace android
diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp
index dcfe91e3866d..b8b46259f0d1 100644
--- a/libs/androidfw/tests/ResTable_test.cpp
+++ b/libs/androidfw/tests/ResTable_test.cpp
@@ -39,8 +39,20 @@ namespace {
*/
#include "data/basic/basic_arsc.h"
+/**
+ * Include a binary library resource table.
+ *
+ * Package: com.android.test.basic
+ */
#include "data/lib/lib_arsc.h"
+/**
+ * Include a system resource table.
+ *
+ * Package: android
+ */
+#include "data/system/system_arsc.h"
+
TEST(ResTableTest, shouldLoadSuccessfully) {
ResTable table;
ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
@@ -282,4 +294,67 @@ TEST(ResTableTest, U16StringToInt) {
testU16StringToInt(u"0x1ffffffff", 0U, false, true);
}
+TEST(ResTableTest, ShareButDontModifyResTable) {
+ ResTable sharedTable;
+ ASSERT_EQ(NO_ERROR, sharedTable.add(basic_arsc, basic_arsc_len));
+
+ ResTable_config param;
+ memset(&param, 0, sizeof(param));
+ param.language[0] = 'v';
+ param.language[1] = 's';
+ sharedTable.setParameters(&param);
+
+ // Check that we get the default value for @integer:number1
+ Res_value val;
+ ssize_t block = sharedTable.getResource(base::R::integer::number1, &val, MAY_NOT_BE_BAG);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ ASSERT_EQ(uint32_t(600), val.data);
+
+ // Create a new table that shares the entries of the shared table.
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(&sharedTable, false));
+
+ // Set a new configuration on the new table.
+ memset(&param, 0, sizeof(param));
+ param.language[0] = 's';
+ param.language[1] = 'v';
+ param.country[0] = 'S';
+ param.country[1] = 'E';
+ table.setParameters(&param);
+
+ // Check that we get a new value in the new table.
+ block = table.getResource(base::R::integer::number1, &val, MAY_NOT_BE_BAG);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ ASSERT_EQ(uint32_t(400), val.data);
+
+ // Check that we still get the old value in the shared table.
+ block = sharedTable.getResource(base::R::integer::number1, &val, MAY_NOT_BE_BAG);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ ASSERT_EQ(uint32_t(600), val.data);
+}
+
+TEST(ResTableTest, GetConfigurationsReturnsUniqueList) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(system_arsc, system_arsc_len));
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ ResTable_config configSv;
+ memset(&configSv, 0, sizeof(configSv));
+ configSv.language[0] = 's';
+ configSv.language[1] = 'v';
+
+ Vector<ResTable_config> configs;
+ table.getConfigurations(&configs);
+
+ EXPECT_EQ(1, std::count(configs.begin(), configs.end(), configSv));
+
+ Vector<String8> locales;
+ table.getLocales(&locales);
+
+ EXPECT_EQ(1, std::count(locales.begin(), locales.end(), String8("sv")));
}
+
+} // namespace
diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h
index ac80d8868090..ff9be164dbb0 100644
--- a/libs/androidfw/tests/TestHelpers.h
+++ b/libs/androidfw/tests/TestHelpers.h
@@ -21,7 +21,7 @@ namespace android {
enum { MAY_NOT_BE_BAG = false };
static inline bool operator==(const android::ResTable_config& a, const android::ResTable_config& b) {
- return memcmp(&a, &b, sizeof(a)) == 0;
+ return a.compare(b) == 0;
}
static inline ::std::ostream& operator<<(::std::ostream& out, const android::ResTable_config& c) {
diff --git a/libs/androidfw/tests/ZipUtils_test.cpp b/libs/androidfw/tests/ZipUtils_test.cpp
index c6038b597f4e..7293843e066a 100644
--- a/libs/androidfw/tests/ZipUtils_test.cpp
+++ b/libs/androidfw/tests/ZipUtils_test.cpp
@@ -45,7 +45,7 @@ TEST_F(ZipUtilsTest, ZipTimeConvertSuccess) {
EXPECT_EQ(2011, t.tm_year + 1900)
<< "Year was improperly converted.";
- EXPECT_EQ(6, t.tm_mon)
+ EXPECT_EQ(5, t.tm_mon)
<< "Month was improperly converted.";
EXPECT_EQ(29, t.tm_mday)
@@ -59,6 +59,11 @@ TEST_F(ZipUtilsTest, ZipTimeConvertSuccess) {
EXPECT_EQ(40, t.tm_sec)
<< "Second was improperly converted.";
+
+ // We don't have enough information to determine timezone related info.
+ EXPECT_EQ(-1, t.tm_isdst);
+ EXPECT_EQ(0, t.tm_gmtoff);
+ EXPECT_EQ(nullptr, t.tm_zone);
}
}
diff --git a/libs/androidfw/tests/data/appaslib/AndroidManifest.xml b/libs/androidfw/tests/data/appaslib/AndroidManifest.xml
new file mode 100644
index 000000000000..e00045b0aa12
--- /dev/null
+++ b/libs/androidfw/tests/data/appaslib/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.basic">
+ <application>
+ </application>
+</manifest>
diff --git a/libs/androidfw/tests/data/appaslib/R.h b/libs/androidfw/tests/data/appaslib/R.h
new file mode 100644
index 000000000000..3af921a7ba65
--- /dev/null
+++ b/libs/androidfw/tests/data/appaslib/R.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 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 __APPASLIB_R_H
+#define __APPASLIB_R_H
+
+namespace appaslib {
+namespace R {
+namespace lib {
+namespace integer {
+ enum {
+ number1 = 0x02020000, // default
+ };
+}
+
+namespace array {
+ enum {
+ integerArray1 = 0x02030000, // default
+ };
+}
+} // namespace lib
+
+namespace app {
+namespace integer {
+ enum {
+ number1 = 0x7f020000, // default
+ };
+}
+
+namespace array {
+ enum {
+ integerArray1 = 0x7f030000, // default
+ };
+}
+} // namespace app
+} // namespace R
+} // namespace appaslib
+
+#endif // __APPASLIB_R_H
diff --git a/libs/androidfw/tests/data/appaslib/appaslib_arsc.h b/libs/androidfw/tests/data/appaslib/appaslib_arsc.h
new file mode 100644
index 000000000000..be176ab5e63f
--- /dev/null
+++ b/libs/androidfw/tests/data/appaslib/appaslib_arsc.h
@@ -0,0 +1,68 @@
+unsigned char appaslib_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0x04, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xdc, 0x02, 0x00, 0x00,
+ 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00,
+ 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x0d, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00,
+ 0x72, 0x00, 0x41, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x31, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x48, 0x00, 0x5c, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x7f,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x48, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x4c, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x03, 0x00, 0x00, 0x00
+};
+unsigned int appaslib_arsc_len = 772;
diff --git a/libs/androidfw/tests/data/appaslib/appaslib_lib_arsc.h b/libs/androidfw/tests/data/appaslib/appaslib_lib_arsc.h
new file mode 100644
index 000000000000..099285a17aad
--- /dev/null
+++ b/libs/androidfw/tests/data/appaslib/appaslib_lib_arsc.h
@@ -0,0 +1,68 @@
+unsigned char appaslib_lib_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0x04, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xdc, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00,
+ 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x0d, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00,
+ 0x72, 0x00, 0x41, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x31, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x48, 0x00, 0x5c, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x07, 0x00, 0x00, 0x03, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x48, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x4c, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x03, 0x00, 0x00, 0x00
+};
+unsigned int appaslib_lib_arsc_len = 772;
diff --git a/libs/androidfw/tests/data/appaslib/build b/libs/androidfw/tests/data/appaslib/build
new file mode 100755
index 000000000000..e4bd88b4032c
--- /dev/null
+++ b/libs/androidfw/tests/data/appaslib/build
@@ -0,0 +1,28 @@
+#!/bin/bash
+#
+# Copyright (C) 2015 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.
+#
+
+PATH_TO_FRAMEWORK_RES=$(gettop)/prebuilts/sdk/current/android.jar
+
+aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc appaslib.arsc && \
+xxd -i appaslib.arsc > appaslib_arsc.h && \
+aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES -F bundle.apk -f --shared-lib && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc appaslib_lib.arsc && \
+xxd -i appaslib_lib.arsc > appaslib_lib_arsc.h \
+
diff --git a/libs/androidfw/tests/data/appaslib/res/values/values.xml b/libs/androidfw/tests/data/appaslib/res/values/values.xml
new file mode 100644
index 000000000000..39b99a6bfb81
--- /dev/null
+++ b/libs/androidfw/tests/data/appaslib/res/values/values.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<resources>
+ <integer name="number1">@array/integerArray1</integer>
+ <integer-array name="integerArray1">
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ </integer-array>
+</resources>
diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h
index aaac74059f58..6694dd0c36e1 100644
--- a/libs/androidfw/tests/data/basic/R.h
+++ b/libs/androidfw/tests/data/basic/R.h
@@ -46,7 +46,7 @@ namespace string {
namespace integer {
enum {
- number1 = 0x7f040000, // default, sv
+ number1 = 0x7f040000, // default, sv, vs
number2 = 0x7f040001, // default
test3 = 0x7f090000, // default (in feature)
diff --git a/libs/androidfw/tests/data/basic/basic_arsc.h b/libs/androidfw/tests/data/basic/basic_arsc.h
index 13ab4fa0d0ea..e497401bdddb 100644
--- a/libs/androidfw/tests/data/basic/basic_arsc.h
+++ b/libs/androidfw/tests/data/basic/basic_arsc.h
@@ -1,5 +1,5 @@
unsigned char basic_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0x68, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0x0c, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x1c, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
@@ -16,7 +16,7 @@ unsigned char basic_arsc[] = {
0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00,
0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01,
- 0xa0, 0x06, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x44, 0x07, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00,
0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00,
0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00,
0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00,
@@ -73,68 +73,81 @@ unsigned char basic_arsc[] = {
0x41, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x31, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x84, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, 0x8c, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
- 0x05, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x24, 0x00, 0x00,
- 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x04, 0x24, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00,
+ 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x50, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00,
+ 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x50, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x74, 0x6e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x02, 0x44, 0x00, 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x4c, 0x00, 0x78, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
- 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x4c, 0x00, 0x74, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0xc8, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x06, 0x7f, 0x01, 0x02, 0x44, 0x00, 0x5c, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x76, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10,
+ 0xc8, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x7f, 0x01, 0x02, 0x4c, 0x00,
+ 0x64, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x54, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x76, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0x90, 0x01, 0x00, 0x00,
- 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x02, 0x44, 0x00, 0x90, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0x58, 0x02, 0x00, 0x00,
+ 0x01, 0x02, 0x4c, 0x00, 0x64, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x73, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x74, 0x6e,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+ 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10,
+ 0x90, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, 0x98, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -146,16 +159,17 @@ unsigned char basic_arsc[] = {
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x7f, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, 0x2c, 0x01, 0x00, 0x00,
0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
- 0x7c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00,
+ 0x84, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x50, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02,
+ 0x08, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02,
+ 0x08, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00
};
-unsigned int basic_arsc_len = 1896;
+unsigned int basic_arsc_len = 2060;
diff --git a/libs/androidfw/tests/data/basic/res/values-vs/values.xml b/libs/androidfw/tests/data/basic/res/values-vs/values.xml
new file mode 100644
index 000000000000..4a5a64016e68
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/values-vs/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<resources>
+ <integer name="number1">600</integer>
+</resources>
diff --git a/libs/androidfw/tests/data/basic/split_de_fr_arsc.h b/libs/androidfw/tests/data/basic/split_de_fr_arsc.h
index b742d2811487..a2aa598e5c90 100644
--- a/libs/androidfw/tests/data/basic/split_de_fr_arsc.h
+++ b/libs/androidfw/tests/data/basic/split_de_fr_arsc.h
@@ -1,5 +1,5 @@
unsigned char split_de_fr_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0xe4, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0xf4, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x1c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
@@ -10,7 +10,7 @@ unsigned char split_de_fr_arsc[] = {
0x32, 0x00, 0x00, 0x00, 0x07, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00,
0x61, 0x00, 0x69, 0x00, 0x20, 0x00, 0x31, 0x00, 0x00, 0x00, 0x07, 0x00,
0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x61, 0x00, 0x69, 0x00, 0x20, 0x00,
- 0x32, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x5c, 0x03, 0x00, 0x00,
+ 0x32, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x6c, 0x03, 0x00, 0x00,
0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
@@ -57,30 +57,32 @@ unsigned char split_de_fr_arsc[] = {
0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
- 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00,
+ 0x78, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x58, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x64, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x02, 0x44, 0x00, 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
- 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x74, 0x6e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00,
+ 0x78, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x58, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x74, 0x6e, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+ 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
};
-unsigned int split_de_fr_arsc_len = 996;
+unsigned int split_de_fr_arsc_len = 1012;
diff --git a/libs/androidfw/tests/data/basic/split_hdpi_v4_arsc.h b/libs/androidfw/tests/data/basic/split_hdpi_v4_arsc.h
index e9fb7ea47b75..0cc39154a5ec 100644
--- a/libs/androidfw/tests/data/basic/split_hdpi_v4_arsc.h
+++ b/libs/androidfw/tests/data/basic/split_hdpi_v4_arsc.h
@@ -1,10 +1,10 @@
unsigned char split_hdpi_v4_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0x08, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0x10, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x1c, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x68, 0x00,
0x64, 0x00, 0x70, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01,
- 0xd0, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0xd8, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00,
0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00,
0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00,
0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00,
@@ -50,19 +50,20 @@ unsigned char split_hdpi_v4_arsc[] = {
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x02, 0x44, 0x00, 0x60, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x4c, 0x00, 0x68, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
};
-unsigned int split_hdpi_v4_arsc_len = 776;
+unsigned int split_hdpi_v4_arsc_len = 784;
diff --git a/libs/androidfw/tests/data/basic/split_xhdpi_v4_arsc.h b/libs/androidfw/tests/data/basic/split_xhdpi_v4_arsc.h
index 7835f71419f2..d44ba9630aba 100644
--- a/libs/androidfw/tests/data/basic/split_xhdpi_v4_arsc.h
+++ b/libs/androidfw/tests/data/basic/split_xhdpi_v4_arsc.h
@@ -1,10 +1,10 @@
unsigned char split_xhdpi_v4_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0x14, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x1c, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x78, 0x00,
0x68, 0x00, 0x64, 0x00, 0x70, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x02, 0x20, 0x01, 0xd0, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x20, 0x01, 0xd8, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00,
0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00,
0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00,
@@ -50,19 +50,20 @@ unsigned char split_xhdpi_v4_arsc[] = {
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x60, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, 0x68, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
-unsigned int split_xhdpi_v4_arsc_len = 780;
+unsigned int split_xhdpi_v4_arsc_len = 788;
diff --git a/libs/androidfw/tests/data/basic/split_xxhdpi_v4_arsc.h b/libs/androidfw/tests/data/basic/split_xxhdpi_v4_arsc.h
index f805db1b009e..2f3f682fb6e4 100644
--- a/libs/androidfw/tests/data/basic/split_xxhdpi_v4_arsc.h
+++ b/libs/androidfw/tests/data/basic/split_xxhdpi_v4_arsc.h
@@ -1,10 +1,10 @@
unsigned char split_xxhdpi_v4_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0x14, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x1c, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x78, 0x00,
0x78, 0x00, 0x68, 0x00, 0x64, 0x00, 0x70, 0x00, 0x69, 0x00, 0x00, 0x00,
- 0x00, 0x02, 0x20, 0x01, 0xd0, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x20, 0x01, 0xd8, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00,
0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00,
0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00,
@@ -50,19 +50,20 @@ unsigned char split_xxhdpi_v4_arsc[] = {
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x60, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, 0x68, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
-unsigned int split_xxhdpi_v4_arsc_len = 780;
+unsigned int split_xxhdpi_v4_arsc_len = 788;
diff --git a/libs/androidfw/tests/data/system/R.h b/libs/androidfw/tests/data/system/R.h
index 27f25fec0d01..6a31fb8ff088 100644
--- a/libs/androidfw/tests/data/system/R.h
+++ b/libs/androidfw/tests/data/system/R.h
@@ -33,6 +33,12 @@ namespace style {
};
}
+namespace integer {
+ enum {
+ number = 0x01030000, // sv
+ };
+}
+
} // namespace R
} // namespace android
diff --git a/libs/androidfw/tests/data/system/res/values-sv/values.xml b/libs/androidfw/tests/data/system/res/values-sv/values.xml
new file mode 100644
index 000000000000..b97bdb68aca7
--- /dev/null
+++ b/libs/androidfw/tests/data/system/res/values-sv/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<resources>
+ <public type="integer" name="number" id="0x01030000" />
+ <integer name="number">1</integer>
+</resources>
diff --git a/libs/androidfw/tests/data/system/system_arsc.h b/libs/androidfw/tests/data/system/system_arsc.h
index 215ecae552c5..b0dab6b357e9 100644
--- a/libs/androidfw/tests/data/system/system_arsc.h
+++ b/libs/androidfw/tests/data/system/system_arsc.h
@@ -1,8 +1,8 @@
unsigned char system_arsc[] = {
- 0x02, 0x00, 0x0c, 0x00, 0x18, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0xf8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xf0, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xd0, 0x03, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00,
0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -25,26 +25,33 @@ unsigned char system_arsc[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
- 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00,
- 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
- 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x98, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x78, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00, 0x65, 0x00,
+ 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00,
+ 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x5e, 0x00,
+ 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x2d, 0x00, 0x70, 0x00,
+ 0x72, 0x00, 0x69, 0x00, 0x76, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x84, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x62, 0x00, 0x61, 0x00, 0x63, 0x00, 0x6b, 0x00, 0x67, 0x00,
0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x00, 0x00,
0x0a, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x67, 0x00,
0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x00, 0x00,
0x09, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00,
- 0x2e, 0x00, 0x4f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
- 0x01, 0x02, 0x44, 0x00, 0x84, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x2e, 0x00, 0x4f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x00, 0x00, 0x06, 0x00,
+ 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x40, 0x01, 0x02, 0x4c, 0x00, 0x8c, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -55,15 +62,27 @@ unsigned char system_arsc[] = {
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, 0x00,
0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x02, 0x44, 0x00,
- 0x70, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x02, 0x4c, 0x00,
+ 0x78, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x50, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0xff, 0xff,
- 0x01, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xff
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
+ 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x01, 0x01,
+ 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xff, 0x02, 0x02, 0x10, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x01, 0x02, 0x4c, 0x00, 0x60, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x76, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
-unsigned int system_arsc_len = 792;
+unsigned int system_arsc_len = 1016;
diff --git a/libs/common_time/common_time_server.cpp b/libs/common_time/common_time_server.cpp
index 01372e00cab8..f72ffaa63712 100644
--- a/libs/common_time/common_time_server.cpp
+++ b/libs/common_time/common_time_server.cpp
@@ -143,7 +143,7 @@ CommonTimeServer::CommonTimeServer()
// Create the eventfd we will use to signal our thread to wake up when
// needed.
- mWakeupThreadFD = eventfd(0, EFD_NONBLOCK);
+ mWakeupThreadFD = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
// seed the random number generator (used to generated timeline IDs)
srand48(static_cast<unsigned int>(systemTime()));
diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp
index a4100a2d44fb..20ecda28b22a 100644
--- a/libs/hwui/AmbientShadow.cpp
+++ b/libs/hwui/AmbientShadow.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
/**
* Extra vertices for the corner for smoother corner.
* Only for outer vertices.
@@ -54,15 +52,14 @@
// If this is set to negative value, then all the edge will be tessellated.
#define ALPHA_THRESHOLD (0.1f / 255.0f)
-#include <math.h>
-#include <utils/Log.h>
-#include <utils/Vector.h>
-
#include "AmbientShadow.h"
+
#include "ShadowTessellator.h"
#include "Vertex.h"
#include "VertexBuffer.h"
-#include "utils/MathUtils.h"
+
+#include <algorithm>
+#include <utils/Log.h>
namespace android {
namespace uirenderer {
@@ -81,7 +78,7 @@ inline Vector2 getNormalFromVertices(const Vector3* vertices, int current, int n
// The input z value will be converted to be non-negative inside.
// The output must be ranged from 0 to 1.
inline float getAlphaFromFactoredZ(float factoredZ) {
- return 1.0 / (1 + MathUtils::max(factoredZ, 0.0f));
+ return 1.0 / (1 + std::max(factoredZ, 0.0f));
}
// The shader is using gaussian function e^-(1-x)*(1-x)*4, therefore, we transform
diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk
deleted file mode 100644
index 38e8be907720..000000000000
--- a/libs/hwui/Android.common.mk
+++ /dev/null
@@ -1,127 +0,0 @@
-# getConfig in external/skia/include/core/SkBitmap.h is deprecated.
-# Allow Gnu extension: in-class initializer of static 'const float' member.
-# DeferredLayerUpdater.h: private field 'mRenderThread' is not used.
-LOCAL_CLANG_CFLAGS += \
- -Wno-deprecated-declarations \
- -Wno-gnu-static-float-init \
- -Wno-unused-private-field
-
-LOCAL_SRC_FILES := \
- font/CacheTexture.cpp \
- font/Font.cpp \
- renderstate/Blend.cpp \
- renderstate/MeshState.cpp \
- renderstate/PixelBufferState.cpp \
- renderstate/RenderState.cpp \
- renderstate/Scissor.cpp \
- renderstate/Stencil.cpp \
- renderstate/TextureState.cpp \
- renderthread/CanvasContext.cpp \
- renderthread/DrawFrameTask.cpp \
- renderthread/EglManager.cpp \
- renderthread/RenderProxy.cpp \
- renderthread/RenderTask.cpp \
- renderthread/RenderThread.cpp \
- renderthread/TimeLord.cpp \
- thread/TaskManager.cpp \
- utils/Blur.cpp \
- utils/GLUtils.cpp \
- utils/LinearAllocator.cpp \
- utils/SortedListImpl.cpp \
- AmbientShadow.cpp \
- AnimationContext.cpp \
- Animator.cpp \
- AnimatorManager.cpp \
- AssetAtlas.cpp \
- Caches.cpp \
- CanvasState.cpp \
- ClipArea.cpp \
- DamageAccumulator.cpp \
- DeferredDisplayList.cpp \
- DeferredLayerUpdater.cpp \
- DisplayList.cpp \
- DisplayListCanvas.cpp \
- Dither.cpp \
- Extensions.cpp \
- FboCache.cpp \
- FontRenderer.cpp \
- FrameInfo.cpp \
- FrameInfoVisualizer.cpp \
- GammaFontRenderer.cpp \
- GlopBuilder.cpp \
- GradientCache.cpp \
- Image.cpp \
- Interpolator.cpp \
- JankTracker.cpp \
- Layer.cpp \
- LayerCache.cpp \
- LayerRenderer.cpp \
- Matrix.cpp \
- OpenGLRenderer.cpp \
- Patch.cpp \
- PatchCache.cpp \
- PathCache.cpp \
- PathTessellator.cpp \
- PixelBuffer.cpp \
- Program.cpp \
- ProgramCache.cpp \
- Properties.cpp \
- RenderBufferCache.cpp \
- RenderNode.cpp \
- RenderProperties.cpp \
- ResourceCache.cpp \
- ShadowTessellator.cpp \
- SkiaCanvas.cpp \
- SkiaCanvasProxy.cpp \
- SkiaShader.cpp \
- Snapshot.cpp \
- SpotShadow.cpp \
- TessellationCache.cpp \
- TextDropShadowCache.cpp \
- Texture.cpp \
- TextureCache.cpp
-
-intermediates := $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,)
-
-LOCAL_C_INCLUDES += \
- external/skia/src/core
-
-LOCAL_CFLAGS += -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
-LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libEGL libGLESv2 libskia libui libgui
-
-ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
- LOCAL_CFLAGS += -DANDROID_ENABLE_RENDERSCRIPT
- LOCAL_SHARED_LIBRARIES += libRS libRScpp
- LOCAL_C_INCLUDES += \
- $(intermediates) \
- frameworks/rs/cpp \
- frameworks/rs \
-
-endif
-
-ifndef HWUI_COMPILE_SYMBOLS
- LOCAL_CFLAGS += -fvisibility=hidden
-endif
-
-ifdef HWUI_COMPILE_FOR_PERF
- # TODO: Non-arm?
- LOCAL_CFLAGS += -fno-omit-frame-pointer -marm -mapcs
-endif
-
-ifeq (true, $(HWUI_NULL_GPU))
- LOCAL_SRC_FILES += \
- tests/nullegl.cpp \
- tests/nullgles.cpp
-
- LOCAL_CFLAGS += -DHWUI_NULL_GPU
-endif
-
-# Defaults for ATRACE_TAG and LOG_TAG for libhwui
-LOCAL_CFLAGS += -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\"
-LOCAL_CFLAGS += -Wall -Wno-unused-parameter -Wunreachable-code
-LOCAL_CFLAGS += -ffast-math -O3
-
-# b/21698669
-ifneq ($(USE_CLANG_PLATFORM_BUILD),true)
- LOCAL_CFLAGS += -Werror
-endif
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 91e289c32b97..8857c41dc33d 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -2,11 +2,340 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+HWUI_NEW_OPS := true
+
+# Enables fine-grained GLES error checking
+# If set to true, every GLES call is wrapped & error checked
+# Has moderate overhead
+HWUI_ENABLE_OPENGL_VALIDATION := false
+
+hwui_src_files := \
+ font/CacheTexture.cpp \
+ font/Font.cpp \
+ hwui/Canvas.cpp \
+ hwui/MinikinSkia.cpp \
+ hwui/MinikinUtils.cpp \
+ hwui/PaintImpl.cpp \
+ hwui/Typeface.cpp \
+ renderstate/Blend.cpp \
+ renderstate/MeshState.cpp \
+ renderstate/OffscreenBufferPool.cpp \
+ renderstate/PixelBufferState.cpp \
+ renderstate/RenderState.cpp \
+ renderstate/Scissor.cpp \
+ renderstate/Stencil.cpp \
+ renderstate/TextureState.cpp \
+ renderthread/CanvasContext.cpp \
+ renderthread/DrawFrameTask.cpp \
+ renderthread/EglManager.cpp \
+ renderthread/RenderProxy.cpp \
+ renderthread/RenderTask.cpp \
+ renderthread/RenderThread.cpp \
+ renderthread/TimeLord.cpp \
+ thread/TaskManager.cpp \
+ utils/Blur.cpp \
+ utils/GLUtils.cpp \
+ utils/LinearAllocator.cpp \
+ utils/NinePatchImpl.cpp \
+ utils/StringUtils.cpp \
+ utils/TestWindowContext.cpp \
+ utils/VectorDrawableUtils.cpp \
+ AmbientShadow.cpp \
+ AnimationContext.cpp \
+ Animator.cpp \
+ AnimatorManager.cpp \
+ AssetAtlas.cpp \
+ Caches.cpp \
+ CanvasState.cpp \
+ ClipArea.cpp \
+ DamageAccumulator.cpp \
+ DeferredDisplayList.cpp \
+ DeferredLayerUpdater.cpp \
+ DeviceInfo.cpp \
+ DisplayList.cpp \
+ DisplayListCanvas.cpp \
+ Dither.cpp \
+ Extensions.cpp \
+ FboCache.cpp \
+ FontRenderer.cpp \
+ FrameInfo.cpp \
+ FrameInfoVisualizer.cpp \
+ GammaFontRenderer.cpp \
+ GlopBuilder.cpp \
+ GpuMemoryTracker.cpp \
+ GradientCache.cpp \
+ Image.cpp \
+ Interpolator.cpp \
+ JankTracker.cpp \
+ Layer.cpp \
+ LayerCache.cpp \
+ LayerRenderer.cpp \
+ LayerUpdateQueue.cpp \
+ Matrix.cpp \
+ OpenGLRenderer.cpp \
+ Patch.cpp \
+ PatchCache.cpp \
+ PathCache.cpp \
+ PathTessellator.cpp \
+ PathParser.cpp \
+ PixelBuffer.cpp \
+ Program.cpp \
+ ProgramCache.cpp \
+ Properties.cpp \
+ PropertyValuesHolder.cpp \
+ PropertyValuesAnimatorSet.cpp \
+ Readback.cpp \
+ RenderBufferCache.cpp \
+ RenderNode.cpp \
+ RenderProperties.cpp \
+ ResourceCache.cpp \
+ ShadowTessellator.cpp \
+ SkiaCanvas.cpp \
+ SkiaCanvasProxy.cpp \
+ SkiaShader.cpp \
+ Snapshot.cpp \
+ SpotShadow.cpp \
+ TessellationCache.cpp \
+ TextDropShadowCache.cpp \
+ Texture.cpp \
+ TextureCache.cpp \
+ VectorDrawable.cpp \
+ protos/hwui.proto
+
+hwui_test_common_src_files := \
+ $(call all-cpp-files-under, tests/common/scenes) \
+ tests/common/TestContext.cpp \
+ tests/common/TestScene.cpp \
+ tests/common/TestUtils.cpp
+
+hwui_cflags := \
+ -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES \
+ -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\" \
+ -Wall -Wno-unused-parameter -Wunreachable-code -Werror
+
+# GCC false-positives on this warning, and since we -Werror that's
+# a problem
+hwui_cflags += -Wno-free-nonheap-object
+
+ifeq (true, $(HWUI_NEW_OPS))
+ hwui_src_files += \
+ BakedOpDispatcher.cpp \
+ BakedOpRenderer.cpp \
+ BakedOpState.cpp \
+ FrameBuilder.cpp \
+ LayerBuilder.cpp \
+ OpDumper.cpp \
+ RecordingCanvas.cpp
+
+ hwui_cflags += -DHWUI_NEW_OPS
+
+endif
+
+ifndef HWUI_COMPILE_SYMBOLS
+ hwui_cflags += -fvisibility=hidden
+endif
+
+ifdef HWUI_COMPILE_FOR_PERF
+ # TODO: Non-arm?
+ hwui_cflags += -fno-omit-frame-pointer -marm -mapcs
+endif
+
+# This has to be lazy-resolved because it depends on the LOCAL_MODULE_CLASS
+# which varies depending on what is being built
+define hwui_proto_include
+$(call local-generated-sources-dir)/proto/$(LOCAL_PATH)
+endef
+
+hwui_c_includes += \
+ external/skia/include/private \
+ external/skia/src/core \
+ external/harfbuzz_ng/src \
+ external/freetype/include
+
+ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
+ hwui_cflags += -DANDROID_ENABLE_RENDERSCRIPT
+ hwui_c_includes += \
+ $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,) \
+ frameworks/rs/cpp \
+ frameworks/rs
+endif
+
+ifeq (true, $(HWUI_ENABLE_OPENGL_VALIDATION))
+ hwui_cflags += -include debug/wrap_gles.h
+ hwui_src_files += debug/wrap_gles.cpp
+ hwui_c_includes += frameworks/native/opengl/libs/GLES2
+ hwui_cflags += -DDEBUG_OPENGL=3
+endif
+
+
+# ------------------------
+# static library
+# ------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_MODULE := libhwui_static
+LOCAL_CFLAGS := $(hwui_cflags)
+LOCAL_SRC_FILES := $(hwui_src_files)
+LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include)
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+ $(LOCAL_PATH) \
+ $(hwui_c_includes) \
+ $(call hwui_proto_include)
+
+include $(LOCAL_PATH)/hwui_static_deps.mk
+include $(BUILD_STATIC_LIBRARY)
+
+# ------------------------
+# static library null gpu
+# ------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_MODULE := libhwui_static_null_gpu
+LOCAL_CFLAGS := \
+ $(hwui_cflags) \
+ -DHWUI_NULL_GPU
+LOCAL_SRC_FILES := \
+ $(hwui_src_files) \
+ debug/nullegl.cpp \
+ debug/nullgles.cpp
+LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include)
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+ $(LOCAL_PATH) \
+ $(hwui_c_includes) \
+ $(call hwui_proto_include)
+
+include $(LOCAL_PATH)/hwui_static_deps.mk
+include $(BUILD_STATIC_LIBRARY)
+
+# ------------------------
+# shared library
+# ------------------------
+
+include $(CLEAR_VARS)
+
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_MODULE := libhwui
+LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
-include $(LOCAL_PATH)/Android.common.mk
-
+include $(LOCAL_PATH)/hwui_static_deps.mk
include $(BUILD_SHARED_LIBRARY)
-include $(call all-makefiles-under,$(LOCAL_PATH))
+# ------------------------
+# unit tests
+# ------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := hwui_unit_tests
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu
+LOCAL_SHARED_LIBRARIES := libmemunreachable
+LOCAL_CFLAGS := \
+ $(hwui_cflags) \
+ -DHWUI_NULL_GPU
+
+LOCAL_SRC_FILES += \
+ $(hwui_test_common_src_files) \
+ tests/unit/main.cpp \
+ tests/unit/CanvasStateTests.cpp \
+ tests/unit/ClipAreaTests.cpp \
+ tests/unit/DamageAccumulatorTests.cpp \
+ tests/unit/DeviceInfoTests.cpp \
+ tests/unit/FatVectorTests.cpp \
+ tests/unit/FontRendererTests.cpp \
+ tests/unit/GlopBuilderTests.cpp \
+ tests/unit/GpuMemoryTrackerTests.cpp \
+ tests/unit/GradientCacheTests.cpp \
+ tests/unit/LayerUpdateQueueTests.cpp \
+ tests/unit/LinearAllocatorTests.cpp \
+ tests/unit/MatrixTests.cpp \
+ tests/unit/OffscreenBufferPoolTests.cpp \
+ tests/unit/RenderNodeTests.cpp \
+ tests/unit/SkiaBehaviorTests.cpp \
+ tests/unit/SnapshotTests.cpp \
+ tests/unit/StringUtilsTests.cpp \
+ tests/unit/TextDropShadowCacheTests.cpp \
+ tests/unit/VectorDrawableTests.cpp
+
+ifeq (true, $(HWUI_NEW_OPS))
+ LOCAL_SRC_FILES += \
+ tests/unit/BakedOpDispatcherTests.cpp \
+ tests/unit/BakedOpRendererTests.cpp \
+ tests/unit/BakedOpStateTests.cpp \
+ tests/unit/FrameBuilderTests.cpp \
+ tests/unit/LeakCheckTests.cpp \
+ tests/unit/OpDumperTests.cpp \
+ tests/unit/RecordingCanvasTests.cpp \
+ tests/unit/SkiaCanvasTests.cpp
+endif
+
+include $(LOCAL_PATH)/hwui_static_deps.mk
+include $(BUILD_NATIVE_TEST)
+
+# ------------------------
+# Macro-bench app
+# ------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp
+LOCAL_MODULE:= hwuitest
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := hwuitest
+LOCAL_MODULE_STEM_64 := hwuitest64
+LOCAL_CFLAGS := $(hwui_cflags)
+
+# set to libhwui_static_null_gpu to skip actual GL commands
+LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static
+
+LOCAL_SRC_FILES += \
+ $(hwui_test_common_src_files) \
+ tests/macrobench/TestSceneRunner.cpp \
+ tests/macrobench/main.cpp
+
+include $(LOCAL_PATH)/hwui_static_deps.mk
+include $(BUILD_EXECUTABLE)
+
+# ------------------------
+# Micro-bench app
+# ---------------------
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp
+LOCAL_MODULE:= hwuimicro
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := hwuimicro
+LOCAL_MODULE_STEM_64 := hwuimicro64
+LOCAL_CFLAGS := \
+ $(hwui_cflags) \
+ -DHWUI_NULL_GPU
+
+LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static_null_gpu
+LOCAL_STATIC_LIBRARIES := libgoogle-benchmark
+
+LOCAL_SRC_FILES += \
+ $(hwui_test_common_src_files) \
+ tests/microbench/main.cpp \
+ tests/microbench/DisplayListCanvasBench.cpp \
+ tests/microbench/FontBench.cpp \
+ tests/microbench/LinearAllocatorBench.cpp \
+ tests/microbench/PathParserBench.cpp \
+ tests/microbench/ShadowBench.cpp \
+ tests/microbench/TaskManagerBench.cpp
+
+ifeq (true, $(HWUI_NEW_OPS))
+ LOCAL_SRC_FILES += \
+ tests/microbench/FrameBuilderBench.cpp
+endif
+
+include $(LOCAL_PATH)/hwui_static_deps.mk
+include $(BUILD_EXECUTABLE)
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 512e0e24aa93..4d65782f684b 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -33,16 +33,18 @@ namespace uirenderer {
BaseRenderNodeAnimator::BaseRenderNodeAnimator(float finalValue)
: mTarget(nullptr)
+ , mStagingTarget(nullptr)
, mFinalValue(finalValue)
, mDeltaValue(0)
, mFromValue(0)
- , mStagingPlayState(NOT_STARTED)
- , mPlayState(NOT_STARTED)
+ , mStagingPlayState(PlayState::NotStarted)
+ , mPlayState(PlayState::NotStarted)
, mHasStartValue(false)
, mStartTime(0)
, mDuration(300)
, mStartDelay(0)
- , mMayRunAsync(true) {
+ , mMayRunAsync(true)
+ , mPlayTime(0) {
}
BaseRenderNodeAnimator::~BaseRenderNodeAnimator() {
@@ -50,7 +52,7 @@ BaseRenderNodeAnimator::~BaseRenderNodeAnimator() {
void BaseRenderNodeAnimator::checkMutable() {
// Should be impossible to hit as the Java-side also has guards for this
- LOG_ALWAYS_FATAL_IF(mStagingPlayState != NOT_STARTED,
+ LOG_ALWAYS_FATAL_IF(mStagingPlayState != PlayState::NotStarted,
"Animator has already been started!");
}
@@ -81,23 +83,133 @@ void BaseRenderNodeAnimator::setStartDelay(nsecs_t startDelay) {
}
void BaseRenderNodeAnimator::attach(RenderNode* target) {
- mTarget = target;
+ mStagingTarget = target;
onAttached();
}
+void BaseRenderNodeAnimator::start() {
+ mStagingPlayState = PlayState::Running;
+ mStagingRequests.push_back(Request::Start);
+ onStagingPlayStateChanged();
+}
+
+void BaseRenderNodeAnimator::cancel() {
+ mStagingPlayState = PlayState::Finished;
+ mStagingRequests.push_back(Request::Cancel);
+ onStagingPlayStateChanged();
+}
+
+void BaseRenderNodeAnimator::reset() {
+ mStagingPlayState = PlayState::Finished;
+ mStagingRequests.push_back(Request::Reset);
+ onStagingPlayStateChanged();
+}
+
+void BaseRenderNodeAnimator::reverse() {
+ mStagingPlayState = PlayState::Reversing;
+ mStagingRequests.push_back(Request::Reverse);
+ onStagingPlayStateChanged();
+}
+
+void BaseRenderNodeAnimator::end() {
+ mStagingPlayState = PlayState::Finished;
+ mStagingRequests.push_back(Request::End);
+ onStagingPlayStateChanged();
+}
+
+void BaseRenderNodeAnimator::resolveStagingRequest(Request request) {
+ switch (request) {
+ case Request::Start:
+ mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ?
+ mPlayTime : 0;
+ mPlayState = PlayState::Running;
+ break;
+ case Request::Reverse:
+ mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ?
+ mPlayTime : mDuration;
+ mPlayState = PlayState::Reversing;
+ break;
+ case Request::Reset:
+ mPlayTime = 0;
+ mPlayState = PlayState::Finished;
+ break;
+ case Request::Cancel:
+ mPlayState = PlayState::Finished;
+ break;
+ case Request::End:
+ mPlayTime = mPlayState == PlayState::Reversing ? 0 : mDuration;
+ mPlayState = PlayState::Finished;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Invalid staging request: %d", static_cast<int>(request));
+ };
+}
+
void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) {
+ if (mStagingTarget) {
+ RenderNode* oldTarget = mTarget;
+ mTarget = mStagingTarget;
+ mStagingTarget = nullptr;
+ if (oldTarget && oldTarget != mTarget) {
+ oldTarget->onAnimatorTargetChanged(this);
+ }
+ }
+
if (!mHasStartValue) {
doSetStartValue(getValue(mTarget));
}
- if (mStagingPlayState > mPlayState) {
- mPlayState = mStagingPlayState;
- // Oh boy, we're starting! Man the battle stations!
- if (mPlayState == RUNNING) {
- transitionToRunning(context);
- } else if (mPlayState == FINISHED) {
+
+ if (!mStagingRequests.empty()) {
+ // No interpolator was set, use the default
+ if (mPlayState == PlayState::NotStarted && !mInterpolator) {
+ mInterpolator.reset(Interpolator::createDefaultInterpolator());
+ }
+ // Keep track of the play state and play time before they are changed when
+ // staging requests are resolved.
+ nsecs_t currentPlayTime = mPlayTime;
+ PlayState prevFramePlayState = mPlayState;
+
+ // Resolve staging requests one by one.
+ for (Request request : mStagingRequests) {
+ resolveStagingRequest(request);
+ }
+ mStagingRequests.clear();
+
+ if (mStagingPlayState == PlayState::Finished) {
+ // Set the staging play time and end the animation
+ updatePlayTime(mPlayTime);
callOnFinishedListener(context);
+ } else if (mStagingPlayState == PlayState::Running
+ || mStagingPlayState == PlayState::Reversing) {
+ bool changed = currentPlayTime != mPlayTime || prevFramePlayState != mStagingPlayState;
+ if (prevFramePlayState != mStagingPlayState) {
+ transitionToRunning(context);
+ }
+ if (changed) {
+ // Now we need to seek to the stagingPlayTime (i.e. the animation progress that was
+ // requested from UI thread). It is achieved by modifying mStartTime, such that
+ // current time - mStartTime = stagingPlayTime (or mDuration -stagingPlayTime in the
+ // case of reversing)
+ nsecs_t currentFrameTime = context.frameTimeMs();
+ if (mPlayState == PlayState::Reversing) {
+ // Reverse is not supported for animations with a start delay, so here we
+ // assume no start delay.
+ mStartTime = currentFrameTime - (mDuration - mPlayTime);
+ } else {
+ // Animation should play forward
+ if (mPlayTime == 0) {
+ // If the request is to start from the beginning, include start delay.
+ mStartTime = currentFrameTime + mStartDelay;
+ } else {
+ // If the request is to seek to a non-zero play time, then we skip start
+ // delay.
+ mStartTime = currentFrameTime - mPlayTime;
+ }
+ }
+ }
}
}
+ onPushStaging();
}
void BaseRenderNodeAnimator::transitionToRunning(AnimationContext& context) {
@@ -114,55 +226,57 @@ void BaseRenderNodeAnimator::transitionToRunning(AnimationContext& context) {
// Set to 0 so that the animate() basically instantly finishes
mStartTime = 0;
}
- // No interpolator was set, use the default
- if (!mInterpolator) {
- mInterpolator.reset(Interpolator::createDefaultInterpolator());
- }
- if (mDuration < 0 || mDuration > 50000) {
+ if (mDuration < 0) {
ALOGW("Your duration is strange and confusing: %" PRId64, mDuration);
}
}
bool BaseRenderNodeAnimator::animate(AnimationContext& context) {
- if (mPlayState < RUNNING) {
+ if (mPlayState < PlayState::Running) {
return false;
}
- if (mPlayState == FINISHED) {
+ if (mPlayState == PlayState::Finished) {
return true;
}
+ // This should be set before setValue() so animators can query this time when setValue
+ // is called.
+ nsecs_t currentPlayTime = context.frameTimeMs() - mStartTime;
+ bool finished = updatePlayTime(currentPlayTime);
+ if (finished && mPlayState != PlayState::Finished) {
+ mPlayState = PlayState::Finished;
+ callOnFinishedListener(context);
+ }
+ return finished;
+}
+
+bool BaseRenderNodeAnimator::updatePlayTime(nsecs_t playTime) {
+ mPlayTime = mPlayState == PlayState::Reversing ? mDuration - playTime : playTime;
+ onPlayTimeChanged(mPlayTime);
// If BaseRenderNodeAnimator is handling the delay (not typical), then
// because the staging properties reflect the final value, we always need
// to call setValue even if the animation isn't yet running or is still
// being delayed as we need to override the staging value
- if (mStartTime > context.frameTimeMs()) {
+ if (playTime < 0) {
setValue(mTarget, mFromValue);
return false;
}
float fraction = 1.0f;
- if (mPlayState == RUNNING && mDuration > 0) {
- fraction = (float)(context.frameTimeMs() - mStartTime) / mDuration;
- }
- if (fraction >= 1.0f) {
- fraction = 1.0f;
- mPlayState = FINISHED;
+ if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) {
+ fraction = mPlayTime / (float) mDuration;
}
+ fraction = MathUtils::clamp(fraction, 0.0f, 1.0f);
fraction = mInterpolator->interpolate(fraction);
setValue(mTarget, mFromValue + (mDeltaValue * fraction));
- if (mPlayState == FINISHED) {
- callOnFinishedListener(context);
- return true;
- }
-
- return false;
+ return playTime >= mDuration;
}
void BaseRenderNodeAnimator::forceEndNow(AnimationContext& context) {
- if (mPlayState < FINISHED) {
- mPlayState = FINISHED;
+ if (mPlayState < PlayState::Finished) {
+ mPlayState = PlayState::Finished;
callOnFinishedListener(context);
}
}
@@ -206,18 +320,36 @@ RenderPropertyAnimator::RenderPropertyAnimator(RenderProperty property, float fi
void RenderPropertyAnimator::onAttached() {
if (!mHasStartValue
- && mTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
- setStartValue((mTarget->stagingProperties().*mPropertyAccess->getter)());
+ && mStagingTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
+ setStartValue((mStagingTarget->stagingProperties().*mPropertyAccess->getter)());
}
}
void RenderPropertyAnimator::onStagingPlayStateChanged() {
- if (mStagingPlayState == RUNNING) {
- (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
- } else if (mStagingPlayState == FINISHED) {
+ if (mStagingPlayState == PlayState::Running) {
+ if (mStagingTarget) {
+ (mStagingTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
+ } else {
+ // In the case of start delay where stagingTarget has been sync'ed over and null'ed
+ // we delay the properties update to push staging.
+ mShouldUpdateStagingProperties = true;
+ }
+ } else if (mStagingPlayState == PlayState::Finished) {
// We're being canceled, so make sure that whatever values the UI thread
// is observing for us is pushed over
+ mShouldSyncPropertyFields = true;
+ }
+}
+
+void RenderPropertyAnimator::onPushStaging() {
+ if (mShouldUpdateStagingProperties) {
+ (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
+ mShouldUpdateStagingProperties = false;
+ }
+
+ if (mShouldSyncPropertyFields) {
mTarget->setPropertyFieldsDirty(dirtyMask());
+ mShouldSyncPropertyFields = false;
}
}
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index 1b3d8e7f4842..fdae0f32d4e6 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -24,6 +24,8 @@
#include "utils/Macros.h"
+#include <vector>
+
namespace android {
namespace uirenderer {
@@ -59,8 +61,14 @@ public:
mMayRunAsync = mayRunAsync;
}
bool mayRunAsync() { return mMayRunAsync; }
- ANDROID_API void start() { mStagingPlayState = RUNNING; onStagingPlayStateChanged(); }
- ANDROID_API void end() { mStagingPlayState = FINISHED; onStagingPlayStateChanged(); }
+ ANDROID_API void start();
+ ANDROID_API void reset();
+ ANDROID_API void reverse();
+ // Terminates the animation at its current progress.
+ ANDROID_API void cancel();
+
+ // Terminates the animation and skip to the end of the animation.
+ ANDROID_API void end();
void attach(RenderNode* target);
virtual void onAttached() {}
@@ -68,33 +76,58 @@ public:
void pushStaging(AnimationContext& context);
bool animate(AnimationContext& context);
- bool isRunning() { return mPlayState == RUNNING; }
- bool isFinished() { return mPlayState == FINISHED; }
+ bool isRunning() { return mPlayState == PlayState::Running
+ || mPlayState == PlayState::Reversing; }
+ bool isFinished() { return mPlayState == PlayState::Finished; }
float finalValue() { return mFinalValue; }
ANDROID_API virtual uint32_t dirtyMask() = 0;
void forceEndNow(AnimationContext& context);
+ RenderNode* target() { return mTarget; }
+ RenderNode* stagingTarget() { return mStagingTarget; }
protected:
+ // PlayState is used by mStagingPlayState and mPlayState to track the state initiated from UI
+ // thread and Render Thread animation state, respectively.
+ // From the UI thread, mStagingPlayState transition looks like
+ // NotStarted -> Running/Reversing -> Finished
+ // ^ |
+ // | |
+ // ----------------------
+ // Note: For mStagingState, the Finished state (optional) is only set when the animation is
+ // terminated by user.
+ //
+ // On Render Thread, mPlayState transition:
+ // NotStart -> Running/Reversing-> Finished
+ // ^ |
+ // | |
+ // ------------------
+ // Note that if the animation is in Running/Reversing state, calling start or reverse again
+ // would do nothing if the animation has the same play direction as the request; otherwise,
+ // the animation would start from where it is and change direction (i.e. Reversing <-> Running)
+
+ enum class PlayState {
+ NotStarted,
+ Running,
+ Reversing,
+ Finished,
+ };
+
BaseRenderNodeAnimator(float finalValue);
virtual ~BaseRenderNodeAnimator();
virtual float getValue(RenderNode* target) const = 0;
virtual void setValue(RenderNode* target, float value) = 0;
- RenderNode* target() { return mTarget; }
void callOnFinishedListener(AnimationContext& context);
virtual void onStagingPlayStateChanged() {}
-
- enum PlayState {
- NOT_STARTED,
- RUNNING,
- FINISHED,
- };
+ virtual void onPlayTimeChanged(nsecs_t playTime) {}
+ virtual void onPushStaging() {}
RenderNode* mTarget;
+ RenderNode* mStagingTarget;
float mFinalValue;
float mDeltaValue;
@@ -108,13 +141,28 @@ protected:
nsecs_t mDuration;
nsecs_t mStartDelay;
bool mMayRunAsync;
+ // Play Time tracks the progress of animation, it should always be [0, mDuration], 0 being
+ // the beginning of the animation, will reach mDuration at the end of an animation.
+ nsecs_t mPlayTime;
sp<AnimationListener> mListener;
private:
+ enum class Request {
+ Start,
+ Reverse,
+ Reset,
+ Cancel,
+ End
+ };
inline void checkMutable();
virtual void transitionToRunning(AnimationContext& context);
void doSetStartValue(float value);
+ bool updatePlayTime(nsecs_t playTime);
+ void resolveStagingRequest(Request request);
+
+ std::vector<Request> mStagingRequests;
+
};
class RenderPropertyAnimator : public BaseRenderNodeAnimator {
@@ -143,6 +191,7 @@ protected:
virtual void setValue(RenderNode* target, float value) override;
virtual void onAttached() override;
virtual void onStagingPlayStateChanged() override;
+ virtual void onPushStaging() override;
private:
typedef bool (RenderProperties::*SetFloatProperty)(float value);
@@ -152,6 +201,8 @@ private:
const PropertyAccessors* mPropertyAccess;
static const PropertyAccessors PROPERTY_ACCESSOR_LUT[];
+ bool mShouldSyncPropertyFields = false;
+ bool mShouldUpdateStagingProperties = false;
};
class CanvasPropertyPrimitiveAnimator : public BaseRenderNodeAnimator {
diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
index 0dababd774c1..f170e9cda8af 100644
--- a/libs/hwui/AnimatorManager.cpp
+++ b/libs/hwui/AnimatorManager.cpp
@@ -27,9 +27,8 @@ namespace uirenderer {
using namespace std;
-static void unref(BaseRenderNodeAnimator* animator) {
+static void detach(sp<BaseRenderNodeAnimator>& animator) {
animator->detach();
- animator->decStrong(nullptr);
}
AnimatorManager::AnimatorManager(RenderNode& parent)
@@ -38,14 +37,28 @@ AnimatorManager::AnimatorManager(RenderNode& parent)
}
AnimatorManager::~AnimatorManager() {
- for_each(mNewAnimators.begin(), mNewAnimators.end(), unref);
- for_each(mAnimators.begin(), mAnimators.end(), unref);
+ for_each(mNewAnimators.begin(), mNewAnimators.end(), detach);
+ for_each(mAnimators.begin(), mAnimators.end(), detach);
}
void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
- animator->incStrong(nullptr);
+ RenderNode* stagingTarget = animator->stagingTarget();
+ if (stagingTarget == &mParent) {
+ return;
+ }
+ mNewAnimators.emplace_back(animator.get());
+ // If the animator is already attached to other RenderNode, remove it from that RenderNode's
+ // new animator list. This ensures one animator only ends up in one newAnimatorList during one
+ // frame, even when it's added multiple times to multiple targets.
+ if (stagingTarget) {
+ stagingTarget->removeAnimator(animator);
+ }
animator->attach(&mParent);
- mNewAnimators.push_back(animator.get());
+}
+
+void AnimatorManager::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) {
+ mNewAnimators.erase(std::remove(mNewAnimators.begin(), mNewAnimators.end(), animator),
+ mNewAnimators.end());
}
void AnimatorManager::setAnimationHandle(AnimationHandle* handle) {
@@ -56,38 +69,40 @@ void AnimatorManager::setAnimationHandle(AnimationHandle* handle) {
&mParent, mParent.getName());
}
-template<typename T>
-static void move_all(T& source, T& dest) {
- dest.reserve(source.size() + dest.size());
- for (typename T::iterator it = source.begin(); it != source.end(); it++) {
- dest.push_back(*it);
- }
- source.clear();
-}
-
void AnimatorManager::pushStaging() {
if (mNewAnimators.size()) {
LOG_ALWAYS_FATAL_IF(!mAnimationHandle,
"Trying to start new animators on %p (%s) without an animation handle!",
&mParent, mParent.getName());
- // Since this is a straight move, we don't need to inc/dec the ref count
- move_all(mNewAnimators, mAnimators);
+
+ // Only add new animators that are not already in the mAnimators list
+ for (auto& anim : mNewAnimators) {
+ if (anim->target() != &mParent) {
+ mAnimators.push_back(std::move(anim));
+ }
+ }
+ mNewAnimators.clear();
}
- for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) {
- (*it)->pushStaging(mAnimationHandle->context());
+ for (auto& animator : mAnimators) {
+ animator->pushStaging(mAnimationHandle->context());
}
}
+void AnimatorManager::onAnimatorTargetChanged(BaseRenderNodeAnimator* animator) {
+ LOG_ALWAYS_FATAL_IF(animator->target() == &mParent, "Target has not been changed");
+ mAnimators.erase(std::remove(mAnimators.begin(), mAnimators.end(), animator), mAnimators.end());
+}
+
class AnimateFunctor {
public:
- AnimateFunctor(TreeInfo& info, AnimationContext& context)
- : dirtyMask(0), mInfo(info), mContext(context) {}
+ AnimateFunctor(TreeInfo& info, AnimationContext& context, uint32_t* outDirtyMask)
+ : mInfo(info), mContext(context), mDirtyMask(outDirtyMask) {}
- bool operator() (BaseRenderNodeAnimator* animator) {
- dirtyMask |= animator->dirtyMask();
+ bool operator() (sp<BaseRenderNodeAnimator>& animator) {
+ *mDirtyMask |= animator->dirtyMask();
bool remove = animator->animate(mContext);
if (remove) {
- animator->decStrong(nullptr);
+ animator->detach();
} else {
if (animator->isRunning()) {
mInfo.out.hasAnimations = true;
@@ -99,11 +114,10 @@ public:
return remove;
}
- uint32_t dirtyMask;
-
private:
TreeInfo& mInfo;
AnimationContext& mContext;
+ uint32_t* mDirtyMask;
};
uint32_t AnimatorManager::animate(TreeInfo& info) {
@@ -124,27 +138,24 @@ uint32_t AnimatorManager::animate(TreeInfo& info) {
}
void AnimatorManager::animateNoDamage(TreeInfo& info) {
- if (!mAnimators.size()) return;
-
animateCommon(info);
}
uint32_t AnimatorManager::animateCommon(TreeInfo& info) {
- AnimateFunctor functor(info, mAnimationHandle->context());
- std::vector< BaseRenderNodeAnimator* >::iterator newEnd;
- newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
+ uint32_t dirtyMask;
+ AnimateFunctor functor(info, mAnimationHandle->context(), &dirtyMask);
+ auto newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
mAnimators.erase(newEnd, mAnimators.end());
mAnimationHandle->notifyAnimationsRan();
mParent.mProperties.updateMatrix();
- return functor.dirtyMask;
+ return dirtyMask;
}
-static void endStagingAnimator(BaseRenderNodeAnimator* animator) {
- animator->end();
+static void endStagingAnimator(sp<BaseRenderNodeAnimator>& animator) {
+ animator->cancel();
if (animator->listener()) {
- animator->listener()->onAnimationFinished(animator);
+ animator->listener()->onAnimationFinished(animator.get());
}
- animator->decStrong(nullptr);
}
void AnimatorManager::endAllStagingAnimators() {
@@ -159,9 +170,8 @@ class EndActiveAnimatorsFunctor {
public:
EndActiveAnimatorsFunctor(AnimationContext& context) : mContext(context) {}
- void operator() (BaseRenderNodeAnimator* animator) {
+ void operator() (sp<BaseRenderNodeAnimator>& animator) {
animator->forceEndNow(mContext);
- animator->decStrong(nullptr);
}
private:
@@ -169,7 +179,7 @@ private:
};
void AnimatorManager::endAllActiveAnimators() {
- ALOGD("endAllStagingAnimators on %p (%s) with handle %p",
+ ALOGD("endAllActiveAnimators on %p (%s) with handle %p",
&mParent, mParent.getName(), mAnimationHandle);
EndActiveAnimatorsFunctor functor(mAnimationHandle->context());
for_each(mAnimators.begin(), mAnimators.end(), functor);
diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h
index fb75eb8599b4..61f6179d217c 100644
--- a/libs/hwui/AnimatorManager.h
+++ b/libs/hwui/AnimatorManager.h
@@ -39,11 +39,13 @@ public:
~AnimatorManager();
void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
+ void removeAnimator(const sp<BaseRenderNodeAnimator>& animator);
void setAnimationHandle(AnimationHandle* handle);
bool hasAnimationHandle() { return mAnimationHandle; }
void pushStaging();
+ void onAnimatorTargetChanged(BaseRenderNodeAnimator* animator);
// Returns the combined dirty mask of all animators run
uint32_t animate(TreeInfo& info);
@@ -66,9 +68,8 @@ private:
AnimationHandle* mAnimationHandle;
// To improve the efficiency of resizing & removing from the vector
- // use manual ref counting instead of sp<>.
- std::vector<BaseRenderNodeAnimator*> mNewAnimators;
- std::vector<BaseRenderNodeAnimator*> mAnimators;
+ std::vector< sp<BaseRenderNodeAnimator> > mNewAnimators;
+ std::vector< sp<BaseRenderNodeAnimator> > mAnimators;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp
index 2889d2ff0b78..6afff1b7158e 100644
--- a/libs/hwui/AssetAtlas.cpp
+++ b/libs/hwui/AssetAtlas.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include "AssetAtlas.h"
#include "Caches.h"
#include "Image.h"
@@ -41,54 +39,36 @@ void AssetAtlas::init(sp<GraphicBuffer> buffer, int64_t* map, int count) {
if (!mTexture) {
Caches& caches = Caches::getInstance();
mTexture = new Texture(caches);
- mTexture->width = buffer->getWidth();
- mTexture->height = buffer->getHeight();
+ mTexture->wrap(mImage->getTexture(),
+ buffer->getWidth(), buffer->getHeight(), GL_RGBA);
createEntries(caches, map, count);
}
} else {
ALOGW("Could not create atlas image");
- delete mImage;
- mImage = nullptr;
+ terminate();
}
-
- updateTextureId();
}
void AssetAtlas::terminate() {
- if (mImage) {
- delete mImage;
- mImage = nullptr;
- updateTextureId();
- }
-}
-
-
-void AssetAtlas::updateTextureId() {
- mTexture->id = mImage ? mImage->getTexture() : 0;
- if (mTexture->id) {
- // Texture ID changed, force-set to defaults to sync the wrapper & GL
- // state objects
- mTexture->setWrap(GL_CLAMP_TO_EDGE, false, true);
- mTexture->setFilter(GL_NEAREST, false, true);
- }
- for (size_t i = 0; i < mEntries.size(); i++) {
- AssetAtlas::Entry* entry = mEntries.valueAt(i);
- entry->texture->id = mTexture->id;
- }
+ delete mImage;
+ mImage = nullptr;
+ delete mTexture;
+ mTexture = nullptr;
+ mEntries.clear();
}
///////////////////////////////////////////////////////////////////////////////
// Entries
///////////////////////////////////////////////////////////////////////////////
-AssetAtlas::Entry* AssetAtlas::getEntry(const SkBitmap* bitmap) const {
- ssize_t index = mEntries.indexOfKey(bitmap->pixelRef());
- return index >= 0 ? mEntries.valueAt(index) : nullptr;
+AssetAtlas::Entry* AssetAtlas::getEntry(const SkPixelRef* pixelRef) const {
+ auto result = mEntries.find(pixelRef);
+ return result != mEntries.end() ? result->second.get() : nullptr;
}
-Texture* AssetAtlas::getEntryTexture(const SkBitmap* bitmap) const {
- ssize_t index = mEntries.indexOfKey(bitmap->pixelRef());
- return index >= 0 ? mEntries.valueAt(index)->texture : nullptr;
+Texture* AssetAtlas::getEntryTexture(const SkPixelRef* pixelRef) const {
+ auto result = mEntries.find(pixelRef);
+ return result != mEntries.end() ? result->second->texture : nullptr;
}
/**
@@ -96,7 +76,8 @@ Texture* AssetAtlas::getEntryTexture(const SkBitmap* bitmap) const {
* instead of applying the changes to the virtual textures.
*/
struct DelegateTexture: public Texture {
- DelegateTexture(Caches& caches, Texture* delegate): Texture(caches), mDelegate(delegate) { }
+ DelegateTexture(Caches& caches, Texture* delegate)
+ : Texture(caches), mDelegate(delegate) { }
virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false,
bool force = false, GLenum renderTarget = GL_TEXTURE_2D) override {
@@ -113,8 +94,8 @@ private:
}; // struct DelegateTexture
void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) {
- const float width = float(mTexture->width);
- const float height = float(mTexture->height);
+ const float width = float(mTexture->width());
+ const float height = float(mTexture->height());
for (int i = 0; i < count; ) {
SkPixelRef* pixelRef = reinterpret_cast<SkPixelRef*>(map[i++]);
@@ -135,13 +116,13 @@ void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) {
Texture* texture = new DelegateTexture(caches, mTexture);
texture->blend = !SkAlphaTypeIsOpaque(pixelRef->info().alphaType());
- texture->width = pixelRef->info().width();
- texture->height = pixelRef->info().height();
+ texture->wrap(mTexture->id(), pixelRef->info().width(),
+ pixelRef->info().height(), mTexture->format());
- Entry* entry = new Entry(pixelRef, texture, mapper, *this);
+ std::unique_ptr<Entry> entry(new Entry(pixelRef, texture, mapper, *this));
texture->uvMapper = &entry->uvMapper;
- mEntries.add(entry->pixelRef, entry);
+ mEntries.emplace(entry->pixelRef, std::move(entry));
}
}
diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h
index f1cd0b4947dc..75400ff494c3 100644
--- a/libs/hwui/AssetAtlas.h
+++ b/libs/hwui/AssetAtlas.h
@@ -17,18 +17,16 @@
#ifndef ANDROID_HWUI_ASSET_ATLAS_H
#define ANDROID_HWUI_ASSET_ATLAS_H
-#include <GLES2/gl2.h>
-
-#include <ui/GraphicBuffer.h>
-
-#include <utils/KeyedVector.h>
+#include "Texture.h"
+#include "UvMapper.h"
#include <cutils/compiler.h>
-
+#include <GLES2/gl2.h>
+#include <ui/GraphicBuffer.h>
#include <SkBitmap.h>
-#include "Texture.h"
-#include "UvMapper.h"
+#include <memory>
+#include <unordered_map>
namespace android {
namespace uirenderer {
@@ -71,6 +69,10 @@ public:
return texture->blend ? &atlas.mBlendKey : &atlas.mOpaqueKey;
}
+ ~Entry() {
+ delete texture;
+ }
+
private:
/**
* The pixel ref that generated this atlas entry.
@@ -90,10 +92,6 @@ public:
, atlas(atlas) {
}
- ~Entry() {
- delete texture;
- }
-
friend class AssetAtlas;
};
@@ -127,7 +125,7 @@ public:
* Can return 0 if the atlas is not initialized.
*/
uint32_t getWidth() const {
- return mTexture ? mTexture->width : 0;
+ return mTexture ? mTexture->width() : 0;
}
/**
@@ -135,7 +133,7 @@ public:
* Can return 0 if the atlas is not initialized.
*/
uint32_t getHeight() const {
- return mTexture ? mTexture->height : 0;
+ return mTexture ? mTexture->height() : 0;
}
/**
@@ -143,24 +141,23 @@ public:
* Can return 0 if the atlas is not initialized.
*/
GLuint getTexture() const {
- return mTexture ? mTexture->id : 0;
+ return mTexture ? mTexture->id() : 0;
}
/**
* Returns the entry in the atlas associated with the specified
- * bitmap. If the bitmap is not in the atlas, return NULL.
+ * pixelRef. If the pixelRef is not in the atlas, return NULL.
*/
- Entry* getEntry(const SkBitmap* bitmap) const;
+ Entry* getEntry(const SkPixelRef* pixelRef) const;
/**
* Returns the texture for the atlas entry associated with the
- * specified bitmap. If the bitmap is not in the atlas, return NULL.
+ * specified pixelRef. If the pixelRef is not in the atlas, return NULL.
*/
- Texture* getEntryTexture(const SkBitmap* bitmap) const;
+ Texture* getEntryTexture(const SkPixelRef* pixelRef) const;
private:
void createEntries(Caches& caches, int64_t* map, int count);
- void updateTextureId();
Texture* mTexture;
Image* mImage;
@@ -168,7 +165,7 @@ private:
const bool mBlendKey;
const bool mOpaqueKey;
- KeyedVector<const SkPixelRef*, Entry*> mEntries;
+ std::unordered_map<const SkPixelRef*, std::unique_ptr<Entry>> mEntries;
}; // class AssetAtlas
}; // namespace uirenderer
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
new file mode 100644
index 000000000000..0f670a822899
--- /dev/null
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -0,0 +1,850 @@
+/*
+ * Copyright (C) 2015 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 "BakedOpDispatcher.h"
+
+#include "BakedOpRenderer.h"
+#include "Caches.h"
+#include "Glop.h"
+#include "GlopBuilder.h"
+#include "Patch.h"
+#include "PathTessellator.h"
+#include "renderstate/OffscreenBufferPool.h"
+#include "renderstate/RenderState.h"
+#include "utils/GLUtils.h"
+#include "VertexBuffer.h"
+
+#include <algorithm>
+#include <math.h>
+#include <SkPaintDefaults.h>
+#include <SkPathOps.h>
+
+namespace android {
+namespace uirenderer {
+
+static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds, const Rect& texCoord) {
+ vertices[0] = { bounds.left, bounds.top, texCoord.left, texCoord.top };
+ vertices[1] = { bounds.right, bounds.top, texCoord.right, texCoord.top };
+ vertices[2] = { bounds.left, bounds.bottom, texCoord.left, texCoord.bottom };
+ vertices[3] = { bounds.right, bounds.bottom, texCoord.right, texCoord.bottom };
+}
+
+void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer,
+ const MergedBakedOpList& opList) {
+
+ const BakedOpState& firstState = *(opList.states[0]);
+ const SkBitmap* bitmap = (static_cast<const BitmapOp*>(opList.states[0]->op))->bitmap;
+
+ AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(bitmap->pixelRef());
+ Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(bitmap);
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ TextureVertex vertices[opList.count * 4];
+ Rect texCoords(0, 0, 1, 1);
+ if (entry) {
+ entry->uvMapper.map(texCoords);
+ }
+ for (size_t i = 0; i < opList.count; i++) {
+ const BakedOpState& state = *(opList.states[i]);
+ TextureVertex* rectVerts = &vertices[i * 4];
+
+ // calculate unclipped bounds, since they'll determine texture coordinates
+ Rect opBounds = state.op->unmappedBounds;
+ state.computedState.transform.mapRect(opBounds);
+ if (CC_LIKELY(state.computedState.transform.isPureTranslate())) {
+ // pure translate, so snap (same behavior as onBitmapOp)
+ opBounds.snapToPixelBoundaries();
+ }
+ storeTexturedRect(rectVerts, opBounds, texCoords);
+ renderer.dirtyRenderTarget(opBounds);
+ }
+
+ const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType)
+ ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(firstState.roundRectClipState)
+ .setMeshTexturedIndexedQuads(vertices, opList.count * 6)
+ .setFillTexturePaint(*texture, textureFillFlags, firstState.op->paint, firstState.alpha)
+ .setTransform(Matrix4::identity(), TransformFlags::None)
+ .setModelViewIdentityEmptyBounds()
+ .build();
+ ClipRect renderTargetClip(opList.clip);
+ const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
+ renderer.renderGlop(nullptr, clip, glop);
+}
+
+void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer,
+ const MergedBakedOpList& opList) {
+ const PatchOp& firstOp = *(static_cast<const PatchOp*>(opList.states[0]->op));
+ const BakedOpState& firstState = *(opList.states[0]);
+ AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(
+ firstOp.bitmap->pixelRef());
+
+ // Batches will usually contain a small number of items so it's
+ // worth performing a first iteration to count the exact number
+ // of vertices we need in the new mesh
+ uint32_t totalVertices = 0;
+
+ for (size_t i = 0; i < opList.count; i++) {
+ const PatchOp& op = *(static_cast<const PatchOp*>(opList.states[i]->op));
+
+ // TODO: cache mesh lookups
+ const Patch* opMesh = renderer.caches().patchCache.get(
+ entry, op.bitmap->width(), op.bitmap->height(),
+ op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
+ totalVertices += opMesh->verticesCount;
+ }
+
+ const bool dirtyRenderTarget = renderer.offscreenRenderTarget();
+
+ uint32_t indexCount = 0;
+
+ TextureVertex vertices[totalVertices];
+ TextureVertex* vertex = &vertices[0];
+ // Create a mesh that contains the transformed vertices for all the
+ // 9-patch objects that are part of the batch. Note that onDefer()
+ // enforces ops drawn by this function to have a pure translate or
+ // identity matrix
+ for (size_t i = 0; i < opList.count; i++) {
+ const PatchOp& op = *(static_cast<const PatchOp*>(opList.states[i]->op));
+ const BakedOpState& state = *opList.states[i];
+
+ // TODO: cache mesh lookups
+ const Patch* opMesh = renderer.caches().patchCache.get(
+ entry, op.bitmap->width(), op.bitmap->height(),
+ op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
+
+
+ uint32_t vertexCount = opMesh->verticesCount;
+ if (vertexCount == 0) continue;
+
+ // We use the bounds to know where to translate our vertices
+ // Using patchOp->state.mBounds wouldn't work because these
+ // bounds are clipped
+ const float tx = floorf(state.computedState.transform.getTranslateX()
+ + op.unmappedBounds.left + 0.5f);
+ const float ty = floorf(state.computedState.transform.getTranslateY()
+ + op.unmappedBounds.top + 0.5f);
+
+ // Copy & transform all the vertices for the current operation
+ TextureVertex* opVertices = opMesh->vertices.get();
+ for (uint32_t j = 0; j < vertexCount; j++, opVertices++) {
+ TextureVertex::set(vertex++,
+ opVertices->x + tx, opVertices->y + ty,
+ opVertices->u, opVertices->v);
+ }
+
+ // Dirty the current layer if possible. When the 9-patch does not
+ // contain empty quads we can take a shortcut and simply set the
+ // dirty rect to the object's bounds.
+ if (dirtyRenderTarget) {
+ if (!opMesh->hasEmptyQuads) {
+ renderer.dirtyRenderTarget(Rect(tx, ty,
+ tx + op.unmappedBounds.getWidth(), ty + op.unmappedBounds.getHeight()));
+ } else {
+ const size_t count = opMesh->quads.size();
+ for (size_t i = 0; i < count; i++) {
+ const Rect& quadBounds = opMesh->quads[i];
+ const float x = tx + quadBounds.left;
+ const float y = ty + quadBounds.top;
+ renderer.dirtyRenderTarget(Rect(x, y,
+ x + quadBounds.getWidth(), y + quadBounds.getHeight()));
+ }
+ }
+ }
+
+ indexCount += opMesh->indexCount;
+ }
+
+
+ Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(firstOp.bitmap);
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ // 9 patches are built for stretching - always filter
+ int textureFillFlags = TextureFillFlags::ForceFilter;
+ if (firstOp.bitmap->colorType() == kAlpha_8_SkColorType) {
+ textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture;
+ }
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(firstState.roundRectClipState)
+ .setMeshTexturedIndexedQuads(vertices, indexCount)
+ .setFillTexturePaint(*texture, textureFillFlags, firstOp.paint, firstState.alpha)
+ .setTransform(Matrix4::identity(), TransformFlags::None)
+ .setModelViewIdentityEmptyBounds()
+ .build();
+ ClipRect renderTargetClip(opList.clip);
+ const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
+ renderer.renderGlop(nullptr, clip, glop);
+}
+
+static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
+ const TextOp& op, const BakedOpState& textOpState) {
+ renderer.caches().textureState().activateTexture(0);
+
+ PaintUtils::TextShadow textShadow;
+ if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
+ LOG_ALWAYS_FATAL("failed to query shadow attributes");
+ }
+
+ renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
+ ShadowTexture* texture = renderer.caches().dropShadowCache.get(
+ op.paint, op.glyphs, op.glyphCount, textShadow.radius, op.positions);
+ // If the drop shadow exceeds the max texture size or couldn't be
+ // allocated, skip drawing
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ const float sx = op.x - texture->left + textShadow.dx;
+ const float sy = op.y - texture->top + textShadow.dy;
+
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(textOpState.roundRectClipState)
+ .setMeshTexturedUnitQuad(nullptr)
+ .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, textOpState.alpha)
+ .setTransform(textOpState.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height()))
+ .build();
+
+ // Compute damage bounds and clip (since may differ from those in textOpState).
+ // Bounds should be same as text op, but with dx/dy offset and radius outset
+ // applied in local space.
+ auto& transform = textOpState.computedState.transform;
+ Rect shadowBounds = op.unmappedBounds; // STROKE
+ const bool expandForStroke = op.paint->getStyle() != SkPaint::kFill_Style;
+ if (expandForStroke) {
+ shadowBounds.outset(op.paint->getStrokeWidth() * 0.5f);
+ }
+ shadowBounds.translate(textShadow.dx, textShadow.dy);
+ shadowBounds.outset(textShadow.radius, textShadow.radius);
+ transform.mapRect(shadowBounds);
+ if (CC_UNLIKELY(expandForStroke &&
+ (!transform.isPureTranslate() || op.paint->getStrokeWidth() < 1.0f))) {
+ shadowBounds.outset(0.5f);
+ }
+
+ auto clipState = textOpState.computedState.clipState;
+ if (clipState->mode != ClipMode::Rectangle
+ || !clipState->rect.contains(shadowBounds)) {
+ // need clip, so pass it and clip bounds
+ shadowBounds.doIntersect(clipState->rect);
+ } else {
+ // don't need clip, ignore
+ clipState = nullptr;
+ }
+
+ renderer.renderGlop(&shadowBounds, clipState, glop);
+}
+
+enum class TextRenderType {
+ Defer,
+ Flush
+};
+
+static void renderTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state,
+ const ClipBase* renderClip, TextRenderType renderType) {
+ FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
+
+ if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ renderTextShadow(renderer, fontRenderer, op, state);
+ }
+
+ float x = op.x;
+ float y = op.y;
+ const Matrix4& transform = state.computedState.transform;
+ const bool pureTranslate = transform.isPureTranslate();
+ if (CC_LIKELY(pureTranslate)) {
+ x = floorf(x + transform.getTranslateX() + 0.5f);
+ y = floorf(y + transform.getTranslateY() + 0.5f);
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(false);
+ } else if (CC_UNLIKELY(transform.isPerspective())) {
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(true);
+ } else {
+ // We only pass a partial transform to the font renderer. That partial
+ // matrix defines how glyphs are rasterized. Typically we want glyphs
+ // to be rasterized at their final size on screen, which means the partial
+ // matrix needs to take the scale factor into account.
+ // When a partial matrix is used to transform glyphs during rasterization,
+ // the mesh is generated with the inverse transform (in the case of scale,
+ // the mesh is generated at 1.0 / scale for instance.) This allows us to
+ // apply the full transform matrix at draw time in the vertex shader.
+ // Applying the full matrix in the shader is the easiest way to handle
+ // rotation and perspective and allows us to always generated quads in the
+ // font renderer which greatly simplifies the code, clipping in particular.
+ float sx, sy;
+ transform.decomposeScale(sx, sy);
+ fontRenderer.setFont(op.paint, SkMatrix::MakeScale(
+ roundf(std::max(1.0f, sx)),
+ roundf(std::max(1.0f, sy))));
+ fontRenderer.setTextureFiltering(true);
+ }
+ Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
+
+ int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
+ SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
+ TextDrawFunctor functor(&renderer, &state, renderClip,
+ x, y, pureTranslate, alpha, mode, op.paint);
+
+ bool forceFinish = (renderType == TextRenderType::Flush);
+ bool mustDirtyRenderTarget = renderer.offscreenRenderTarget();
+ const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect() : nullptr;
+ fontRenderer.renderPosText(op.paint, localOpClip, op.glyphs, op.glyphCount, x, y,
+ op.positions, mustDirtyRenderTarget ? &layerBounds : nullptr, &functor, forceFinish);
+
+ if (mustDirtyRenderTarget) {
+ if (!pureTranslate) {
+ transform.mapRect(layerBounds);
+ }
+ renderer.dirtyRenderTarget(layerBounds);
+ }
+}
+
+void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer,
+ const MergedBakedOpList& opList) {
+ ClipRect renderTargetClip(opList.clip);
+ const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
+ for (size_t i = 0; i < opList.count; i++) {
+ const BakedOpState& state = *(opList.states[i]);
+ const TextOp& op = *(static_cast<const TextOp*>(state.op));
+ TextRenderType renderType = (i + 1 == opList.count)
+ ? TextRenderType::Flush : TextRenderType::Defer;
+ renderTextOp(renderer, op, state, clip, renderType);
+ }
+}
+
+namespace VertexBufferRenderFlags {
+ enum {
+ Offset = 0x1,
+ ShadowInterp = 0x2,
+ };
+}
+
+static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state,
+ const VertexBuffer& vertexBuffer, float translateX, float translateY,
+ const SkPaint& paint, int vertexBufferRenderFlags) {
+ if (CC_LIKELY(vertexBuffer.getVertexCount())) {
+ bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp;
+ const int transformFlags = vertexBufferRenderFlags & VertexBufferRenderFlags::Offset
+ ? TransformFlags::OffsetByFudgeFactor : 0;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshVertexBuffer(vertexBuffer, shadowInterp)
+ .setFillPaint(paint, state.alpha)
+ .setTransform(state.computedState.transform, transformFlags)
+ .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds())
+ .build();
+ renderer.renderGlop(state, glop);
+ }
+}
+
+static void renderConvexPath(BakedOpRenderer& renderer, const BakedOpState& state,
+ const SkPath& path, const SkPaint& paint) {
+ VertexBuffer vertexBuffer;
+ // TODO: try clipping large paths to viewport
+ PathTessellator::tessellatePath(path, &paint, state.computedState.transform, vertexBuffer);
+ renderVertexBuffer(renderer, state, vertexBuffer, 0.0f, 0.0f, paint, 0);
+}
+
+static void renderPathTexture(BakedOpRenderer& renderer, const BakedOpState& state,
+ float xOffset, float yOffset, PathTexture& texture, const SkPaint& paint) {
+ Rect dest(texture.width(), texture.height());
+ dest.translate(xOffset + texture.left - texture.offset,
+ yOffset + texture.top - texture.offset);
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUnitQuad(nullptr)
+ .setFillPathTexturePaint(texture, paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRect(dest)
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+SkRect getBoundsOfFill(const RecordedOp& op) {
+ SkRect bounds = op.unmappedBounds.toSkRect();
+ if (op.paint->getStyle() == SkPaint::kStrokeAndFill_Style) {
+ float outsetDistance = op.paint->getStrokeWidth() / 2;
+ bounds.outset(outsetDistance, outsetDistance);
+ }
+ return bounds;
+}
+
+void BakedOpDispatcher::onArcOp(BakedOpRenderer& renderer, const ArcOp& op, const BakedOpState& state) {
+ // TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180)
+ if (op.paint->getStyle() != SkPaint::kStroke_Style
+ || op.paint->getPathEffect() != nullptr
+ || op.useCenter) {
+ PathTexture* texture = renderer.caches().pathCache.getArc(
+ op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(),
+ op.startAngle, op.sweepAngle, op.useCenter, op.paint);
+ const AutoTexture holder(texture);
+ if (CC_LIKELY(holder.texture)) {
+ renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top,
+ *texture, *(op.paint));
+ }
+ } else {
+ SkRect rect = getBoundsOfFill(op);
+ SkPath path;
+ if (op.useCenter) {
+ path.moveTo(rect.centerX(), rect.centerY());
+ }
+ path.arcTo(rect, op.startAngle, op.sweepAngle, !op.useCenter);
+ if (op.useCenter) {
+ path.close();
+ }
+ renderConvexPath(renderer, state, path, *(op.paint));
+ }
+}
+
+void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) {
+ Texture* texture = renderer.getTexture(op.bitmap);
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
+ ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUnitQuad(texture->uvMapper)
+ .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRectSnap(Rect(texture->width(), texture->height()))
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMeshOp& op, const BakedOpState& state) {
+ const static UvMapper defaultUvMapper;
+ const uint32_t elementCount = op.meshWidth * op.meshHeight * 6;
+
+ std::unique_ptr<ColorTextureVertex[]> mesh(new ColorTextureVertex[elementCount]);
+ ColorTextureVertex* vertex = &mesh[0];
+
+ const int* colors = op.colors;
+ std::unique_ptr<int[]> tempColors;
+ if (!colors) {
+ uint32_t colorsCount = (op.meshWidth + 1) * (op.meshHeight + 1);
+ tempColors.reset(new int[colorsCount]);
+ memset(tempColors.get(), 0xff, colorsCount * sizeof(int));
+ colors = tempColors.get();
+ }
+
+ Texture* texture = renderer.renderState().assetAtlas().getEntryTexture(op.bitmap->pixelRef());
+ const UvMapper& mapper(texture && texture->uvMapper ? *texture->uvMapper : defaultUvMapper);
+
+ for (int32_t y = 0; y < op.meshHeight; y++) {
+ for (int32_t x = 0; x < op.meshWidth; x++) {
+ uint32_t i = (y * (op.meshWidth + 1) + x) * 2;
+
+ float u1 = float(x) / op.meshWidth;
+ float u2 = float(x + 1) / op.meshWidth;
+ float v1 = float(y) / op.meshHeight;
+ float v2 = float(y + 1) / op.meshHeight;
+
+ mapper.map(u1, v1, u2, v2);
+
+ int ax = i + (op.meshWidth + 1) * 2;
+ int ay = ax + 1;
+ int bx = i;
+ int by = bx + 1;
+ int cx = i + 2;
+ int cy = cx + 1;
+ int dx = i + (op.meshWidth + 1) * 2 + 2;
+ int dy = dx + 1;
+
+ const float* vertices = op.vertices;
+ ColorTextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2, colors[dx / 2]);
+ ColorTextureVertex::set(vertex++, vertices[ax], vertices[ay], u1, v2, colors[ax / 2]);
+ ColorTextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1, colors[bx / 2]);
+
+ ColorTextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2, colors[dx / 2]);
+ ColorTextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1, colors[bx / 2]);
+ ColorTextureVertex::set(vertex++, vertices[cx], vertices[cy], u2, v1, colors[cx / 2]);
+ }
+ }
+
+ if (!texture) {
+ texture = renderer.caches().textureCache.get(op.bitmap);
+ if (!texture) {
+ return;
+ }
+ }
+ const AutoTexture autoCleanup(texture);
+
+ /*
+ * TODO: handle alpha_8 textures correctly by applying paint color, but *not*
+ * shader in that case to mimic the behavior in SkiaCanvas::drawBitmapMesh.
+ */
+ const int textureFillFlags = TextureFillFlags::None;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshColoredTexturedMesh(mesh.get(), elementCount)
+ .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewOffsetRect(0, 0, op.unmappedBounds)
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onBitmapRectOp(BakedOpRenderer& renderer, const BitmapRectOp& op, const BakedOpState& state) {
+ Texture* texture = renderer.getTexture(op.bitmap);
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ Rect uv(std::max(0.0f, op.src.left / texture->width()),
+ std::max(0.0f, op.src.top / texture->height()),
+ std::min(1.0f, op.src.right / texture->width()),
+ std::min(1.0f, op.src.bottom / texture->height()));
+
+ const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
+ ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
+ const bool tryToSnap = MathUtils::areEqual(op.src.getWidth(), op.unmappedBounds.getWidth())
+ && MathUtils::areEqual(op.src.getHeight(), op.unmappedBounds.getHeight());
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUvQuad(texture->uvMapper, uv)
+ .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onColorOp(BakedOpRenderer& renderer, const ColorOp& op, const BakedOpState& state) {
+ SkPaint paint;
+ paint.setColor(op.color);
+ paint.setXfermodeMode(op.mode);
+
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshUnitQuad()
+ .setFillPaint(paint, state.alpha)
+ .setTransform(Matrix4::identity(), TransformFlags::None)
+ .setModelViewMapUnitToRect(state.computedState.clipState->rect)
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onFunctorOp(BakedOpRenderer& renderer, const FunctorOp& op, const BakedOpState& state) {
+ renderer.renderFunctor(op, state);
+}
+
+void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) {
+ VertexBuffer buffer;
+ PathTessellator::tessellateLines(op.points, op.floatCount, op.paint,
+ state.computedState.transform, buffer);
+ int displayFlags = op.paint->isAntiAlias() ? 0 : VertexBufferRenderFlags::Offset;
+ renderVertexBuffer(renderer, state, buffer, 0, 0, *(op.paint), displayFlags);
+}
+
+void BakedOpDispatcher::onOvalOp(BakedOpRenderer& renderer, const OvalOp& op, const BakedOpState& state) {
+ if (op.paint->getPathEffect() != nullptr) {
+ PathTexture* texture = renderer.caches().pathCache.getOval(
+ op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.paint);
+ const AutoTexture holder(texture);
+ if (CC_LIKELY(holder.texture)) {
+ renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top,
+ *texture, *(op.paint));
+ }
+ } else {
+ SkPath path;
+ SkRect rect = getBoundsOfFill(op);
+ path.addOval(rect);
+
+ if (state.computedState.localProjectionPathMask != nullptr) {
+ // Mask the ripple path by the local space projection mask in local space.
+ // Note that this can create CCW paths.
+ Op(path, *state.computedState.localProjectionPathMask, kIntersect_SkPathOp, &path);
+ }
+ renderConvexPath(renderer, state, path, *(op.paint));
+ }
+}
+
+void BakedOpDispatcher::onPatchOp(BakedOpRenderer& renderer, const PatchOp& op, const BakedOpState& state) {
+ // 9 patches are built for stretching - always filter
+ int textureFillFlags = TextureFillFlags::ForceFilter;
+ if (op.bitmap->colorType() == kAlpha_8_SkColorType) {
+ textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture;
+ }
+
+ // TODO: avoid redoing the below work each frame:
+ AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(op.bitmap->pixelRef());
+ const Patch* mesh = renderer.caches().patchCache.get(
+ entry, op.bitmap->width(), op.bitmap->height(),
+ op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
+
+ Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(op.bitmap);
+ if (CC_LIKELY(texture)) {
+ const AutoTexture autoCleanup(texture);
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshPatchQuads(*mesh)
+ .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
+ Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
+ .build();
+ renderer.renderGlop(state, glop);
+ }
+}
+
+void BakedOpDispatcher::onPathOp(BakedOpRenderer& renderer, const PathOp& op, const BakedOpState& state) {
+ PathTexture* texture = renderer.caches().pathCache.get(op.path, op.paint);
+ const AutoTexture holder(texture);
+ if (CC_LIKELY(holder.texture)) {
+ // Unlike other callers to renderPathTexture, no offsets are used because PathOp doesn't
+ // have any translate built in, other than what's in the SkPath itself
+ renderPathTexture(renderer, state, 0, 0, *texture, *(op.paint));
+ }
+}
+
+void BakedOpDispatcher::onPointsOp(BakedOpRenderer& renderer, const PointsOp& op, const BakedOpState& state) {
+ VertexBuffer buffer;
+ PathTessellator::tessellatePoints(op.points, op.floatCount, op.paint,
+ state.computedState.transform, buffer);
+ int displayFlags = op.paint->isAntiAlias() ? 0 : VertexBufferRenderFlags::Offset;
+ renderVertexBuffer(renderer, state, buffer, 0, 0, *(op.paint), displayFlags);
+}
+
+// See SkPaintDefaults.h
+#define SkPaintDefaults_MiterLimit SkIntToScalar(4)
+
+void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) {
+ if (op.paint->getStyle() != SkPaint::kFill_Style) {
+ // only fill + default miter is supported by drawConvexPath, since others must handle joins
+ static_assert(SkPaintDefaults_MiterLimit == 4.0f, "Miter limit has changed");
+ if (CC_UNLIKELY(op.paint->getPathEffect() != nullptr
+ || op.paint->getStrokeJoin() != SkPaint::kMiter_Join
+ || op.paint->getStrokeMiter() != SkPaintDefaults_MiterLimit)) {
+ PathTexture* texture = renderer.caches().pathCache.getRect(
+ op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.paint);
+ const AutoTexture holder(texture);
+ if (CC_LIKELY(holder.texture)) {
+ renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top,
+ *texture, *(op.paint));
+ }
+ } else {
+ SkPath path;
+ path.addRect(getBoundsOfFill(op));
+ renderConvexPath(renderer, state, path, *(op.paint));
+ }
+ } else {
+ if (op.paint->isAntiAlias() && !state.computedState.transform.isSimple()) {
+ SkPath path;
+ path.addRect(op.unmappedBounds.toSkRect());
+ renderConvexPath(renderer, state, path, *(op.paint));
+ } else {
+ // render simple unit quad, no tessellation required
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshUnitQuad()
+ .setFillPaint(*op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRect(op.unmappedBounds)
+ .build();
+ renderer.renderGlop(state, glop);
+ }
+ }
+}
+
+void BakedOpDispatcher::onRoundRectOp(BakedOpRenderer& renderer, const RoundRectOp& op, const BakedOpState& state) {
+ if (op.paint->getPathEffect() != nullptr) {
+ PathTexture* texture = renderer.caches().pathCache.getRoundRect(
+ op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(),
+ op.rx, op.ry, op.paint);
+ const AutoTexture holder(texture);
+ if (CC_LIKELY(holder.texture)) {
+ renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top,
+ *texture, *(op.paint));
+ }
+ } else {
+ const VertexBuffer* buffer = renderer.caches().tessellationCache.getRoundRect(
+ state.computedState.transform, *(op.paint),
+ op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.rx, op.ry);
+ renderVertexBuffer(renderer, state, *buffer,
+ op.unmappedBounds.left, op.unmappedBounds.top, *(op.paint), 0);
+ }
+}
+
+static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, float casterAlpha,
+ const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) {
+ SkPaint paint;
+ paint.setAntiAlias(true); // want to use AlphaVertex
+
+ // The caller has made sure casterAlpha > 0.
+ uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha;
+ if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
+ ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
+ }
+ if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) {
+ paint.setAlpha((uint8_t)(casterAlpha * ambientShadowAlpha));
+ renderVertexBuffer(renderer, state, *ambientShadowVertexBuffer, 0, 0,
+ paint, VertexBufferRenderFlags::ShadowInterp);
+ }
+
+ uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha;
+ if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
+ spotShadowAlpha = Properties::overrideSpotShadowStrength;
+ }
+ if (spotShadowVertexBuffer && spotShadowAlpha > 0) {
+ paint.setAlpha((uint8_t)(casterAlpha * spotShadowAlpha));
+ renderVertexBuffer(renderer, state, *spotShadowVertexBuffer, 0, 0,
+ paint, VertexBufferRenderFlags::ShadowInterp);
+ }
+}
+
+void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
+ TessellationCache::vertexBuffer_pair_t buffers = op.shadowTask->getResult();
+ renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
+}
+
+void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) {
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
+ .setFillPaint(*op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewOffsetRect(0, 0, op.unmappedBounds)
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
+ renderTextOp(renderer, op, state, state.computedState.getClipIfNeeded(), TextRenderType::Flush);
+}
+
+void BakedOpDispatcher::onTextOnPathOp(BakedOpRenderer& renderer, const TextOnPathOp& op, const BakedOpState& state) {
+ // Note: can't trust clipSideFlags since we record with unmappedBounds == clip.
+ // TODO: respect clipSideFlags, once we record with bounds
+ auto renderTargetClip = state.computedState.clipState;
+
+ FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(true);
+
+ Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
+
+ int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
+ SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
+ TextDrawFunctor functor(&renderer, &state, renderTargetClip,
+ 0.0f, 0.0f, false, alpha, mode, op.paint);
+
+ bool mustDirtyRenderTarget = renderer.offscreenRenderTarget();
+ const Rect localSpaceClip = state.computedState.computeLocalSpaceClip();
+ if (fontRenderer.renderTextOnPath(op.paint, &localSpaceClip, op.glyphs, op.glyphCount,
+ op.path, op.hOffset, op.vOffset,
+ mustDirtyRenderTarget ? &layerBounds : nullptr, &functor)) {
+ if (mustDirtyRenderTarget) {
+ // manually dirty render target, since TextDrawFunctor won't
+ state.computedState.transform.mapRect(layerBounds);
+ renderer.dirtyRenderTarget(layerBounds);
+ }
+ }
+}
+
+void BakedOpDispatcher::onTextureLayerOp(BakedOpRenderer& renderer, const TextureLayerOp& op, const BakedOpState& state) {
+ const bool tryToSnap = !op.layer->getForceFilter();
+ float alpha = (op.layer->getAlpha() / 255.0f) * state.alpha;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUvQuad(nullptr, Rect(0, 1, 1, 0)) // TODO: simplify with VBO
+ .setFillTextureLayer(*(op.layer), alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRectOptionalSnap(tryToSnap, Rect(op.layer->getWidth(), op.layer->getHeight()))
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
+ // Note that we don't use op->paint in this function - it's never set on a LayerOp
+ OffscreenBuffer* buffer = *op.layerHandle;
+
+ if (CC_UNLIKELY(!buffer)) {
+ // Layer was not allocated, which can occur if there were no draw ops inside. We draw the
+ // equivalent by drawing a rect with the same layer properties (alpha/xfer/filter).
+ SkPaint paint;
+ paint.setAlpha(op.alpha * 255);
+ paint.setXfermodeMode(op.mode);
+ paint.setColorFilter(op.colorFilter);
+ RectOp rectOp(op.unmappedBounds, op.localMatrix, op.localClip, &paint);
+ BakedOpDispatcher::onRectOp(renderer, rectOp, state);
+ } else {
+ float layerAlpha = op.alpha * state.alpha;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount)
+ .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
+ Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
+ .build();
+ renderer.renderGlop(state, glop);
+ }
+}
+
+void BakedOpDispatcher::onCopyToLayerOp(BakedOpRenderer& renderer, const CopyToLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL_IF(*(op.layerHandle) != nullptr, "layer already exists!");
+ *(op.layerHandle) = renderer.copyToLayer(state.computedState.clippedBounds);
+ LOG_ALWAYS_FATAL_IF(*op.layerHandle == nullptr, "layer copy failed");
+}
+
+void BakedOpDispatcher::onCopyFromLayerOp(BakedOpRenderer& renderer, const CopyFromLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL_IF(*op.layerHandle == nullptr, "no layer to draw underneath!");
+ if (!state.computedState.clippedBounds.isEmpty()) {
+ if (op.paint && op.paint->getAlpha() < 255) {
+ SkPaint layerPaint;
+ layerPaint.setAlpha(op.paint->getAlpha());
+ layerPaint.setXfermodeMode(SkXfermode::kDstIn_Mode);
+ layerPaint.setColorFilter(op.paint->getColorFilter());
+ RectOp rectOp(state.computedState.clippedBounds, Matrix4::identity(), nullptr, &layerPaint);
+ BakedOpDispatcher::onRectOp(renderer, rectOp, state);
+ }
+
+ OffscreenBuffer& layer = **(op.layerHandle);
+ auto mode = PaintUtils::getXfermodeDirect(op.paint);
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUvQuad(nullptr, layer.getTextureCoordinates())
+ .setFillLayer(layer.texture, nullptr, 1.0f, mode, Blend::ModeOrderSwap::Swap)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRect(state.computedState.clippedBounds)
+ .build();
+ renderer.renderGlop(state, glop);
+ }
+ renderer.renderState().layerPool().putOrDelete(*op.layerHandle);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/BakedOpDispatcher.h b/libs/hwui/BakedOpDispatcher.h
new file mode 100644
index 000000000000..4dfdd3ff619a
--- /dev/null
+++ b/libs/hwui/BakedOpDispatcher.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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_BAKED_OP_DISPATCHER_H
+#define ANDROID_HWUI_BAKED_OP_DISPATCHER_H
+
+#include "BakedOpState.h"
+#include "RecordedOp.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Provides all "onBitmapOp(...)" style static methods for every op type, which convert the
+ * RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer.
+ *
+ * onXXXOp methods must either render directly with the renderer, or call a static renderYYY
+ * method to render content. There should never be draw content rejection in BakedOpDispatcher -
+ * it must happen at a higher level (except in error-ish cases, like texture-too-big).
+ */
+class BakedOpDispatcher {
+public:
+ // Declares all "onMergedBitmapOps(...)" style methods for mergeable op types
+#define X(Type) \
+ static void onMerged##Type##s(BakedOpRenderer& renderer, const MergedBakedOpList& opList);
+ MAP_MERGEABLE_OPS(X)
+#undef X
+
+ // Declares all "onBitmapOp(...)" style methods for every op type
+#define X(Type) \
+ static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state);
+ MAP_RENDERABLE_OPS(X)
+#undef X
+
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_BAKED_OP_DISPATCHER_H
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
new file mode 100644
index 000000000000..3c302b3f6c9f
--- /dev/null
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2015 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 "BakedOpRenderer.h"
+
+#include "Caches.h"
+#include "Glop.h"
+#include "GlopBuilder.h"
+#include "renderstate/OffscreenBufferPool.h"
+#include "renderstate/RenderState.h"
+#include "utils/GLUtils.h"
+#include "VertexBuffer.h"
+
+#include <algorithm>
+
+namespace android {
+namespace uirenderer {
+
+OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t height) {
+ LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
+
+ OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
+ startRepaintLayer(buffer, Rect(width, height));
+ return buffer;
+}
+
+void BakedOpRenderer::recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) {
+ mRenderState.layerPool().putOrDelete(offscreenBuffer);
+}
+
+void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) {
+ LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
+
+ // subtract repaintRect from region, since it will be regenerated
+ if (repaintRect.contains(0, 0,
+ offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight)) {
+ // repaint full layer, so throw away entire region
+ offscreenBuffer->region.clear();
+ } else {
+ offscreenBuffer->region.subtractSelf(android::Rect(repaintRect.left, repaintRect.top,
+ repaintRect.right, repaintRect.bottom));
+ }
+
+ mRenderTarget.offscreenBuffer = offscreenBuffer;
+
+ // create and bind framebuffer
+ mRenderTarget.frameBufferId = mRenderState.createFramebuffer();
+ mRenderState.bindFramebuffer(mRenderTarget.frameBufferId);
+
+ // attach the texture to the FBO
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ offscreenBuffer->texture.id(), 0);
+ GL_CHECKPOINT(LOW);
+
+ LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
+ "framebuffer incomplete!");
+
+ // Change the viewport & ortho projection
+ setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight);
+
+ clearColorBuffer(repaintRect);
+}
+
+void BakedOpRenderer::endLayer() {
+ if (mRenderTarget.stencil) {
+ // if stencil was used for clipping, detach it and return it to pool
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
+ GL_CHECKPOINT(MODERATE);
+ mCaches.renderBufferCache.put(mRenderTarget.stencil);
+ mRenderTarget.stencil = nullptr;
+ }
+ mRenderTarget.lastStencilClip = nullptr;
+
+ mRenderTarget.offscreenBuffer->updateMeshFromRegion();
+ mRenderTarget.offscreenBuffer = nullptr; // It's in drawLayerOp's hands now.
+
+ // Detach the texture from the FBO
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ GL_CHECKPOINT(LOW);
+ mRenderState.deleteFramebuffer(mRenderTarget.frameBufferId);
+ mRenderTarget.frameBufferId = 0;
+}
+
+OffscreenBuffer* BakedOpRenderer::copyToLayer(const Rect& area) {
+ OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState,
+ area.getWidth(), area.getHeight());
+ if (!area.isEmpty()) {
+ mCaches.textureState().activateTexture(0);
+ mCaches.textureState().bindTexture(buffer->texture.id());
+
+ glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
+ area.left, mRenderTarget.viewportHeight - area.bottom,
+ area.getWidth(), area.getHeight());
+ }
+ return buffer;
+}
+
+void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {
+ LOG_ALWAYS_FATAL_IF(mRenderTarget.frameBufferId != 0, "primary framebufferId must be 0");
+ mRenderState.bindFramebuffer(0);
+ setViewport(width, height);
+
+ if (!mOpaque) {
+ clearColorBuffer(repaintRect);
+ }
+
+ mRenderState.debugOverdraw(true, true);
+}
+
+void BakedOpRenderer::endFrame(const Rect& repaintRect) {
+ if (CC_UNLIKELY(Properties::debugOverdraw)) {
+ ClipRect overdrawClip(repaintRect);
+ Rect viewportRect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight);
+ // overdraw visualization
+ for (int i = 1; i <= 4; i++) {
+ if (i < 4) {
+ // nth level of overdraw tests for n+1 draws per pixel
+ mRenderState.stencil().enableDebugTest(i + 1, false);
+ } else {
+ // 4th level tests for 4 or higher draws per pixel
+ mRenderState.stencil().enableDebugTest(4, true);
+ }
+
+ SkPaint paint;
+ paint.setColor(mCaches.getOverdrawColor(i));
+ Glop glop;
+ GlopBuilder(mRenderState, mCaches, &glop)
+ .setRoundRectClipState(nullptr)
+ .setMeshUnitQuad()
+ .setFillPaint(paint, 1.0f)
+ .setTransform(Matrix4::identity(), TransformFlags::None)
+ .setModelViewMapUnitToRect(viewportRect)
+ .build();
+ renderGlop(nullptr, &overdrawClip, glop);
+ }
+ mRenderState.stencil().disable();
+ }
+
+ // Note: we leave FBO 0 renderable here, for post-frame-content decoration
+}
+
+void BakedOpRenderer::setViewport(uint32_t width, uint32_t height) {
+ mRenderTarget.viewportWidth = width;
+ mRenderTarget.viewportHeight = height;
+ mRenderTarget.orthoMatrix.loadOrtho(width, height);
+
+ mRenderState.setViewport(width, height);
+ mRenderState.blend().syncEnabled();
+}
+
+void BakedOpRenderer::clearColorBuffer(const Rect& rect) {
+ if (rect.contains(Rect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight))) {
+ // Full viewport is being cleared - disable scissor
+ mRenderState.scissor().setEnabled(false);
+ } else {
+ // Requested rect is subset of viewport - scissor to it to avoid over-clearing
+ mRenderState.scissor().setEnabled(true);
+ mRenderState.scissor().set(rect.left, mRenderTarget.viewportHeight - rect.bottom,
+ rect.getWidth(), rect.getHeight());
+ }
+ glClear(GL_COLOR_BUFFER_BIT);
+ if (!mRenderTarget.frameBufferId) mHasDrawn = true;
+}
+
+Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
+ Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef());
+ if (!texture) {
+ return mCaches.textureCache.get(bitmap);
+ }
+ return texture;
+}
+
+void BakedOpRenderer::drawRects(const float* rects, int count, const SkPaint* paint) {
+ std::vector<Vertex> vertices;
+ vertices.reserve(count);
+ Vertex* vertex = vertices.data();
+
+ for (int index = 0; index < count; index += 4) {
+ float l = rects[index + 0];
+ float t = rects[index + 1];
+ float r = rects[index + 2];
+ float b = rects[index + 3];
+
+ Vertex::set(vertex++, l, t);
+ Vertex::set(vertex++, r, t);
+ Vertex::set(vertex++, l, b);
+ Vertex::set(vertex++, r, b);
+ }
+
+ LOG_ALWAYS_FATAL_IF(mRenderTarget.frameBufferId != 0, "decoration only supported for FBO 0");
+ // TODO: Currently assume full FBO damage, due to FrameInfoVisualizer::unionDirty.
+ // Should should scissor/set mHasDrawn safely.
+ mRenderState.scissor().setEnabled(false);
+ mHasDrawn = true;
+ Glop glop;
+ GlopBuilder(mRenderState, mCaches, &glop)
+ .setRoundRectClipState(nullptr)
+ .setMeshIndexedQuads(vertices.data(), count / 4)
+ .setFillPaint(*paint, 1.0f)
+ .setTransform(Matrix4::identity(), TransformFlags::None)
+ .setModelViewIdentityEmptyBounds()
+ .build();
+ mRenderState.render(glop, mRenderTarget.orthoMatrix);
+}
+
+// clears and re-fills stencil with provided rendertarget space quads,
+// and then put stencil into test mode
+void BakedOpRenderer::setupStencilQuads(std::vector<Vertex>& quadVertices,
+ int incrementThreshold) {
+ mRenderState.stencil().enableWrite(incrementThreshold);
+ mRenderState.stencil().clear();
+ Glop glop;
+ GlopBuilder(mRenderState, mCaches, &glop)
+ .setRoundRectClipState(nullptr)
+ .setMeshIndexedQuads(quadVertices.data(), quadVertices.size() / 4)
+ .setFillBlack()
+ .setTransform(Matrix4::identity(), TransformFlags::None)
+ .setModelViewIdentityEmptyBounds()
+ .build();
+ mRenderState.render(glop, mRenderTarget.orthoMatrix);
+ mRenderState.stencil().enableTest(incrementThreshold);
+}
+
+void BakedOpRenderer::setupStencilRectList(const ClipBase* clip) {
+ LOG_ALWAYS_FATAL_IF(clip->mode != ClipMode::RectangleList, "can't rectlist clip without rectlist");
+ auto&& rectList = reinterpret_cast<const ClipRectList*>(clip)->rectList;
+ int quadCount = rectList.getTransformedRectanglesCount();
+ std::vector<Vertex> rectangleVertices;
+ rectangleVertices.reserve(quadCount * 4);
+ for (int i = 0; i < quadCount; i++) {
+ const TransformedRectangle& tr(rectList.getTransformedRectangle(i));
+ const Matrix4& transform = tr.getTransform();
+ Rect bounds = tr.getBounds();
+ if (transform.rectToRect()) {
+ // If rectToRect, can simply map bounds before storing verts
+ transform.mapRect(bounds);
+ bounds.doIntersect(clip->rect);
+ if (bounds.isEmpty()) {
+ continue; // will be outside of scissor, skip
+ }
+ }
+
+ rectangleVertices.push_back(Vertex{bounds.left, bounds.top});
+ rectangleVertices.push_back(Vertex{bounds.right, bounds.top});
+ rectangleVertices.push_back(Vertex{bounds.left, bounds.bottom});
+ rectangleVertices.push_back(Vertex{bounds.right, bounds.bottom});
+
+ if (!transform.rectToRect()) {
+ // If not rectToRect, must map each point individually
+ for (auto cur = rectangleVertices.end() - 4; cur < rectangleVertices.end(); cur++) {
+ transform.mapPoint(cur->x, cur->y);
+ }
+ }
+ }
+ setupStencilQuads(rectangleVertices, rectList.getTransformedRectanglesCount());
+}
+
+void BakedOpRenderer::setupStencilRegion(const ClipBase* clip) {
+ LOG_ALWAYS_FATAL_IF(clip->mode != ClipMode::Region, "can't region clip without region");
+ auto&& region = reinterpret_cast<const ClipRegion*>(clip)->region;
+
+ std::vector<Vertex> regionVertices;
+ SkRegion::Cliperator it(region, clip->rect.toSkIRect());
+ while (!it.done()) {
+ const SkIRect& r = it.rect();
+ regionVertices.push_back(Vertex{(float)r.fLeft, (float)r.fTop});
+ regionVertices.push_back(Vertex{(float)r.fRight, (float)r.fTop});
+ regionVertices.push_back(Vertex{(float)r.fLeft, (float)r.fBottom});
+ regionVertices.push_back(Vertex{(float)r.fRight, (float)r.fBottom});
+ it.next();
+ }
+ setupStencilQuads(regionVertices, 0);
+}
+
+void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const ClipBase* clip) {
+ // Prepare scissor (done before stencil, to simplify filling stencil)
+ mRenderState.scissor().setEnabled(clip != nullptr);
+ if (clip) {
+ mRenderState.scissor().set(mRenderTarget.viewportHeight, clip->rect);
+ }
+
+ // If stencil may be used for clipping, enable it, fill it, or disable it as appropriate
+ if (CC_LIKELY(!Properties::debugOverdraw)) {
+ // only modify stencil mode and content when it's not used for overdraw visualization
+ if (CC_UNLIKELY(clip && clip->mode != ClipMode::Rectangle)) {
+ // NOTE: this pointer check is only safe for non-rect clips,
+ // since rect clips may be created on the stack
+ if (mRenderTarget.lastStencilClip != clip) {
+ // Stencil needed, but current stencil isn't up to date
+ mRenderTarget.lastStencilClip = clip;
+
+ if (mRenderTarget.frameBufferId != 0 && !mRenderTarget.stencil) {
+ OffscreenBuffer* layer = mRenderTarget.offscreenBuffer;
+ mRenderTarget.stencil = mCaches.renderBufferCache.get(
+ Stencil::getLayerStencilFormat(),
+ layer->texture.width(), layer->texture.height());
+ // stencil is bound + allocated - associate it with current FBO
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, mRenderTarget.stencil->getName());
+ }
+
+ if (clip->mode == ClipMode::RectangleList) {
+ setupStencilRectList(clip);
+ } else {
+ setupStencilRegion(clip);
+ }
+ } else {
+ // stencil is up to date - just need to ensure it's enabled (since an unclipped
+ // or scissor-only clipped op may have been drawn, disabling the stencil)
+ int incrementThreshold = 0;
+ if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) {
+ auto&& rectList = reinterpret_cast<const ClipRectList*>(clip)->rectList;
+ incrementThreshold = rectList.getTransformedRectanglesCount();
+ }
+ mRenderState.stencil().enableTest(incrementThreshold);
+ }
+ } else {
+ // either scissor or no clip, so disable stencil test
+ mRenderState.stencil().disable();
+ }
+ }
+
+ if (dirtyBounds) {
+ // dirty offscreenbuffer if present
+ dirtyRenderTarget(*dirtyBounds);
+ }
+}
+
+void BakedOpRenderer::renderGlopImpl(const Rect* dirtyBounds, const ClipBase* clip,
+ const Glop& glop) {
+ prepareRender(dirtyBounds, clip);
+ mRenderState.render(glop, mRenderTarget.orthoMatrix);
+ if (!mRenderTarget.frameBufferId) mHasDrawn = true;
+}
+
+void BakedOpRenderer::renderFunctor(const FunctorOp& op, const BakedOpState& state) {
+ prepareRender(&state.computedState.clippedBounds, state.computedState.getClipIfNeeded());
+
+ DrawGlInfo info;
+ auto&& clip = state.computedState.clipRect();
+ info.clipLeft = clip.left;
+ info.clipTop = clip.top;
+ info.clipRight = clip.right;
+ info.clipBottom = clip.bottom;
+ info.isLayer = offscreenRenderTarget();
+ info.width = mRenderTarget.viewportWidth;
+ info.height = mRenderTarget.viewportHeight;
+ state.computedState.transform.copyTo(&info.transform[0]);
+
+ mRenderState.invokeFunctor(op.functor, DrawGlInfo::kModeDraw, &info);
+}
+
+void BakedOpRenderer::dirtyRenderTarget(const Rect& uiDirty) {
+ if (mRenderTarget.offscreenBuffer) {
+ mRenderTarget.offscreenBuffer->dirty(uiDirty);
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
new file mode 100644
index 000000000000..62bc564a4a2a
--- /dev/null
+++ b/libs/hwui/BakedOpRenderer.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2015 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 "BakedOpState.h"
+#include "Matrix.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+class Caches;
+struct Glop;
+class Layer;
+class RenderState;
+struct ClipBase;
+
+/**
+ * Main rendering manager for a collection of work - one frame + any contained FBOs.
+ *
+ * Manages frame and FBO lifecycle, binding the GL framebuffer as appropriate. This is the only
+ * place where FBOs are bound, created, and destroyed.
+ *
+ * All rendering operations will be sent by the Dispatcher, a collection of static methods,
+ * which has intentionally limited access to the renderer functionality.
+ */
+class BakedOpRenderer {
+public:
+ typedef void (*GlopReceiver)(BakedOpRenderer&, const Rect*, const ClipBase*, const Glop&);
+ /**
+ * Position agnostic shadow lighting info. Used with all shadow ops in scene.
+ */
+ struct LightInfo {
+ LightInfo() : LightInfo(0, 0) {}
+ LightInfo(uint8_t ambientShadowAlpha,
+ uint8_t spotShadowAlpha)
+ : ambientShadowAlpha(ambientShadowAlpha)
+ , spotShadowAlpha(spotShadowAlpha) {}
+ uint8_t ambientShadowAlpha;
+ uint8_t spotShadowAlpha;
+ };
+
+ BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque,
+ const LightInfo& lightInfo)
+ : mGlopReceiver(DefaultGlopReceiver)
+ , mRenderState(renderState)
+ , mCaches(caches)
+ , mOpaque(opaque)
+ , mLightInfo(lightInfo) {
+ }
+
+ RenderState& renderState() { return mRenderState; }
+ Caches& caches() { return mCaches; }
+
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect);
+ void endFrame(const Rect& repaintRect);
+ WARN_UNUSED_RESULT OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height);
+ void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer);
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect);
+ void endLayer();
+ WARN_UNUSED_RESULT OffscreenBuffer* copyToLayer(const Rect& area);
+
+ Texture* getTexture(const SkBitmap* bitmap);
+ const LightInfo& getLightInfo() const { return mLightInfo; }
+
+ void renderGlop(const BakedOpState& state, const Glop& glop) {
+ renderGlop(&state.computedState.clippedBounds,
+ state.computedState.getClipIfNeeded(),
+ glop);
+ }
+ void renderFunctor(const FunctorOp& op, const BakedOpState& state);
+
+ void renderGlop(const Rect* dirtyBounds, const ClipBase* clip, const Glop& glop) {
+ mGlopReceiver(*this, dirtyBounds, clip, glop);
+ }
+ bool offscreenRenderTarget() { return mRenderTarget.offscreenBuffer != nullptr; }
+ void dirtyRenderTarget(const Rect& dirtyRect);
+ bool didDraw() const { return mHasDrawn; }
+
+ uint32_t getViewportWidth() const { return mRenderTarget.viewportWidth; }
+ uint32_t getViewportHeight() const { return mRenderTarget.viewportHeight; }
+
+ // simple draw methods, to be used for end frame decoration
+ void drawRect(float left, float top, float right, float bottom, const SkPaint* paint) {
+ float ltrb[4] = { left, top, right, bottom };
+ drawRects(ltrb, 4, paint);
+ }
+ void drawRects(const float* rects, int count, const SkPaint* paint);
+protected:
+ GlopReceiver mGlopReceiver;
+private:
+ static void DefaultGlopReceiver(BakedOpRenderer& renderer, const Rect* dirtyBounds,
+ const ClipBase* clip, const Glop& glop) {
+ renderer.renderGlopImpl(dirtyBounds, clip, glop);
+ }
+ void renderGlopImpl(const Rect* dirtyBounds, const ClipBase* clip, const Glop& glop);
+ void setViewport(uint32_t width, uint32_t height);
+ void clearColorBuffer(const Rect& clearRect);
+ void prepareRender(const Rect* dirtyBounds, const ClipBase* clip);
+ void setupStencilRectList(const ClipBase* clip);
+ void setupStencilRegion(const ClipBase* clip);
+ void setupStencilQuads(std::vector<Vertex>& quadVertices, int incrementThreshold);
+
+ RenderState& mRenderState;
+ Caches& mCaches;
+ bool mOpaque;
+ bool mHasDrawn = false;
+
+ // render target state - setup by start/end layer/frame
+ // only valid to use in between start/end pairs.
+ struct {
+ // If not drawing to a layer: fbo = 0, offscreenBuffer = null,
+ // Otherwise these refer to currently painting layer's state
+ GLuint frameBufferId = 0;
+ OffscreenBuffer* offscreenBuffer = nullptr;
+
+ // Used when drawing to a layer and using stencil clipping. otherwise null.
+ RenderBuffer* stencil = nullptr;
+
+ // value representing the ClipRectList* or ClipRegion* currently stored in
+ // the stencil of the current render target
+ const ClipBase* lastStencilClip = nullptr;
+
+ // Size of renderable region in current render target - for layers, may not match actual
+ // bounds of FBO texture. offscreenBuffer->texture has this information.
+ uint32_t viewportWidth = 0;
+ uint32_t viewportHeight = 0;
+
+ Matrix4 orthoMatrix;
+ } mRenderTarget;
+
+ const LightInfo mLightInfo;
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp
new file mode 100644
index 000000000000..9f98241c6caa
--- /dev/null
+++ b/libs/hwui/BakedOpState.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2015 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 "BakedOpState.h"
+
+#include "ClipArea.h"
+
+namespace android {
+namespace uirenderer {
+
+static int computeClipSideFlags(const Rect& clip, const Rect& bounds) {
+ int clipSideFlags = 0;
+ if (clip.left > bounds.left) clipSideFlags |= OpClipSideFlags::Left;
+ if (clip.top > bounds.top) clipSideFlags |= OpClipSideFlags::Top;
+ if (clip.right < bounds.right) clipSideFlags |= OpClipSideFlags::Right;
+ if (clip.bottom < bounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom;
+ return clipSideFlags;
+}
+
+ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
+ const RecordedOp& recordedOp, bool expandForStroke) {
+ // resolvedMatrix = parentMatrix * localMatrix
+ transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix);
+
+ // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
+ clippedBounds = recordedOp.unmappedBounds;
+ if (CC_UNLIKELY(expandForStroke)) {
+ // account for non-hairline stroke
+ clippedBounds.outset(recordedOp.paint->getStrokeWidth() * 0.5f);
+ }
+ transform.mapRect(clippedBounds);
+ if (CC_UNLIKELY(expandForStroke
+ && (!transform.isPureTranslate() || recordedOp.paint->getStrokeWidth() < 1.0f))) {
+ // account for hairline stroke when stroke may be < 1 scaled pixel
+ // Non translate || strokeWidth < 1 is conservative, but will cover all cases
+ clippedBounds.outset(0.5f);
+ }
+
+ // resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
+ clipState = snapshot.serializeIntersectedClip(allocator,
+ recordedOp.localClip, *(snapshot.transform));
+ LOG_ALWAYS_FATAL_IF(!clipState, "must clip!");
+
+ const Rect& clipRect = clipState->rect;
+ if (CC_UNLIKELY(clipRect.isEmpty() || !clippedBounds.intersects(clipRect))) {
+ // Rejected based on either empty clip, or bounds not intersecting with clip
+
+ // Note: we could rewind the clipState object in situations where the clipRect is empty,
+ // but *only* if the caching logic within ClipArea was aware of the rewind.
+ clipState = nullptr;
+ clippedBounds.setEmpty();
+ } else {
+ // Not rejected! compute true clippedBounds, clipSideFlags, and path mask
+ clipSideFlags = computeClipSideFlags(clipRect, clippedBounds);
+ clippedBounds.doIntersect(clipRect);
+
+ if (CC_UNLIKELY(snapshot.projectionPathMask)) {
+ // map projection path mask from render target space into op space,
+ // so intersection with op geometry is possible
+ Matrix4 inverseTransform;
+ inverseTransform.loadInverse(transform);
+ SkMatrix skInverseTransform;
+ inverseTransform.copyTo(skInverseTransform);
+
+ auto localMask = allocator.create<SkPath>();
+ snapshot.projectionPathMask->transform(skInverseTransform, localMask);
+ localProjectionPathMask = localMask;
+ }
+ }
+}
+
+ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
+ const Matrix4& localTransform, const ClipBase* localClip) {
+ transform.loadMultiply(*snapshot.transform, localTransform);
+ clipState = snapshot.serializeIntersectedClip(allocator, localClip, *(snapshot.transform));
+ clippedBounds = clipState->rect;
+ clipSideFlags = OpClipSideFlags::Full;
+ localProjectionPathMask = nullptr;
+}
+
+ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot)
+ : transform(*snapshot.transform)
+ , clipState(snapshot.mutateClipArea().serializeClip(allocator))
+ , clippedBounds(clipState->rect)
+ , clipSideFlags(OpClipSideFlags::Full)
+ , localProjectionPathMask(nullptr) {}
+
+ResolvedRenderState::ResolvedRenderState(const ClipRect* clipRect, const Rect& dstRect)
+ : transform(Matrix4::identity())
+ , clipState(clipRect)
+ , clippedBounds(dstRect)
+ , clipSideFlags(computeClipSideFlags(clipRect->rect, dstRect))
+ , localProjectionPathMask(nullptr) {
+ clippedBounds.doIntersect(clipRect->rect);
+}
+
+BakedOpState* BakedOpState::tryConstruct(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+ BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
+ allocator, snapshot, recordedOp, false);
+ if (bakedState->computedState.clippedBounds.isEmpty()) {
+ // bounds are empty, so op is rejected
+ allocator.rewindIfLastAlloc(bakedState);
+ return nullptr;
+ }
+ return bakedState;
+}
+
+BakedOpState* BakedOpState::tryConstructUnbounded(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+ return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp);
+}
+
+BakedOpState* BakedOpState::tryStrokeableOpConstruct(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+ bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
+ ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
+ : true;
+
+ BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
+ allocator, snapshot, recordedOp, expandForStroke);
+ if (bakedState->computedState.clippedBounds.isEmpty()) {
+ // bounds are empty, so op is rejected
+ // NOTE: this won't succeed if a clip was allocated
+ allocator.rewindIfLastAlloc(bakedState);
+ return nullptr;
+ }
+ return bakedState;
+}
+
+BakedOpState* BakedOpState::tryShadowOpConstruct(LinearAllocator& allocator,
+ Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+
+ // clip isn't empty, so construct the op
+ return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr);
+}
+
+BakedOpState* BakedOpState::directConstruct(LinearAllocator& allocator,
+ const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) {
+ return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp);
+}
+
+void BakedOpState::setupOpacity(const SkPaint* paint) {
+ computedState.opaqueOverClippedBounds = computedState.transform.isSimple()
+ && computedState.clipState->mode == ClipMode::Rectangle
+ && MathUtils::areEqual(alpha, 1.0f)
+ && !roundRectClipState
+ && PaintUtils::isOpaquePaint(paint);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
new file mode 100644
index 000000000000..e1441fca5ee2
--- /dev/null
+++ b/libs/hwui/BakedOpState.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2015 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_BAKED_OP_STATE_H
+#define ANDROID_HWUI_BAKED_OP_STATE_H
+
+#include "Matrix.h"
+#include "RecordedOp.h"
+#include "Rect.h"
+#include "Snapshot.h"
+
+namespace android {
+namespace uirenderer {
+
+namespace OpClipSideFlags {
+ enum {
+ None = 0x0,
+ Left = 0x1,
+ Top = 0x2,
+ Right = 0x4,
+ Bottom = 0x8,
+ Full = 0xF,
+ // ConservativeFull = 0x1F needed?
+ };
+}
+
+/**
+ * Holds a list of BakedOpStates of ops that can be drawn together
+ */
+struct MergedBakedOpList {
+ const BakedOpState*const* states;
+ size_t count;
+ int clipSideFlags;
+ Rect clip;
+};
+
+/**
+ * Holds the resolved clip, transform, and bounds of a recordedOp, when replayed with a snapshot
+ */
+class ResolvedRenderState {
+public:
+ ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
+ const RecordedOp& recordedOp, bool expandForStroke);
+
+ // Constructor for unbounded ops *with* transform/clip
+ ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
+ const Matrix4& localTransform, const ClipBase* localClip);
+
+ // Constructor for unbounded ops without transform/clip (namely shadows)
+ ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot);
+
+ // Constructor for primitive ops provided clip, and no transform
+ ResolvedRenderState(const ClipRect* viewportRect, const Rect& dstRect);
+
+ Rect computeLocalSpaceClip() const {
+ Matrix4 inverse;
+ inverse.loadInverse(transform);
+
+ Rect outClip(clipRect());
+ inverse.mapRect(outClip);
+ return outClip;
+ }
+
+ const Rect& clipRect() const {
+ return clipState->rect;
+ }
+
+ bool requiresClip() const {
+ return clipSideFlags != OpClipSideFlags::None
+ || CC_UNLIKELY(clipState->mode != ClipMode::Rectangle);
+ }
+
+ // returns the clip if it's needed to draw the operation, otherwise nullptr
+ const ClipBase* getClipIfNeeded() const {
+ return requiresClip() ? clipState : nullptr;
+ }
+
+ Matrix4 transform;
+ const ClipBase* clipState = nullptr;
+ Rect clippedBounds;
+ int clipSideFlags = 0;
+ const SkPath* localProjectionPathMask = nullptr;
+ bool opaqueOverClippedBounds = false;
+};
+
+/**
+ * Self-contained op wrapper, containing all resolved state required to draw the op.
+ *
+ * Stashed pointers within all point to longer lived objects, with no ownership implied.
+ */
+class BakedOpState {
+public:
+ static BakedOpState* tryConstruct(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp);
+
+ static BakedOpState* tryConstructUnbounded(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp);
+
+ enum class StrokeBehavior {
+ // stroking is forced, regardless of style on paint (such as for lines)
+ Forced,
+ // stroking is defined by style on paint
+ StyleDefined,
+ };
+
+ static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior);
+
+ static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
+ Snapshot& snapshot, const ShadowOp* shadowOpPtr);
+
+ static BakedOpState* directConstruct(LinearAllocator& allocator,
+ const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp);
+
+ // Set opaqueOverClippedBounds. If this method isn't called, the op is assumed translucent.
+ void setupOpacity(const SkPaint* paint);
+
+ // computed state:
+ ResolvedRenderState computedState;
+
+ // simple state (straight pointer/value storage):
+ const float alpha;
+ const RoundRectClipState* roundRectClipState;
+ const RecordedOp* op;
+
+private:
+ friend class LinearAllocator;
+
+ BakedOpState(LinearAllocator& allocator, Snapshot& snapshot,
+ const RecordedOp& recordedOp, bool expandForStroke)
+ : computedState(allocator, snapshot, recordedOp, expandForStroke)
+ , alpha(snapshot.alpha)
+ , roundRectClipState(snapshot.roundRectClipState)
+ , op(&recordedOp) {}
+
+ // TODO: fix this brittleness
+ BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const RecordedOp& recordedOp)
+ : computedState(allocator, snapshot, recordedOp.localMatrix, recordedOp.localClip)
+ , alpha(snapshot.alpha)
+ , roundRectClipState(snapshot.roundRectClipState)
+ , op(&recordedOp) {}
+
+ BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr)
+ : computedState(allocator, snapshot)
+ , alpha(snapshot.alpha)
+ , roundRectClipState(snapshot.roundRectClipState)
+ , op(shadowOpPtr) {}
+
+ BakedOpState(const ClipRect* clipRect, const Rect& dstRect, const RecordedOp& recordedOp)
+ : computedState(clipRect, dstRect)
+ , alpha(1.0f)
+ , roundRectClipState(nullptr)
+ , op(&recordedOp) {}
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_BAKED_OP_STATE_H
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 2763e89e19c7..eaa1c330d5dd 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include "Caches.h"
#include "GammaFontRenderer.h"
@@ -25,6 +23,7 @@
#include "ShadowTessellator.h"
#include "utils/GLUtils.h"
+#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/String8.h>
@@ -56,7 +55,6 @@ Caches::Caches(RenderState& renderState)
, mInitialized(false) {
INIT_LOGD("Creating OpenGL renderer caches");
init();
- initFont();
initConstraints();
initStaticProperties();
initExtensions();
@@ -70,8 +68,6 @@ bool Caches::init() {
mRegionMesh = nullptr;
mProgram = nullptr;
- mFunctorsCount = 0;
-
patchCache.init();
mInitialized = true;
@@ -82,10 +78,6 @@ bool Caches::init() {
return true;
}
-void Caches::initFont() {
- fontRenderer = GammaFontRenderer::createRenderer();
-}
-
void Caches::initExtensions() {
if (mExtensions.hasDebugMarker()) {
eventMark = glInsertEventMarkerEXT;
@@ -104,15 +96,9 @@ void Caches::initConstraints() {
}
void Caches::initStaticProperties() {
- gpuPixelBuffersEnabled = false;
-
// OpenGL ES 3.0+ specific features
- if (mExtensions.hasPixelBufferObjects()) {
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_ENABLE_GPU_PIXEL_BUFFERS, property, "true") > 0) {
- gpuPixelBuffersEnabled = !strcmp(property, "true");
- }
- }
+ gpuPixelBuffersEnabled = mExtensions.hasPixelBufferObjects()
+ && property_get_bool(PROPERTY_ENABLE_GPU_PIXEL_BUFFERS, true);
}
void Caches::terminate() {
@@ -207,14 +193,14 @@ void Caches::dumpMemoryUsage(String8 &log) {
dropShadowCache.getMaxSize());
log.appendFormat(" PatchCache %8d / %8d\n",
patchCache.getSize(), patchCache.getMaxSize());
- for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
- const uint32_t sizeA8 = fontRenderer->getFontRendererSize(i, GL_ALPHA);
- const uint32_t sizeRGBA = fontRenderer->getFontRendererSize(i, GL_RGBA);
- log.appendFormat(" FontRenderer %d A8 %8d / %8d\n", i, sizeA8, sizeA8);
- log.appendFormat(" FontRenderer %d RGBA %8d / %8d\n", i, sizeRGBA, sizeRGBA);
- log.appendFormat(" FontRenderer %d total %8d / %8d\n", i, sizeA8 + sizeRGBA,
- sizeA8 + sizeRGBA);
- }
+
+ const uint32_t sizeA8 = fontRenderer.getFontRendererSize(GL_ALPHA);
+ const uint32_t sizeRGBA = fontRenderer.getFontRendererSize(GL_RGBA);
+ log.appendFormat(" FontRenderer A8 %8d / %8d\n", sizeA8, sizeA8);
+ log.appendFormat(" FontRenderer RGBA %8d / %8d\n", sizeRGBA, sizeRGBA);
+ log.appendFormat(" FontRenderer total %8d / %8d\n", sizeA8 + sizeRGBA,
+ sizeA8 + sizeRGBA);
+
log.appendFormat("Other:\n");
log.appendFormat(" FboCache %8d / %8d\n",
fboCache.getSize(), fboCache.getMaxSize());
@@ -226,10 +212,8 @@ void Caches::dumpMemoryUsage(String8 &log) {
total += tessellationCache.getSize();
total += dropShadowCache.getSize();
total += patchCache.getSize();
- for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
- total += fontRenderer->getFontRendererSize(i, GL_ALPHA);
- total += fontRenderer->getFontRendererSize(i, GL_RGBA);
- }
+ total += fontRenderer.getFontRendererSize(GL_ALPHA);
+ total += fontRenderer.getFontRendererSize(GL_RGBA);
log.appendFormat("Total memory usage:\n");
log.appendFormat(" %d bytes, %.2f MB\n", total, total / 1024.0f / 1024.0f);
@@ -249,22 +233,22 @@ void Caches::flush(FlushMode mode) {
FLUSH_LOGD("Flushing caches (mode %d)", mode);
switch (mode) {
- case kFlushMode_Full:
+ case FlushMode::Full:
textureCache.clear();
patchCache.clear();
dropShadowCache.clear();
gradientCache.clear();
- fontRenderer->clear();
+ fontRenderer.clear();
fboCache.clear();
dither.clear();
// fall through
- case kFlushMode_Moderate:
- fontRenderer->flush();
+ case FlushMode::Moderate:
+ fontRenderer.flush();
textureCache.flush();
pathCache.clear();
tessellationCache.clear();
// fall through
- case kFlushMode_Layers:
+ case FlushMode::Layers:
layerCache.clear();
renderBufferCache.clear();
break;
@@ -278,38 +262,6 @@ void Caches::flush(FlushMode mode) {
}
///////////////////////////////////////////////////////////////////////////////
-// Tiling
-///////////////////////////////////////////////////////////////////////////////
-
-void Caches::startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool discard) {
- if (mExtensions.hasTiledRendering() && !Properties::debugOverdraw) {
- glStartTilingQCOM(x, y, width, height, (discard ? GL_NONE : GL_COLOR_BUFFER_BIT0_QCOM));
- }
-}
-
-void Caches::endTiling() {
- if (mExtensions.hasTiledRendering() && !Properties::debugOverdraw) {
- glEndTilingQCOM(GL_COLOR_BUFFER_BIT0_QCOM);
- }
-}
-
-bool Caches::hasRegisteredFunctors() {
- return mFunctorsCount > 0;
-}
-
-void Caches::registerFunctors(uint32_t functorCount) {
- mFunctorsCount += functorCount;
-}
-
-void Caches::unregisterFunctors(uint32_t functorCount) {
- if (functorCount > mFunctorsCount) {
- mFunctorsCount = 0;
- } else {
- mFunctorsCount -= functorCount;
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////
// Regions
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 804f609e9a0f..eac9359beab3 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -17,15 +17,11 @@
#ifndef ANDROID_HWUI_CACHES_H
#define ANDROID_HWUI_CACHES_H
-#ifndef LOG_TAG
- #define LOG_TAG "OpenGLRenderer"
-#endif
-
-
#include "AssetAtlas.h"
#include "Dither.h"
#include "Extensions.h"
#include "FboCache.h"
+#include "GammaFontRenderer.h"
#include "GradientCache.h"
#include "LayerCache.h"
#include "PatchCache.h"
@@ -47,18 +43,16 @@
#include <GLES3/gl3.h>
#include <utils/KeyedVector.h>
-#include <utils/Singleton.h>
-#include <utils/Vector.h>
#include <cutils/compiler.h>
#include <SkPath.h>
+#include <vector>
+
namespace android {
namespace uirenderer {
-class GammaFontRenderer;
-
///////////////////////////////////////////////////////////////////////////////
// Caches
///////////////////////////////////////////////////////////////////////////////
@@ -87,10 +81,10 @@ private:
static Caches* sInstance;
public:
- enum FlushMode {
- kFlushMode_Layers = 0,
- kFlushMode_Moderate,
- kFlushMode_Full
+ enum class FlushMode {
+ Layers = 0,
+ Moderate,
+ Full
};
/**
@@ -98,6 +92,8 @@ public:
*/
bool init();
+ bool isInitialized() { return mInitialized; }
+
/**
* Flush the cache.
*
@@ -107,7 +103,7 @@ public:
/**
* Destroys all resources associated with this cache. This should
- * be called after a flush(kFlushMode_Full).
+ * be called after a flush(FlushMode::Full).
*/
void terminate();
@@ -128,10 +124,6 @@ public:
*/
void deleteLayerDeferred(Layer* layer);
-
- void startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool discard);
- void endTiling();
-
/**
* Returns the mesh used to draw regions. Calling this method will
* bind a VBO of type GL_ELEMENT_ARRAY_BUFFER that contains the
@@ -145,10 +137,6 @@ public:
void dumpMemoryUsage();
void dumpMemoryUsage(String8& log);
- bool hasRegisteredFunctors();
- void registerFunctors(uint32_t functorCount);
- void unregisterFunctors(uint32_t functorCount);
-
// Misc
GLint maxTextureSize;
@@ -168,7 +156,7 @@ public:
TextDropShadowCache dropShadowCache;
FboCache fboCache;
- GammaFontRenderer* fontRenderer;
+ GammaFontRenderer fontRenderer;
TaskManager tasks;
@@ -190,8 +178,6 @@ public:
TextureState& textureState() { return *mTextureState; }
private:
-
- void initFont();
void initExtensions();
void initConstraints();
void initStaticProperties();
@@ -206,12 +192,10 @@ private:
std::unique_ptr<TextureVertex[]> mRegionMesh;
mutable Mutex mGarbageLock;
- Vector<Layer*> mLayerGarbage;
+ std::vector<Layer*> mLayerGarbage;
bool mInitialized;
- uint32_t mFunctorsCount;
-
// TODO: move below to RenderState
PixelBufferState* mPixelBufferState = nullptr;
TextureState* mTextureState = nullptr;
diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp
index e22b0d3084ab..e2149d1e4a69 100644
--- a/libs/hwui/CanvasState.cpp
+++ b/libs/hwui/CanvasState.cpp
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-#include <SkCanvas.h>
-
#include "CanvasState.h"
+#include "hwui/Canvas.h"
#include "utils/MathUtils.h"
namespace android {
@@ -28,38 +27,86 @@ CanvasState::CanvasState(CanvasStateClient& renderer)
, mWidth(-1)
, mHeight(-1)
, mSaveCount(1)
- , mFirstSnapshot(new Snapshot)
, mCanvas(renderer)
- , mSnapshot(mFirstSnapshot) {
-
+ , mSnapshot(&mFirstSnapshot) {
}
CanvasState::~CanvasState() {
+ // First call freeSnapshot on all but mFirstSnapshot
+ // to invoke all the dtors
+ freeAllSnapshots();
+
+ // Now actually release the memory
+ while (mSnapshotPool) {
+ void* temp = mSnapshotPool;
+ mSnapshotPool = mSnapshotPool->previous;
+ free(temp);
+ }
+}
+
+void CanvasState::initializeRecordingSaveStack(int viewportWidth, int viewportHeight) {
+ if (mWidth != viewportWidth || mHeight != viewportHeight) {
+ mWidth = viewportWidth;
+ mHeight = viewportHeight;
+ mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight);
+ mCanvas.onViewportInitialized();
+ }
+ freeAllSnapshots();
+ mSnapshot = allocSnapshot(&mFirstSnapshot, SaveFlags::MatrixClip);
+ mSnapshot->setRelativeLightCenter(Vector3());
+ mSaveCount = 1;
}
-void CanvasState::initializeSaveStack(float clipLeft, float clipTop,
+void CanvasState::initializeSaveStack(
+ int viewportWidth, int viewportHeight,
+ float clipLeft, float clipTop,
float clipRight, float clipBottom, const Vector3& lightCenter) {
- mSnapshot = new Snapshot(mFirstSnapshot,
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ if (mWidth != viewportWidth || mHeight != viewportHeight) {
+ mWidth = viewportWidth;
+ mHeight = viewportHeight;
+ mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight);
+ mCanvas.onViewportInitialized();
+ }
+
+ freeAllSnapshots();
+ mSnapshot = allocSnapshot(&mFirstSnapshot, SaveFlags::MatrixClip);
mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom);
mSnapshot->fbo = mCanvas.getTargetFbo();
mSnapshot->setRelativeLightCenter(lightCenter);
mSaveCount = 1;
}
-void CanvasState::setViewport(int width, int height) {
- mWidth = width;
- mHeight = height;
- mFirstSnapshot->initializeViewport(width, height);
- mCanvas.onViewportInitialized();
-
- // create a temporary 1st snapshot, so old snapshots are released,
- // and viewport can be queried safely.
- // TODO: remove, combine viewport + save stack initialization
- mSnapshot = new Snapshot(mFirstSnapshot,
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- mSaveCount = 1;
+Snapshot* CanvasState::allocSnapshot(Snapshot* previous, int savecount) {
+ void* memory;
+ if (mSnapshotPool) {
+ memory = mSnapshotPool;
+ mSnapshotPool = mSnapshotPool->previous;
+ mSnapshotPoolCount--;
+ } else {
+ memory = malloc(sizeof(Snapshot));
+ }
+ return new (memory) Snapshot(previous, savecount);
+}
+
+void CanvasState::freeSnapshot(Snapshot* snapshot) {
+ snapshot->~Snapshot();
+ // Arbitrary number, just don't let this grown unbounded
+ if (mSnapshotPoolCount > 10) {
+ free((void*) snapshot);
+ } else {
+ snapshot->previous = mSnapshotPool;
+ mSnapshotPool = snapshot;
+ mSnapshotPoolCount++;
+ }
+}
+
+void CanvasState::freeAllSnapshots() {
+ while (mSnapshot != &mFirstSnapshot) {
+ Snapshot* temp = mSnapshot;
+ mSnapshot = mSnapshot->previous;
+ freeSnapshot(temp);
+ }
}
///////////////////////////////////////////////////////////////////////////////
@@ -73,7 +120,7 @@ void CanvasState::setViewport(int width, int height) {
* stack, and ensures restoreToCount() doesn't call back into subclass overrides.
*/
int CanvasState::saveSnapshot(int flags) {
- mSnapshot = new Snapshot(mSnapshot, flags);
+ mSnapshot = allocSnapshot(mSnapshot, flags);
return mSaveCount++;
}
@@ -85,14 +132,16 @@ int CanvasState::save(int flags) {
* Guaranteed to restore without side-effects.
*/
void CanvasState::restoreSnapshot() {
- sp<Snapshot> toRemove = mSnapshot;
- sp<Snapshot> toRestore = mSnapshot->previous;
+ Snapshot* toRemove = mSnapshot;
+ Snapshot* toRestore = mSnapshot->previous;
mSaveCount--;
mSnapshot = toRestore;
// subclass handles restore implementation
mCanvas.onSnapshotRestored(*toRemove, *toRestore);
+
+ freeSnapshot(toRemove);
}
void CanvasState::restore() {
@@ -138,7 +187,7 @@ void CanvasState::setMatrix(const SkMatrix& matrix) {
}
void CanvasState::setMatrix(const Matrix4& matrix) {
- mSnapshot->transform->load(matrix);
+ *(mSnapshot->transform) = matrix;
}
void CanvasState::concatMatrix(const SkMatrix& matrix) {
@@ -155,17 +204,20 @@ void CanvasState::concatMatrix(const Matrix4& matrix) {
///////////////////////////////////////////////////////////////////////////////
bool CanvasState::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
- mDirtyClip |= mSnapshot->clip(left, top, right, bottom, op);
+ mSnapshot->clip(Rect(left, top, right, bottom), op);
+ mDirtyClip = true;
return !mSnapshot->clipIsEmpty();
}
bool CanvasState::clipPath(const SkPath* path, SkRegion::Op op) {
- mDirtyClip |= mSnapshot->clipPath(*path, op);
+ mSnapshot->clipPath(*path, op);
+ mDirtyClip = true;
return !mSnapshot->clipIsEmpty();
}
bool CanvasState::clipRegion(const SkRegion* region, SkRegion::Op op) {
- mDirtyClip |= mSnapshot->clipRegionTransformed(*region, op);
+ mSnapshot->clipRegionTransformed(*region, op);
+ mDirtyClip = true;
return !mSnapshot->clipIsEmpty();
}
@@ -219,7 +271,7 @@ bool CanvasState::calculateQuickRejectForScissor(float left, float top,
currentTransform()->mapRect(r);
r.snapGeometryToPixelBoundaries(snapOut);
- Rect clipRect(currentClipRect());
+ Rect clipRect(currentRenderTargetClip());
clipRect.snapToPixelBoundaries();
if (!clipRect.intersects(r)) return true;
@@ -247,7 +299,7 @@ bool CanvasState::quickRejectConservative(float left, float top,
currentTransform()->mapRect(r);
r.roundOut(); // rounded out to be conservative
- Rect clipRect(currentClipRect());
+ Rect clipRect(currentRenderTargetClip());
clipRect.snapToPixelBoundaries();
if (!clipRect.intersects(r)) return true;
diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h
index b35db28eaf82..b9e87ae5595d 100644
--- a/libs/hwui/CanvasState.h
+++ b/libs/hwui/CanvasState.h
@@ -17,12 +17,12 @@
#ifndef ANDROID_HWUI_CANVAS_STATE_H
#define ANDROID_HWUI_CANVAS_STATE_H
+#include "Snapshot.h"
+
#include <SkMatrix.h>
#include <SkPath.h>
#include <SkRegion.h>
-#include "Snapshot.h"
-
namespace android {
namespace uirenderer {
@@ -71,7 +71,7 @@ public:
* (getClip/Matrix), but so that quickRejection can also be used.
*/
-class ANDROID_API CanvasState {
+class CanvasState {
public:
CanvasState(CanvasStateClient& renderer);
~CanvasState();
@@ -80,10 +80,15 @@ public:
* Initializes the first snapshot, computing the projection matrix,
* and stores the dimensions of the render target.
*/
- void initializeSaveStack(float clipLeft, float clipTop, float clipRight, float clipBottom,
- const Vector3& lightCenter);
+ void initializeRecordingSaveStack(int viewportWidth, int viewportHeight);
- void setViewport(int width, int height);
+ /**
+ * Initializes the first snapshot, computing the projection matrix,
+ * and stores the dimensions of the render target.
+ */
+ void initializeSaveStack(int viewportWidth, int viewportHeight,
+ float clipLeft, float clipTop, float clipRight, float clipBottom,
+ const Vector3& lightCenter);
bool hasRectToRectTransform() const {
return CC_LIKELY(currentTransform()->rectToRect());
@@ -148,7 +153,7 @@ public:
void setInvisible(bool value) { mSnapshot->invisible = value; }
inline const mat4* currentTransform() const { return currentSnapshot()->transform; }
- inline const Rect& currentClipRect() const { return currentSnapshot()->getClipRect(); }
+ inline const Rect& currentRenderTargetClip() const { return currentSnapshot()->getRenderTargetClip(); }
inline Region* currentRegion() const { return currentSnapshot()->region; }
inline int currentFlags() const { return currentSnapshot()->flags; }
const Vector3& currentLightCenter() const { return currentSnapshot()->getRelativeLightCenter(); }
@@ -159,17 +164,17 @@ public:
int getHeight() const { return mHeight; }
bool clipIsSimple() const { return currentSnapshot()->clipIsSimple(); }
- inline const Snapshot* currentSnapshot() const {
- return mSnapshot != nullptr ? mSnapshot.get() : mFirstSnapshot.get();
- }
- inline Snapshot* writableSnapshot() { return mSnapshot.get(); }
- inline const Snapshot* firstSnapshot() const { return mFirstSnapshot.get(); }
+ inline const Snapshot* currentSnapshot() const { return mSnapshot; }
+ inline Snapshot* writableSnapshot() { return mSnapshot; }
+ inline const Snapshot* firstSnapshot() const { return &mFirstSnapshot; }
private:
- /// No default constructor - must supply a CanvasStateClient (mCanvas).
- CanvasState();
+ Snapshot* allocSnapshot(Snapshot* previous, int savecount);
+ void freeSnapshot(Snapshot* snapshot);
+ void freeAllSnapshots();
/// indicates that the clip has been changed since the last time it was consumed
+ // TODO: delete when switching to HWUI_NEW_OPS
bool mDirtyClip;
/// Dimensions of the drawing surface
@@ -179,13 +184,18 @@ private:
int mSaveCount;
/// Base state
- sp<Snapshot> mFirstSnapshot;
+ Snapshot mFirstSnapshot;
/// Host providing callbacks
CanvasStateClient& mCanvas;
/// Current state
- sp<Snapshot> mSnapshot;
+ Snapshot* mSnapshot;
+
+ // Pool of allocated snapshots to re-use
+ // NOTE: The dtors have already been invoked!
+ Snapshot* mSnapshotPool = nullptr;
+ int mSnapshotPoolCount = 0;
}; // class CanvasState
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp
index b1a68447fc21..fe6823925083 100644
--- a/libs/hwui/ClipArea.cpp
+++ b/libs/hwui/ClipArea.cpp
@@ -15,28 +15,19 @@
*/
#include "ClipArea.h"
+#include "utils/LinearAllocator.h"
+
#include <SkPath.h>
#include <limits>
-
-#include "Rect.h"
+#include <type_traits>
namespace android {
namespace uirenderer {
-static bool intersect(Rect& r, const Rect& r2) {
- bool hasIntersection = r.intersect(r2);
- if (!hasIntersection) {
- r.setEmpty();
- }
- return hasIntersection;
-}
-
static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) {
- Vertex v;
- v.x = x;
- v.y = y;
+ Vertex v = {x, y};
transform.mapPoint(v.x, v.y);
- transformedBounds.expandToCoverVertex(v.x, v.y);
+ transformedBounds.expandToCover(v.x, v.y);
}
Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) {
@@ -50,6 +41,10 @@ Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) {
return transformedBounds;
}
+void ClipBase::dump() const {
+ ALOGD("mode %d" RECT_STRING, mode, RECT_ARGS(rect));
+}
+
/*
* TransformedRectangle
*/
@@ -69,9 +64,8 @@ bool TransformedRectangle::canSimplyIntersectWith(
return mTransform == other.mTransform;
}
-bool TransformedRectangle::intersectWith(const TransformedRectangle& other) {
- Rect translatedBounds(other.mBounds);
- return intersect(mBounds, translatedBounds);
+void TransformedRectangle::intersectWith(const TransformedRectangle& other) {
+ mBounds.doIntersect(other.mBounds);
}
bool TransformedRectangle::isEmpty() const {
@@ -148,7 +142,7 @@ Rect RectangleList::calculateBounds() const {
if (index == 0) {
bounds = tr.transformedBounds();
} else {
- bounds.intersect(tr.transformedBounds());
+ bounds.doIntersect(tr.transformedBounds());
}
}
return bounds;
@@ -182,12 +176,18 @@ SkRegion RectangleList::convertToRegion(const SkRegion& clip) const {
return rectangleListAsRegion;
}
+void RectangleList::transform(const Matrix4& transform) {
+ for (int index = 0; index < mTransformedRectanglesCount; index++) {
+ mTransformedRectangles[index].transform(transform);
+ }
+}
+
/*
* ClipArea
*/
ClipArea::ClipArea()
- : mMode(kModeRectangle) {
+ : mMode(ClipMode::Rectangle) {
}
/*
@@ -195,58 +195,67 @@ ClipArea::ClipArea()
*/
void ClipArea::setViewportDimensions(int width, int height) {
+ mPostViewportClipObserved = false;
mViewportBounds.set(0, 0, width, height);
mClipRect = mViewportBounds;
}
void ClipArea::setEmpty() {
- mMode = kModeRectangle;
+ onClipUpdated();
+ mMode = ClipMode::Rectangle;
mClipRect.setEmpty();
mClipRegion.setEmpty();
mRectangleList.setEmpty();
}
void ClipArea::setClip(float left, float top, float right, float bottom) {
- mMode = kModeRectangle;
+ onClipUpdated();
+ mMode = ClipMode::Rectangle;
mClipRect.set(left, top, right, bottom);
mClipRegion.setEmpty();
}
-bool ClipArea::clipRectWithTransform(float left, float top, float right,
- float bottom, const mat4* transform, SkRegion::Op op) {
- Rect r(left, top, right, bottom);
- return clipRectWithTransform(r, transform, op);
-}
-
-bool ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform,
+void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform,
SkRegion::Op op) {
+ if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true;
+ if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
+ onClipUpdated();
switch (mMode) {
- case kModeRectangle:
- return rectangleModeClipRectWithTransform(r, transform, op);
- case kModeRectangleList:
- return rectangleListModeClipRectWithTransform(r, transform, op);
- case kModeRegion:
- return regionModeClipRectWithTransform(r, transform, op);
+ case ClipMode::Rectangle:
+ rectangleModeClipRectWithTransform(r, transform, op);
+ break;
+ case ClipMode::RectangleList:
+ rectangleListModeClipRectWithTransform(r, transform, op);
+ break;
+ case ClipMode::Region:
+ regionModeClipRectWithTransform(r, transform, op);
+ break;
}
- return false;
}
-bool ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
+void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
+ if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true;
+ if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
+ onClipUpdated();
enterRegionMode();
mClipRegion.op(region, op);
onClipRegionUpdated();
- return true;
}
-bool ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform,
+void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform,
SkRegion::Op op) {
+ if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true;
+ if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
+ onClipUpdated();
SkMatrix skTransform;
transform->copyTo(skTransform);
SkPath transformed;
path.transform(skTransform, &transformed);
SkRegion region;
regionFromPath(transformed, region);
- return clipRegion(region, op);
+ enterRegionMode();
+ mClipRegion.op(region, op);
+ onClipRegionUpdated();
}
/*
@@ -257,41 +266,31 @@ void ClipArea::enterRectangleMode() {
// Entering rectangle mode discards any
// existing clipping information from the other modes.
// The only way this occurs is by a clip setting operation.
- mMode = kModeRectangle;
+ mMode = ClipMode::Rectangle;
}
-bool ClipArea::rectangleModeClipRectWithTransform(const Rect& r,
+void ClipArea::rectangleModeClipRectWithTransform(const Rect& r,
const mat4* transform, SkRegion::Op op) {
if (op == SkRegion::kReplace_Op && transform->rectToRect()) {
mClipRect = r;
transform->mapRect(mClipRect);
- return true;
+ return;
} else if (op != SkRegion::kIntersect_Op) {
enterRegionMode();
- return regionModeClipRectWithTransform(r, transform, op);
+ regionModeClipRectWithTransform(r, transform, op);
+ return;
}
if (transform->rectToRect()) {
Rect transformed(r);
transform->mapRect(transformed);
- bool hasIntersection = mClipRect.intersect(transformed);
- if (!hasIntersection) {
- mClipRect.setEmpty();
- }
- return true;
+ mClipRect.doIntersect(transformed);
+ return;
}
enterRectangleListMode();
- return rectangleListModeClipRectWithTransform(r, transform, op);
-}
-
-bool ClipArea::rectangleModeClipRectWithTransform(float left, float top,
- float right, float bottom, const mat4* transform, SkRegion::Op op) {
- Rect r(left, top, right, bottom);
- bool result = rectangleModeClipRectWithTransform(r, transform, op);
- mClipRect = mRectangleList.calculateBounds();
- return result;
+ rectangleListModeClipRectWithTransform(r, transform, op);
}
/*
@@ -302,25 +301,18 @@ void ClipArea::enterRectangleListMode() {
// Is is only legal to enter rectangle list mode from
// rectangle mode, since rectangle list mode cannot represent
// all clip areas that can be represented by a region.
- ALOG_ASSERT(mMode == kModeRectangle);
- mMode = kModeRectangleList;
+ ALOG_ASSERT(mMode == ClipMode::Rectangle);
+ mMode = ClipMode::RectangleList;
mRectangleList.set(mClipRect, Matrix4::identity());
}
-bool ClipArea::rectangleListModeClipRectWithTransform(const Rect& r,
+void ClipArea::rectangleListModeClipRectWithTransform(const Rect& r,
const mat4* transform, SkRegion::Op op) {
if (op != SkRegion::kIntersect_Op
|| !mRectangleList.intersectWith(r, *transform)) {
enterRegionMode();
- return regionModeClipRectWithTransform(r, transform, op);
+ regionModeClipRectWithTransform(r, transform, op);
}
- return true;
-}
-
-bool ClipArea::rectangleListModeClipRectWithTransform(float left, float top,
- float right, float bottom, const mat4* transform, SkRegion::Op op) {
- Rect r(left, top, right, bottom);
- return rectangleListModeClipRectWithTransform(r, transform, op);
}
/*
@@ -328,12 +320,11 @@ bool ClipArea::rectangleListModeClipRectWithTransform(float left, float top,
*/
void ClipArea::enterRegionMode() {
- Mode oldMode = mMode;
- mMode = kModeRegion;
- if (oldMode != kModeRegion) {
- if (oldMode == kModeRectangle) {
- mClipRegion.setRect(mClipRect.left, mClipRect.top,
- mClipRect.right, mClipRect.bottom);
+ ClipMode oldMode = mMode;
+ mMode = ClipMode::Region;
+ if (oldMode != ClipMode::Region) {
+ if (oldMode == ClipMode::Rectangle) {
+ mClipRegion.setRect(mClipRect.toSkIRect());
} else {
mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
onClipRegionUpdated();
@@ -341,20 +332,13 @@ void ClipArea::enterRegionMode() {
}
}
-bool ClipArea::regionModeClipRectWithTransform(const Rect& r,
+void ClipArea::regionModeClipRectWithTransform(const Rect& r,
const mat4* transform, SkRegion::Op op) {
SkPath transformedRect = pathFromTransformedRectangle(r, *transform);
SkRegion transformedRectRegion;
regionFromPath(transformedRect, transformedRectRegion);
mClipRegion.op(transformedRectRegion, op);
onClipRegionUpdated();
- return true;
-}
-
-bool ClipArea::regionModeClipRectWithTransform(float left, float top,
- float right, float bottom, const mat4* transform, SkRegion::Op op) {
- return regionModeClipRectWithTransform(Rect(left, top, right, bottom),
- transform, op);
}
void ClipArea::onClipRegionUpdated() {
@@ -370,5 +354,184 @@ void ClipArea::onClipRegionUpdated() {
}
}
+/**
+ * Clip serialization
+ */
+
+const ClipBase* ClipArea::serializeClip(LinearAllocator& allocator) {
+ if (!mPostViewportClipObserved) {
+ // Only initial clip-to-viewport observed, so no serialization of clip necessary
+ return nullptr;
+ }
+
+ static_assert(std::is_trivially_destructible<Rect>::value,
+ "expect Rect to be trivially destructible");
+ static_assert(std::is_trivially_destructible<RectangleList>::value,
+ "expect RectangleList to be trivially destructible");
+
+ if (mLastSerialization == nullptr) {
+ ClipBase* serialization = nullptr;
+ switch (mMode) {
+ case ClipMode::Rectangle:
+ serialization = allocator.create<ClipRect>(mClipRect);
+ break;
+ case ClipMode::RectangleList:
+ serialization = allocator.create<ClipRectList>(mRectangleList);
+ serialization->rect = mRectangleList.calculateBounds();
+ break;
+ case ClipMode::Region:
+ serialization = allocator.create<ClipRegion>(mClipRegion);
+ serialization->rect.set(mClipRegion.getBounds());
+ break;
+ }
+ serialization->intersectWithRoot = mReplaceOpObserved;
+ // TODO: this is only done for draw time, should eventually avoid for record time
+ serialization->rect.snapToPixelBoundaries();
+ mLastSerialization = serialization;
+ }
+ return mLastSerialization;
+}
+
+inline static const RectangleList& getRectList(const ClipBase* scb) {
+ return reinterpret_cast<const ClipRectList*>(scb)->rectList;
+}
+
+inline static const SkRegion& getRegion(const ClipBase* scb) {
+ return reinterpret_cast<const ClipRegion*>(scb)->region;
+}
+
+// Conservative check for too many rectangles to fit in rectangle list.
+// For simplicity, doesn't account for rect merging
+static bool cannotFitInRectangleList(const ClipArea& clipArea, const ClipBase* scb) {
+ int currentRectCount = clipArea.isRectangleList()
+ ? clipArea.getRectangleList().getTransformedRectanglesCount()
+ : 1;
+ int recordedRectCount = (scb->mode == ClipMode::RectangleList)
+ ? getRectList(scb).getTransformedRectanglesCount()
+ : 1;
+ return currentRectCount + recordedRectCount > RectangleList::kMaxTransformedRectangles;
+}
+
+static const ClipRect sEmptyClipRect(Rect(0, 0));
+
+const ClipBase* ClipArea::serializeIntersectedClip(LinearAllocator& allocator,
+ const ClipBase* recordedClip, const Matrix4& recordedClipTransform) {
+
+ // if no recordedClip passed, just serialize current state
+ if (!recordedClip) return serializeClip(allocator);
+
+ // if either is empty, clip is empty
+ if (CC_UNLIKELY(recordedClip->rect.isEmpty())|| mClipRect.isEmpty()) return &sEmptyClipRect;
+
+ if (!mLastResolutionResult
+ || recordedClip != mLastResolutionClip
+ || recordedClipTransform != mLastResolutionTransform) {
+ mLastResolutionClip = recordedClip;
+ mLastResolutionTransform = recordedClipTransform;
+
+ if (CC_LIKELY(mMode == ClipMode::Rectangle
+ && recordedClip->mode == ClipMode::Rectangle
+ && recordedClipTransform.rectToRect())) {
+ // common case - result is a single rectangle
+ auto rectClip = allocator.create<ClipRect>(recordedClip->rect);
+ recordedClipTransform.mapRect(rectClip->rect);
+ rectClip->rect.doIntersect(mClipRect);
+ rectClip->rect.snapToPixelBoundaries();
+ mLastResolutionResult = rectClip;
+ } else if (CC_UNLIKELY(mMode == ClipMode::Region
+ || recordedClip->mode == ClipMode::Region
+ || cannotFitInRectangleList(*this, recordedClip))) {
+ // region case
+ SkRegion other;
+ switch (recordedClip->mode) {
+ case ClipMode::Rectangle:
+ if (CC_LIKELY(recordedClipTransform.rectToRect())) {
+ // simple transform, skip creating SkPath
+ Rect resultClip(recordedClip->rect);
+ recordedClipTransform.mapRect(resultClip);
+ other.setRect(resultClip.toSkIRect());
+ } else {
+ SkPath transformedRect = pathFromTransformedRectangle(recordedClip->rect,
+ recordedClipTransform);
+ other.setPath(transformedRect, createViewportRegion());
+ }
+ break;
+ case ClipMode::RectangleList: {
+ RectangleList transformedList(getRectList(recordedClip));
+ transformedList.transform(recordedClipTransform);
+ other = transformedList.convertToRegion(createViewportRegion());
+ break;
+ }
+ case ClipMode::Region:
+ other = getRegion(recordedClip);
+
+ // TODO: handle non-translate transforms properly!
+ other.translate(recordedClipTransform.getTranslateX(),
+ recordedClipTransform.getTranslateY());
+ }
+
+ ClipRegion* regionClip = allocator.create<ClipRegion>();
+ switch (mMode) {
+ case ClipMode::Rectangle:
+ regionClip->region.op(mClipRect.toSkIRect(), other, SkRegion::kIntersect_Op);
+ break;
+ case ClipMode::RectangleList:
+ regionClip->region.op(mRectangleList.convertToRegion(createViewportRegion()),
+ other, SkRegion::kIntersect_Op);
+ break;
+ case ClipMode::Region:
+ regionClip->region.op(mClipRegion, other, SkRegion::kIntersect_Op);
+ break;
+ }
+ // Don't need to snap, since region's in int bounds
+ regionClip->rect.set(regionClip->region.getBounds());
+ mLastResolutionResult = regionClip;
+ } else {
+ auto rectListClip = allocator.create<ClipRectList>(mRectangleList);
+ auto&& rectList = rectListClip->rectList;
+ if (mMode == ClipMode::Rectangle) {
+ rectList.set(mClipRect, Matrix4::identity());
+ }
+
+ if (recordedClip->mode == ClipMode::Rectangle) {
+ rectList.intersectWith(recordedClip->rect, recordedClipTransform);
+ } else {
+ const RectangleList& other = getRectList(recordedClip);
+ for (int i = 0; i < other.getTransformedRectanglesCount(); i++) {
+ auto&& tr = other.getTransformedRectangle(i);
+ Matrix4 totalTransform(recordedClipTransform);
+ totalTransform.multiply(tr.getTransform());
+ rectList.intersectWith(tr.getBounds(), totalTransform);
+ }
+ }
+ rectListClip->rect = rectList.calculateBounds();
+ rectListClip->rect.snapToPixelBoundaries();
+ mLastResolutionResult = rectListClip;
+ }
+ }
+ return mLastResolutionResult;
+}
+
+void ClipArea::applyClip(const ClipBase* clip, const Matrix4& transform) {
+ if (!clip) return; // nothing to do
+
+ if (CC_LIKELY(clip->mode == ClipMode::Rectangle)) {
+ clipRectWithTransform(clip->rect, &transform, SkRegion::kIntersect_Op);
+ } else if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) {
+ auto&& rectList = getRectList(clip);
+ for (int i = 0; i < rectList.getTransformedRectanglesCount(); i++) {
+ auto&& tr = rectList.getTransformedRectangle(i);
+ Matrix4 totalTransform(transform);
+ totalTransform.multiply(tr.getTransform());
+ clipRectWithTransform(tr.getBounds(), &totalTransform, SkRegion::kIntersect_Op);
+ }
+ } else {
+ SkRegion region(getRegion(clip));
+ // TODO: handle non-translate transforms properly!
+ region.translate(transform.getTranslateX(), transform.getTranslateY());
+ clipRegion(region, SkRegion::kIntersect_Op);
+ }
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h
index 51ef27b4e9cc..6eb2eef5a5f9 100644
--- a/libs/hwui/ClipArea.h
+++ b/libs/hwui/ClipArea.h
@@ -16,15 +16,17 @@
#ifndef CLIPAREA_H
#define CLIPAREA_H
-#include <SkRegion.h>
-
#include "Matrix.h"
#include "Rect.h"
#include "utils/Pair.h"
+#include <SkRegion.h>
+
namespace android {
namespace uirenderer {
+class LinearAllocator;
+
Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform);
class TransformedRectangle {
@@ -33,7 +35,7 @@ public:
TransformedRectangle(const Rect& bounds, const Matrix4& transform);
bool canSimplyIntersectWith(const TransformedRectangle& other) const;
- bool intersectWith(const TransformedRectangle& other);
+ void intersectWith(const TransformedRectangle& other);
bool isEmpty() const;
@@ -50,6 +52,12 @@ public:
return mTransform;
}
+ void transform(const Matrix4& transform) {
+ Matrix4 t;
+ t.loadMultiply(transform, mTransform);
+ mTransform = t;
+ }
+
private:
Rect mBounds;
Matrix4 mTransform;
@@ -66,19 +74,64 @@ public:
void setEmpty();
void set(const Rect& bounds, const Matrix4& transform);
bool intersectWith(const Rect& bounds, const Matrix4& transform);
+ void transform(const Matrix4& transform);
SkRegion convertToRegion(const SkRegion& clip) const;
Rect calculateBounds() const;
-private:
enum {
kMaxTransformedRectangles = 5
};
+private:
int mTransformedRectanglesCount;
TransformedRectangle mTransformedRectangles[kMaxTransformedRectangles];
};
+enum class ClipMode {
+ Rectangle,
+ RectangleList,
+
+ // region and path - intersected. if either is empty, don't use
+ Region
+};
+
+struct ClipBase {
+ ClipBase(ClipMode mode)
+ : mode(mode) {}
+ ClipBase(const Rect& rect)
+ : mode(ClipMode::Rectangle)
+ , rect(rect) {}
+ const ClipMode mode;
+ bool intersectWithRoot = false;
+ // Bounds of the clipping area, used to define the scissor, and define which
+ // portion of the stencil is updated/used
+ Rect rect;
+
+ void dump() const;
+};
+
+struct ClipRect : ClipBase {
+ ClipRect(const Rect& rect)
+ : ClipBase(rect) {}
+};
+
+struct ClipRectList : ClipBase {
+ ClipRectList(const RectangleList& rectList)
+ : ClipBase(ClipMode::RectangleList)
+ , rectList(rectList) {}
+ RectangleList rectList;
+};
+
+struct ClipRegion : ClipBase {
+ ClipRegion(const SkRegion& region)
+ : ClipBase(ClipMode::Region)
+ , region(region) {}
+ ClipRegion()
+ : ClipBase(ClipMode::Region) {}
+ SkRegion region;
+};
+
class ClipArea {
public:
ClipArea();
@@ -91,12 +144,10 @@ public:
void setEmpty();
void setClip(float left, float top, float right, float bottom);
- bool clipRectWithTransform(float left, float top, float right, float bottom,
- const mat4* transform, SkRegion::Op op = SkRegion::kIntersect_Op);
- bool clipRectWithTransform(const Rect& r, const mat4* transform,
- SkRegion::Op op = SkRegion::kIntersect_Op);
- bool clipRegion(const SkRegion& region, SkRegion::Op op = SkRegion::kIntersect_Op);
- bool clipPathWithTransform(const SkPath& path, const mat4* transform,
+ void clipRectWithTransform(const Rect& r, const mat4* transform,
+ SkRegion::Op op);
+ void clipRegion(const SkRegion& region, SkRegion::Op op);
+ void clipPathWithTransform(const SkPath& path, const mat4* transform,
SkRegion::Op op);
const Rect& getClipRect() const {
@@ -112,41 +163,45 @@ public:
}
bool isRegion() const {
- return kModeRegion == mMode;
+ return ClipMode::Region == mMode;
}
bool isSimple() const {
- return mMode == kModeRectangle;
+ return mMode == ClipMode::Rectangle;
}
bool isRectangleList() const {
- return mMode == kModeRectangleList;
+ return mMode == ClipMode::RectangleList;
}
+ WARN_UNUSED_RESULT const ClipBase* serializeClip(LinearAllocator& allocator);
+ WARN_UNUSED_RESULT const ClipBase* serializeIntersectedClip(LinearAllocator& allocator,
+ const ClipBase* recordedClip, const Matrix4& recordedClipTransform);
+ void applyClip(const ClipBase* recordedClip, const Matrix4& recordedClipTransform);
+
private:
void enterRectangleMode();
- bool rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op);
- bool rectangleModeClipRectWithTransform(float left, float top, float right,
- float bottom, const mat4* transform, SkRegion::Op op);
+ void rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op);
void enterRectangleListMode();
- bool rectangleListModeClipRectWithTransform(float left, float top,
- float right, float bottom, const mat4* transform, SkRegion::Op op);
- bool rectangleListModeClipRectWithTransform(const Rect& r,
+ void rectangleListModeClipRectWithTransform(const Rect& r,
const mat4* transform, SkRegion::Op op);
void enterRegionModeFromRectangleMode();
void enterRegionModeFromRectangleListMode();
void enterRegionMode();
- bool regionModeClipRectWithTransform(const Rect& r, const mat4* transform,
+ void regionModeClipRectWithTransform(const Rect& r, const mat4* transform,
SkRegion::Op op);
- bool regionModeClipRectWithTransform(float left, float top, float right,
- float bottom, const mat4* transform, SkRegion::Op op);
void ensureClipRegion();
void onClipRegionUpdated();
- bool clipRegionOp(float left, float top, float right, float bottom,
- SkRegion::Op op);
+
+ // Called by every state modifying public method.
+ void onClipUpdated() {
+ mPostViewportClipObserved = true;
+ mLastSerialization = nullptr;
+ mLastResolutionResult = nullptr;
+ }
SkRegion createViewportRegion() {
return SkRegion(mViewportBounds.toSkIRect());
@@ -158,13 +213,23 @@ private:
pathAsRegion.setPath(path, createViewportRegion());
}
- enum Mode {
- kModeRectangle,
- kModeRegion,
- kModeRectangleList
- };
+ ClipMode mMode;
+ bool mPostViewportClipObserved = false;
+ bool mReplaceOpObserved = false;
+
+ /**
+ * If mLastSerialization is non-null, it represents an already serialized copy
+ * of the current clip state. If null, it has not been computed.
+ */
+ const ClipBase* mLastSerialization = nullptr;
+
+ /**
+ * This pair of pointers is a single entry cache of most recently seen
+ */
+ const ClipBase* mLastResolutionResult = nullptr;
+ const ClipBase* mLastResolutionClip = nullptr;
+ Matrix4 mLastResolutionTransform;
- Mode mMode;
Rect mViewportBounds;
Rect mClipRect;
SkRegion mClipRegion;
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index 9bd3bdc5617e..6d5833b3be86 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -45,7 +45,7 @@ struct DirtyStack {
};
DamageAccumulator::DamageAccumulator() {
- mHead = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack));
+ mHead = mAllocator.create_trivial<DirtyStack>();
memset(mHead, 0, sizeof(DirtyStack));
// Create a root that we will not pop off
mHead->prev = mHead;
@@ -78,7 +78,7 @@ void DamageAccumulator::computeCurrentTransform(Matrix4* outMatrix) const {
void DamageAccumulator::pushCommon() {
if (!mHead->next) {
- DirtyStack* nextFrame = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack));
+ DirtyStack* nextFrame = mAllocator.create_trivial<DirtyStack>();
nextFrame->next = nullptr;
nextFrame->prev = mHead;
mHead->next = nextFrame;
@@ -121,7 +121,14 @@ void DamageAccumulator::popTransform() {
static inline void mapRect(const Matrix4* matrix, const SkRect& in, SkRect* out) {
if (in.isEmpty()) return;
Rect temp(in);
- matrix->mapRect(temp);
+ if (CC_LIKELY(!matrix->isPerspective())) {
+ matrix->mapRect(temp);
+ } else {
+ // Don't attempt to calculate damage for a perspective transform
+ // as the numbers this works with can break the perspective
+ // calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX
+ temp.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
+ }
out->join(RECT_ARGS(temp));
}
@@ -134,7 +141,14 @@ static inline void mapRect(const RenderProperties& props, const SkRect& in, SkRe
const SkMatrix* transform = props.getTransformMatrix();
SkRect temp(in);
if (transform && !transform->isIdentity()) {
- transform->mapRect(&temp);
+ if (CC_LIKELY(!transform->hasPerspective())) {
+ transform->mapRect(&temp);
+ } else {
+ // Don't attempt to calculate damage for a perspective transform
+ // as the numbers this works with can break the perspective
+ // calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX
+ temp.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
+ }
}
temp.offset(props.getLeft(), props.getTop());
out->join(temp);
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index dd3365a49d78..250296ecc89f 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -24,6 +24,11 @@
#include "utils/Macros.h"
+// Smaller than INT_MIN/INT_MAX because we offset these values
+// and thus don't want to be adding offsets to INT_MAX, that's bad
+#define DIRTY_MIN (-0x7ffffff-1)
+#define DIRTY_MAX (0x7ffffff)
+
namespace android {
namespace uirenderer {
@@ -52,7 +57,7 @@ public:
// Returns the current dirty area, *NOT* transformed by pushed transforms
void peekAtDirty(SkRect* dest) const;
- void computeCurrentTransform(Matrix4* outMatrix) const;
+ ANDROID_API void computeCurrentTransform(Matrix4* outMatrix) const;
void finish(SkRect* totalDirty);
diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h
index 5808aaca76be..748edef730b7 100644
--- a/libs/hwui/Debug.h
+++ b/libs/hwui/Debug.h
@@ -17,11 +17,18 @@
#ifndef ANDROID_HWUI_DEBUG_H
#define ANDROID_HWUI_DEBUG_H
-// Turn on to check for OpenGL errors on each frame
-#define DEBUG_OPENGL 1
+#define DEBUG_LEVEL_HIGH 3
+#define DEBUG_LEVEL_MODERATE 2
+#define DEBUG_LEVEL_LOW 1
+#define DEBUG_LEVEL_NONE 0
-// Turn on to display informations about the GPU
-#define DEBUG_EXTENSIONS 0
+// Turn on to check for OpenGL errors on each frame
+// Note DEBUG_LEVEL_HIGH for DEBUG_OPENGL is only setable by enabling
+// HWUI_ENABLE_OPENGL_VALIDATION when building HWUI. Similarly if
+// HWUI_ENABLE_OPENGL_VALIDATION is set then this is always DEBUG_LEVEL_HIGH
+#ifndef DEBUG_OPENGL
+#define DEBUG_OPENGL DEBUG_LEVEL_LOW
+#endif
// Turn on to enable initialization information
#define DEBUG_INIT 0
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index f05857c43a35..1b0f42466bff 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -14,11 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-#define ATRACE_TAG ATRACE_TAG_VIEW
-
-#include <SkCanvas.h>
-
#include <utils/Trace.h>
#include <ui/Rect.h>
#include <ui/Region.h>
@@ -47,6 +42,12 @@ namespace uirenderer {
#define DEBUG_COLOR_MERGEDBATCH 0x5f7f7fff
#define DEBUG_COLOR_MERGEDBATCH_SOLO 0x5f7fff7f
+static bool avoidOverdraw() {
+ // Don't avoid overdraw when visualizing it, since that makes it harder to
+ // debug where it's coming from, and when the problem occurs.
+ return !Properties::debugOverdraw;
+};
+
/////////////////////////////////////////////////////////////////////////////////
// Operation Batches
/////////////////////////////////////////////////////////////////////////////////
@@ -72,7 +73,7 @@ public:
// NOTE: ignore empty bounds special case, since we don't merge across those ops
mBounds.unionWith(state->mBounds);
mAllOpsOpaque &= opaqueOverBounds;
- mOps.add(OpStatePair(op, state));
+ mOps.push_back(OpStatePair(op, state));
}
bool intersects(const Rect& rect) {
@@ -136,7 +137,7 @@ public:
inline int count() const { return mOps.size(); }
protected:
- Vector<OpStatePair> mOps;
+ std::vector<OpStatePair> mOps;
Rect mBounds; // union of bounds of contained ops
private:
bool mAllOpsOpaque;
@@ -221,7 +222,10 @@ public:
// if paints are equal, then modifiers + paint attribs don't need to be compared
if (op->mPaint == mOps[0].op->mPaint) return true;
- if (op->getPaintAlpha() != mOps[0].op->getPaintAlpha()) return false;
+ if (PaintUtils::getAlphaDirect(op->mPaint)
+ != PaintUtils::getAlphaDirect(mOps[0].op->mPaint)) {
+ return false;
+ }
if (op->mPaint && mOps[0].op->mPaint &&
op->mPaint->getColorFilter() != mOps[0].op->mPaint->getColorFilter()) {
@@ -413,7 +417,7 @@ void DeferredDisplayList::addClip(OpenGLRenderer& renderer, ClipOp* op) {
* beginning of the frame. This would avoid targetting and removing an FBO in the middle of a frame.
*
* saveLayer operations should be pulled to the beginning of the frame if the canvas doesn't have a
- * complex clip, and if the flags (kClip_SaveFlag & kClipToLayer_SaveFlag) are set.
+ * complex clip, and if the flags (SaveFlags::Clip & SaveFlags::ClipToLayer) are set.
*/
void DeferredDisplayList::addSaveLayer(OpenGLRenderer& renderer,
SaveLayerOp* op, int newSaveCount) {
@@ -421,7 +425,7 @@ void DeferredDisplayList::addSaveLayer(OpenGLRenderer& renderer,
this, op, op->getFlags(), newSaveCount);
storeStateOpBarrier(renderer, op);
- mSaveStack.push(newSaveCount);
+ mSaveStack.push_back(newSaveCount);
}
/**
@@ -432,11 +436,11 @@ void DeferredDisplayList::addSave(OpenGLRenderer& renderer, SaveOp* op, int newS
int saveFlags = op->getFlags();
DEFER_LOGD("%p adding saveOp %p, flags %x, new count %d", this, op, saveFlags, newSaveCount);
- if (recordingComplexClip() && (saveFlags & SkCanvas::kClip_SaveFlag)) {
+ if (recordingComplexClip() && (saveFlags & SaveFlags::Clip)) {
// store and replay the save operation, as it may be needed to correctly playback the clip
DEFER_LOGD(" adding save barrier with new save count %d", newSaveCount);
storeStateOpBarrier(renderer, op);
- mSaveStack.push(newSaveCount);
+ mSaveStack.push_back(newSaveCount);
}
}
@@ -459,11 +463,11 @@ void DeferredDisplayList::addRestoreToCount(OpenGLRenderer& renderer, StateOp* o
resetBatchingState();
}
- if (mSaveStack.isEmpty() || newSaveCount > mSaveStack.top()) {
+ if (mSaveStack.empty() || newSaveCount > mSaveStack.back()) {
return;
}
- while (!mSaveStack.isEmpty() && mSaveStack.top() >= newSaveCount) mSaveStack.pop();
+ while (!mSaveStack.empty() && mSaveStack.back() >= newSaveCount) mSaveStack.pop_back();
storeRestoreToCountBarrier(renderer, op, mSaveStack.size() + FLUSH_SAVE_STACK_DEPTH);
}
@@ -495,10 +499,10 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
// the merge path in those cases
deferInfo.mergeable &= !recordingComplexClip();
deferInfo.opaqueOverBounds &= !recordingComplexClip()
- && mSaveStack.isEmpty()
+ && mSaveStack.empty()
&& !state->mRoundRectClipState;
- if (CC_LIKELY(mAvoidOverdraw) && mBatches.size() &&
+ if (CC_LIKELY(avoidOverdraw()) && mBatches.size() &&
state->mClipSideFlags != kClipSide_ConservativeFull &&
deferInfo.opaqueOverBounds && state->mBounds.contains(mBounds)) {
// avoid overdraw by resetting drawing state + discarding drawing ops
@@ -510,7 +514,7 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
// TODO: elegant way to reuse batches?
DrawBatch* b = new DrawBatch(deferInfo);
b->add(op, state, deferInfo.opaqueOverBounds);
- mBatches.add(b);
+ mBatches.push_back(b);
return;
}
@@ -520,12 +524,12 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
// insertion point of a new batch, will hopefully be immediately after similar batch
// (eventually, should be similar shader)
int insertBatchIndex = mBatches.size();
- if (!mBatches.isEmpty()) {
+ if (!mBatches.empty()) {
if (state->mBounds.isEmpty()) {
- // don't know the bounds for op, so add to last batch and start from scratch on next op
+ // don't know the bounds for op, so create new batch and start from scratch on next op
DrawBatch* b = new DrawBatch(deferInfo);
b->add(op, state, deferInfo.opaqueOverBounds);
- mBatches.add(b);
+ mBatches.push_back(b);
resetBatchingState();
#if DEBUG_DEFER
DEFER_LOGD("Warning: Encountered op with empty bounds, resetting batches");
@@ -536,7 +540,11 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
if (deferInfo.mergeable) {
// Try to merge with any existing batch with same mergeId.
- if (mMergingBatches[deferInfo.batchId].get(deferInfo.mergeId, targetBatch)) {
+ std::unordered_map<mergeid_t, DrawBatch*>& mergingBatch
+ = mMergingBatches[deferInfo.batchId];
+ auto getResult = mergingBatch.find(deferInfo.mergeId);
+ if (getResult != mergingBatch.end()) {
+ targetBatch = getResult->second;
if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op, state)) {
targetBatch = nullptr;
}
@@ -580,7 +588,8 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
if (deferInfo.mergeable) {
targetBatch = new MergingDrawBatch(deferInfo,
renderer.getViewportWidth(), renderer.getViewportHeight());
- mMergingBatches[deferInfo.batchId].put(deferInfo.mergeId, targetBatch);
+ mMergingBatches[deferInfo.batchId].insert(
+ std::make_pair(deferInfo.mergeId, targetBatch));
} else {
targetBatch = new DrawBatch(deferInfo);
mBatchLookup[deferInfo.batchId] = targetBatch;
@@ -589,7 +598,7 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
DEFER_LOGD("creating %singBatch %p, bid %x, at %d",
deferInfo.mergeable ? "Merg" : "Draw",
targetBatch, deferInfo.batchId, insertBatchIndex);
- mBatches.insertAt(targetBatch, insertBatchIndex);
+ mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
}
targetBatch->add(op, state, deferInfo.opaqueOverBounds);
@@ -600,7 +609,7 @@ void DeferredDisplayList::storeStateOpBarrier(OpenGLRenderer& renderer, StateOp*
DeferredDisplayState* state = createState();
renderer.storeDisplayState(*state, getStateOpDeferFlags());
- mBatches.add(new StateOpBatch(op, state));
+ mBatches.push_back(new StateOpBatch(op, state));
resetBatchingState();
}
@@ -610,10 +619,10 @@ void DeferredDisplayList::storeRestoreToCountBarrier(OpenGLRenderer& renderer, S
this, newSaveCount, mBatches.size());
// store displayState for the restore operation, as it may be associated with a saveLayer that
- // doesn't have kClip_SaveFlag set
+ // doesn't have SaveFlags::Clip set
DeferredDisplayState* state = createState();
renderer.storeDisplayState(*state, getStateOpDeferFlags());
- mBatches.add(new RestoreToCountBatch(op, state, newSaveCount));
+ mBatches.push_back(new RestoreToCountBatch(op, state, newSaveCount));
resetBatchingState();
}
@@ -621,7 +630,7 @@ void DeferredDisplayList::storeRestoreToCountBarrier(OpenGLRenderer& renderer, S
// Replay / flush
/////////////////////////////////////////////////////////////////////////////////
-static void replayBatchList(const Vector<Batch*>& batchList,
+static void replayBatchList(const std::vector<Batch*>& batchList,
OpenGLRenderer& renderer, Rect& dirty) {
for (unsigned int i = 0; i < batchList.size(); i++) {
@@ -634,7 +643,7 @@ static void replayBatchList(const Vector<Batch*>& batchList,
void DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) {
ATRACE_NAME("flush drawing commands");
- Caches::getInstance().fontRenderer->endPrecaching();
+ Caches::getInstance().fontRenderer.endPrecaching();
if (isEmpty()) return; // nothing to flush
renderer.restoreToCount(1);
@@ -643,9 +652,9 @@ void DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) {
renderer.eventMark("Flush");
// save and restore so that reordering doesn't affect final state
- renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ renderer.save(SaveFlags::MatrixClip);
- if (CC_LIKELY(mAvoidOverdraw)) {
+ if (CC_LIKELY(avoidOverdraw())) {
for (unsigned int i = 1; i < mBatches.size(); i++) {
if (mBatches[i] && mBatches[i]->coversBounds(mBounds)) {
discardDrawingBatches(i - 1);
@@ -667,7 +676,7 @@ void DeferredDisplayList::discardDrawingBatches(const unsigned int maxIndex) {
// leave deferred state ops alone for simplicity (empty save restore pairs may now exist)
if (mBatches[i] && mBatches[i]->purelyDrawBatch()) {
delete mBatches[i];
- mBatches.replaceAt(nullptr, i);
+ mBatches[i] = nullptr;
}
}
mEarliestUnclearedIndex = maxIndex + 1;
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index 160c1ad2d1f6..98ccf11b1c2a 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -17,15 +17,17 @@
#ifndef ANDROID_HWUI_DEFERRED_DISPLAY_LIST_H
#define ANDROID_HWUI_DEFERRED_DISPLAY_LIST_H
+#include <unordered_map>
+
#include <utils/Errors.h>
#include <utils/LinearAllocator.h>
-#include <utils/Vector.h>
-#include <utils/TinyHashMap.h>
#include "Matrix.h"
#include "OpenGLRenderer.h"
#include "Rect.h"
+#include <vector>
+
class SkBitmap;
namespace android {
@@ -47,11 +49,6 @@ typedef const void* mergeid_t;
class DeferredDisplayState {
public:
- /** static void* operator new(size_t size); PURPOSELY OMITTED **/
- static void* operator new(size_t size, LinearAllocator& allocator) {
- return allocator.alloc(size);
- }
-
// global op bounds, mapped by mMatrix to be in screen space coordinates, clipped
Rect mBounds;
@@ -59,7 +56,6 @@ public:
bool mClipValid;
Rect mClip;
int mClipSideFlags; // specifies which sides of the bounds are clipped, unclipped if cleared
- bool mClipped;
mat4 mMatrix;
float mAlpha;
const RoundRectClipState* mRoundRectClipState;
@@ -81,8 +77,8 @@ public:
class DeferredDisplayList {
friend struct DeferStateStruct; // used to give access to allocator
public:
- DeferredDisplayList(const Rect& bounds, bool avoidOverdraw = true) :
- mBounds(bounds), mAvoidOverdraw(avoidOverdraw) {
+ DeferredDisplayList(const Rect& bounds)
+ : mBounds(bounds) {
clear();
}
~DeferredDisplayList() { clear(); }
@@ -100,7 +96,7 @@ public:
kOpBatch_Count, // Add other batch ids before this
};
- bool isEmpty() { return mBatches.isEmpty(); }
+ bool isEmpty() { return mBatches.empty(); }
/**
* Plays back all of the draw ops recorded into batches to the renderer.
@@ -123,7 +119,7 @@ private:
DeferredDisplayList(const DeferredDisplayList& other); // disallow copy
DeferredDisplayState* createState() {
- return new (mAllocator) DeferredDisplayState();
+ return mAllocator.create_trivial<DeferredDisplayState>();
}
void tryRecycleState(DeferredDisplayState* state) {
@@ -150,17 +146,16 @@ private:
// layer space bounds of rendering
Rect mBounds;
- const bool mAvoidOverdraw;
/**
* At defer time, stores the *defer time* savecount of save/saveLayer ops that were deferred, so
* that when an associated restoreToCount is deferred, it can be recorded as a
* RestoreToCountBatch
*/
- Vector<int> mSaveStack;
+ std::vector<int> mSaveStack;
int mComplexClipStackStart;
- Vector<Batch*> mBatches;
+ std::vector<Batch*> mBatches;
// Maps batch ids to the most recent *non-merging* batch of that id
Batch* mBatchLookup[kOpBatch_Count];
@@ -176,7 +171,7 @@ private:
* MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
* collide, which avoids the need to resolve mergeid collisions.
*/
- TinyHashMap<mergeid_t, DrawBatch*> mMergingBatches[kOpBatch_Count];
+ std::unordered_map<mergeid_t, DrawBatch*> mMergingBatches[kOpBatch_Count];
LinearAllocator mAllocator;
};
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index a17904e31047..f833a5405a5c 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -24,14 +24,13 @@
namespace android {
namespace uirenderer {
-DeferredLayerUpdater::DeferredLayerUpdater(renderthread::RenderThread& thread, Layer* layer)
+DeferredLayerUpdater::DeferredLayerUpdater(Layer* layer)
: mSurfaceTexture(nullptr)
, mTransform(nullptr)
, mNeedsGLContextAttach(false)
, mUpdateTexImage(false)
, mLayer(layer)
- , mCaches(Caches::getInstance())
- , mRenderThread(thread) {
+ , mCaches(Caches::getInstance()) {
mWidth = mLayer->layer.getWidth();
mHeight = mLayer->layer.getHeight();
mBlend = mLayer->isBlend();
@@ -48,13 +47,13 @@ DeferredLayerUpdater::~DeferredLayerUpdater() {
}
void DeferredLayerUpdater::setPaint(const SkPaint* paint) {
- OpenGLRenderer::getAlphaAndModeDirect(paint, &mAlpha, &mMode);
+ mAlpha = PaintUtils::getAlphaDirect(paint);
+ mMode = PaintUtils::getXfermodeDirect(paint);
SkColorFilter* colorFilter = (paint) ? paint->getColorFilter() : nullptr;
SkRefCnt_SafeAssign(mColorFilter, colorFilter);
}
-bool DeferredLayerUpdater::apply() {
- bool success = true;
+void DeferredLayerUpdater::apply() {
// These properties are applied the same to both layer types
mLayer->setColorFilter(mColorFilter);
mLayer->setAlpha(mAlpha, mMode);
@@ -73,7 +72,6 @@ bool DeferredLayerUpdater::apply() {
setTransform(nullptr);
}
}
- return success;
}
void DeferredLayerUpdater::doUpdateTexImage() {
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index 82f2741b7478..44a24c840892 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -35,7 +35,7 @@ class DeferredLayerUpdater : public VirtualLightRefBase {
public:
// Note that DeferredLayerUpdater assumes it is taking ownership of the layer
// and will not call incrementRef on it as a result.
- ANDROID_API DeferredLayerUpdater(renderthread::RenderThread& thread, Layer* layer);
+ ANDROID_API DeferredLayerUpdater(Layer* layer);
ANDROID_API ~DeferredLayerUpdater();
ANDROID_API bool setSize(int width, int height) {
@@ -47,6 +47,9 @@ public:
return false;
}
+ int getWidth() { return mWidth; }
+ int getHeight() { return mHeight; }
+
ANDROID_API bool setBlend(bool blend) {
if (blend != mBlend) {
mBlend = blend;
@@ -75,15 +78,19 @@ public:
mTransform = matrix ? new SkMatrix(*matrix) : nullptr;
}
+ SkMatrix* getTransform() {
+ return mTransform;
+ }
+
ANDROID_API void setPaint(const SkPaint* paint);
- ANDROID_API bool apply();
+ void apply();
Layer* backingLayer() {
return mLayer;
}
- ANDROID_API void detachSurfaceTexture();
+ void detachSurfaceTexture();
private:
// Generic properties
@@ -101,7 +108,6 @@ private:
Layer* mLayer;
Caches& mCaches;
- renderthread::RenderThread& mRenderThread;
void doUpdateTexImage();
};
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
new file mode 100644
index 000000000000..4cfbb2a43198
--- /dev/null
+++ b/libs/hwui/DeviceInfo.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 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 <DeviceInfo.h>
+
+#include "Extensions.h"
+
+#include <GLES2/gl2.h>
+#include <log/log.h>
+
+#include <thread>
+#include <mutex>
+
+namespace android {
+namespace uirenderer {
+
+static DeviceInfo* sDeviceInfo = nullptr;
+static std::once_flag sInitializedFlag;
+
+const DeviceInfo* DeviceInfo::get() {
+ LOG_ALWAYS_FATAL_IF(!sDeviceInfo, "DeviceInfo not yet initialized.");
+ return sDeviceInfo;
+}
+
+void DeviceInfo::initialize() {
+ std::call_once(sInitializedFlag, []() {
+ sDeviceInfo = new DeviceInfo();
+ sDeviceInfo->load();
+ });
+}
+
+void DeviceInfo::load() {
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
new file mode 100644
index 000000000000..f576a4f48021
--- /dev/null
+++ b/libs/hwui/DeviceInfo.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 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 DEVICEINFO_H
+#define DEVICEINFO_H
+
+#include "Extensions.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+class DeviceInfo {
+ PREVENT_COPY_AND_ASSIGN(DeviceInfo);
+public:
+ // returns nullptr if DeviceInfo is not initialized yet
+ // Note this does not have a memory fence so it's up to the caller
+ // to use one if required. Normally this should not be necessary
+ static const DeviceInfo* get();
+
+ // only call this after GL has been initialized, or at any point if compiled
+ // with HWUI_NULL_GPU
+ static void initialize();
+
+ const Extensions& extensions() const { return mExtensions; }
+
+ int maxTextureSize() const { return mMaxTextureSize; }
+
+private:
+ DeviceInfo() {}
+ ~DeviceInfo() {}
+
+ void load();
+
+ Extensions mExtensions;
+ int mMaxTextureSize;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* DEVICEINFO_H */
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index e679bff18c86..b572bdaccb86 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define ATRACE_TAG ATRACE_TAG_VIEW
-
#include <SkCanvas.h>
#include <algorithm>
@@ -23,47 +21,75 @@
#include "Debug.h"
#include "DisplayList.h"
+#include "RenderNode.h"
+
+#if HWUI_NEW_OPS
+#include "RecordedOp.h"
+#else
#include "DisplayListOp.h"
+#endif
namespace android {
namespace uirenderer {
-DisplayListData::DisplayListData()
+DisplayList::DisplayList()
: projectionReceiveIndex(-1)
+ , stdAllocator(allocator)
+ , chunks(stdAllocator)
+ , ops(stdAllocator)
+ , children(stdAllocator)
+ , bitmapResources(stdAllocator)
+ , pathResources(stdAllocator)
+ , patchResources(stdAllocator)
+ , paints(stdAllocator)
+ , regions(stdAllocator)
+ , referenceHolders(stdAllocator)
+ , functors(stdAllocator)
+ , pushStagingFunctors(stdAllocator)
, hasDrawOps(false) {
}
-DisplayListData::~DisplayListData() {
+DisplayList::~DisplayList() {
cleanupResources();
}
-void DisplayListData::cleanupResources() {
- ResourceCache& resourceCache = ResourceCache::getInstance();
- resourceCache.lock();
+void DisplayList::cleanupResources() {
+ if (CC_UNLIKELY(patchResources.size())) {
+ ResourceCache& resourceCache = ResourceCache::getInstance();
+ resourceCache.lock();
- for (size_t i = 0; i < patchResources.size(); i++) {
- resourceCache.decrementRefcountLocked(patchResources.itemAt(i));
- }
+ for (size_t i = 0; i < patchResources.size(); i++) {
+ resourceCache.decrementRefcountLocked(patchResources[i]);
+ }
- resourceCache.unlock();
+ resourceCache.unlock();
+ }
for (size_t i = 0; i < pathResources.size(); i++) {
- const SkPath* path = pathResources.itemAt(i);
+ const SkPath* path = pathResources[i];
if (path->unique() && Caches::hasInstance()) {
Caches::getInstance().pathCache.removeDeferred(path);
}
delete path;
}
+ for (auto& iter : functors) {
+ if (iter.listener) {
+ iter.listener->onGlFunctorReleased(iter.functor);
+ }
+ }
+
patchResources.clear();
pathResources.clear();
paints.clear();
regions.clear();
}
-size_t DisplayListData::addChild(DrawRenderNodeOp* op) {
- mReferenceHolders.push(op->renderNode());
- return mChildren.add(op);
+size_t DisplayList::addChild(NodeOpType* op) {
+ referenceHolders.push_back(op->renderNode);
+ size_t index = children.size();
+ children.push_back(op);
+ return index;
}
}; // namespace uirenderer
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 7fbda1f6b192..5b3227b7db97 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -17,10 +17,6 @@
#ifndef ANDROID_HWUI_DISPLAY_LIST_H
#define ANDROID_HWUI_DISPLAY_LIST_H
-#ifndef LOG_TAG
- #define LOG_TAG "OpenGLRenderer"
-#endif
-
#include <SkCamera.h>
#include <SkMatrix.h>
@@ -31,7 +27,6 @@
#include <utils/RefBase.h>
#include <utils/SortedVector.h>
#include <utils/String8.h>
-#include <utils/Vector.h>
#include <cutils/compiler.h>
@@ -40,9 +35,12 @@
#include "Debug.h"
#include "CanvasProperty.h"
#include "DeferredDisplayList.h"
+#include "GlFunctorLifecycleListener.h"
#include "Matrix.h"
#include "RenderProperties.h"
+#include <vector>
+
class SkBitmap;
class SkPaint;
class SkPath;
@@ -58,12 +56,19 @@ class OpenGLRenderer;
class Rect;
class Layer;
-class ClipRectOp;
-class SaveLayerOp;
-class SaveOp;
-class RestoreToCountOp;
+#if HWUI_NEW_OPS
+struct RecordedOp;
+struct RenderNodeOp;
+
+typedef RecordedOp BaseOpType;
+typedef RenderNodeOp NodeOpType;
+#else
class DrawRenderNodeOp;
+typedef DisplayListOp BaseOpType;
+typedef DrawRenderNodeOp NodeOpType;
+#endif
+
/**
* Holds data used in the playback a tree of DisplayLists.
*/
@@ -106,70 +111,104 @@ struct ReplayStateStruct : public PlaybackStateStruct {
};
/**
+ * Functor that can be used for objects with data in both UI thread and RT to keep the data
+ * in sync. This functor, when added to DisplayList, will be call during DisplayList sync.
+ */
+struct PushStagingFunctor {
+ PushStagingFunctor() {}
+ virtual ~PushStagingFunctor() {}
+ virtual void operator ()() {}
+};
+
+struct FunctorContainer {
+ Functor* functor;
+ GlFunctorLifecycleListener* listener;
+};
+
+/**
* Data structure that holds the list of commands used in display list stream
*/
-class DisplayListData {
+class DisplayList {
friend class DisplayListCanvas;
+ friend class RecordingCanvas;
public:
struct Chunk {
- // range of included ops in DLD::displayListOps
+ // range of included ops in DisplayList::ops()
size_t beginOpIndex;
size_t endOpIndex;
- // range of included children in DLD::mChildren
+ // range of included children in DisplayList::children()
size_t beginChildIndex;
size_t endChildIndex;
// whether children with non-zero Z in the chunk should be reordered
bool reorderChildren;
+#if HWUI_NEW_OPS
+ const ClipBase* reorderClip;
+#endif
};
- DisplayListData();
- ~DisplayListData();
-
- // pointers to all ops within display list, pointing into allocator data
- Vector<DisplayListOp*> displayListOps;
+ DisplayList();
+ ~DisplayList();
- // index of DisplayListOp restore, after which projected descendents should be drawn
+ // index of DisplayListOp restore, after which projected descendants should be drawn
int projectionReceiveIndex;
- Vector<const SkBitmap*> bitmapResources;
- Vector<const SkPath*> pathResources;
- Vector<const Res_png_9patch*> patchResources;
+ const LsaVector<Chunk>& getChunks() const { return chunks; }
+ const LsaVector<BaseOpType*>& getOps() const { return ops; }
- std::vector<std::unique_ptr<const SkPaint>> paints;
- std::vector<std::unique_ptr<const SkRegion>> regions;
- Vector<Functor*> functors;
+ const LsaVector<NodeOpType*>& getChildren() const { return children; }
- const Vector<Chunk>& getChunks() const {
- return chunks;
- }
+ const LsaVector<const SkBitmap*>& getBitmapResources() const { return bitmapResources; }
+ const LsaVector<FunctorContainer>& getFunctors() const { return functors; }
+ const LsaVector<PushStagingFunctor*>& getPushStagingFunctors() { return pushStagingFunctors; }
+
+ size_t addChild(NodeOpType* childOp);
- size_t addChild(DrawRenderNodeOp* childOp);
- const Vector<DrawRenderNodeOp*>& children() { return mChildren; }
void ref(VirtualLightRefBase* prop) {
- mReferenceHolders.push(prop);
+ referenceHolders.push_back(prop);
}
size_t getUsedSize() {
return allocator.usedSize();
}
bool isEmpty() {
+#if HWUI_NEW_OPS
+ return ops.empty();
+#else
return !hasDrawOps;
+#endif
}
private:
- Vector< sp<VirtualLightRefBase> > mReferenceHolders;
+ // allocator into which all ops and LsaVector arrays allocated
+ LinearAllocator allocator;
+ LinearStdAllocator<void*> stdAllocator;
- // list of children display lists for quick, non-drawing traversal
- Vector<DrawRenderNodeOp*> mChildren;
+ LsaVector<Chunk> chunks;
+ LsaVector<BaseOpType*> ops;
- Vector<Chunk> chunks;
+ // list of Ops referring to RenderNode children for quick, non-drawing traversal
+ LsaVector<NodeOpType*> children;
- // allocator into which all ops were allocated
- LinearAllocator allocator;
- bool hasDrawOps;
+ // Resources - Skia objects + 9 patches referred to by this DisplayList
+ LsaVector<const SkBitmap*> bitmapResources;
+ LsaVector<const SkPath*> pathResources;
+ LsaVector<const Res_png_9patch*> patchResources;
+ LsaVector<std::unique_ptr<const SkPaint>> paints;
+ LsaVector<std::unique_ptr<const SkRegion>> regions;
+ LsaVector< sp<VirtualLightRefBase> > referenceHolders;
+
+ // List of functors
+ LsaVector<FunctorContainer> functors;
+
+ // List of functors that need to be notified of pushStaging. Note that this list gets nothing
+ // but a callback during sync DisplayList, unlike the list of functors defined above, which
+ // gets special treatment exclusive for webview.
+ LsaVector<PushStagingFunctor*> pushStagingFunctors;
+
+ bool hasDrawOps; // only used if !HWUI_NEW_OPS
void cleanupResources();
};
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index 2dd52788074d..ca968cef91b2 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -16,11 +16,12 @@
#include "DisplayListCanvas.h"
-#include "ResourceCache.h"
#include "DeferredDisplayList.h"
#include "DeferredLayerUpdater.h"
#include "DisplayListOp.h"
+#include "ResourceCache.h"
#include "RenderNode.h"
+#include "VectorDrawable.h"
#include "utils/PaintUtils.h"
#include <SkCamera.h>
@@ -31,70 +32,64 @@
namespace android {
namespace uirenderer {
-DisplayListCanvas::DisplayListCanvas()
+DisplayListCanvas::DisplayListCanvas(int width, int height)
: mState(*this)
, mResourceCache(ResourceCache::getInstance())
- , mDisplayListData(nullptr)
+ , mDisplayList(nullptr)
, mTranslateX(0.0f)
, mTranslateY(0.0f)
, mHasDeferredTranslate(false)
, mDeferredBarrierType(kBarrier_None)
, mHighContrastText(false)
, mRestoreSaveCount(-1) {
+ resetRecording(width, height);
}
DisplayListCanvas::~DisplayListCanvas() {
- LOG_ALWAYS_FATAL_IF(mDisplayListData,
+ LOG_ALWAYS_FATAL_IF(mDisplayList,
"Destroyed a DisplayListCanvas during a record!");
}
-///////////////////////////////////////////////////////////////////////////////
-// Operations
-///////////////////////////////////////////////////////////////////////////////
-
-DisplayListData* DisplayListCanvas::finishRecording() {
- mPaintMap.clear();
- mRegionMap.clear();
- mPathMap.clear();
- DisplayListData* data = mDisplayListData;
- mDisplayListData = nullptr;
- mSkiaCanvasProxy.reset(nullptr);
- return data;
-}
-
-void DisplayListCanvas::prepareDirty(float left, float top,
- float right, float bottom) {
-
- LOG_ALWAYS_FATAL_IF(mDisplayListData,
+void DisplayListCanvas::resetRecording(int width, int height) {
+ LOG_ALWAYS_FATAL_IF(mDisplayList,
"prepareDirty called a second time during a recording!");
- mDisplayListData = new DisplayListData();
+ mDisplayList = new DisplayList();
- mState.initializeSaveStack(0, 0, mState.getWidth(), mState.getHeight(), Vector3());
+ mState.initializeSaveStack(width, height,
+ 0, 0, width, height, Vector3());
mDeferredBarrierType = kBarrier_InOrder;
mState.setDirtyClip(false);
mRestoreSaveCount = -1;
}
-bool DisplayListCanvas::finish() {
+
+///////////////////////////////////////////////////////////////////////////////
+// Operations
+///////////////////////////////////////////////////////////////////////////////
+
+DisplayList* DisplayListCanvas::finishRecording() {
flushRestoreToCount();
flushTranslate();
- return false;
-}
-void DisplayListCanvas::interrupt() {
-}
-
-void DisplayListCanvas::resume() {
+ mPaintMap.clear();
+ mRegionMap.clear();
+ mPathMap.clear();
+ DisplayList* displayList = mDisplayList;
+ mDisplayList = nullptr;
+ mSkiaCanvasProxy.reset(nullptr);
+ return displayList;
}
-void DisplayListCanvas::callDrawGLFunction(Functor *functor) {
+void DisplayListCanvas::callDrawGLFunction(Functor* functor,
+ GlFunctorLifecycleListener* listener) {
addDrawOp(new (alloc()) DrawFunctorOp(functor));
- mDisplayListData->functors.add(functor);
+ mDisplayList->functors.push_back({functor, listener});
+ mDisplayList->ref(listener);
}
SkCanvas* DisplayListCanvas::asSkCanvas() {
- LOG_ALWAYS_FATAL_IF(!mDisplayListData,
+ LOG_ALWAYS_FATAL_IF(!mDisplayList,
"attempting to get an SkCanvas when we are not recording!");
if (!mSkiaCanvasProxy) {
mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this));
@@ -110,7 +105,7 @@ SkCanvas* DisplayListCanvas::asSkCanvas() {
return mSkiaCanvasProxy.get();
}
-int DisplayListCanvas::save(SkCanvas::SaveFlags flags) {
+int DisplayListCanvas::save(SaveFlags::Flags flags) {
addStateOp(new (alloc()) SaveOp((int) flags));
return mState.save((int) flags);
}
@@ -133,9 +128,9 @@ void DisplayListCanvas::restoreToCount(int saveCount) {
}
int DisplayListCanvas::saveLayer(float left, float top, float right, float bottom,
- const SkPaint* paint, SkCanvas::SaveFlags flags) {
+ const SkPaint* paint, SaveFlags::Flags flags) {
// force matrix/clip isolation for layer
- flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag;
+ flags |= SaveFlags::MatrixClip;
paint = refPaint(paint);
addStateOp(new (alloc()) SaveLayerOp(left, top, right, bottom, paint, (int) flags));
@@ -176,11 +171,6 @@ void DisplayListCanvas::setMatrix(const SkMatrix& matrix) {
mState.setMatrix(matrix);
}
-void DisplayListCanvas::setLocalMatrix(const SkMatrix& matrix) {
- addStateOp(new (alloc()) SetLocalMatrixOp(matrix));
- mState.setMatrix(matrix);
-}
-
void DisplayListCanvas::concat(const SkMatrix& matrix) {
addStateOp(new (alloc()) ConcatMatrixOp(matrix));
mState.concatMatrix(matrix);
@@ -229,11 +219,11 @@ void DisplayListCanvas::drawRenderNode(RenderNode* renderNode) {
addRenderNodeOp(op);
}
-void DisplayListCanvas::drawLayer(DeferredLayerUpdater* layerHandle, float x, float y) {
+void DisplayListCanvas::drawLayer(DeferredLayerUpdater* layerHandle) {
// We ref the DeferredLayerUpdater due to its thread-safe ref-counting
// semantics.
- mDisplayListData->ref(layerHandle);
- addDrawOp(new (alloc()) DrawLayerOp(layerHandle->backingLayer(), x, y));
+ mDisplayList->ref(layerHandle);
+ addDrawOp(new (alloc()) DrawLayerOp(layerHandle->backingLayer()));
}
void DisplayListCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
@@ -245,7 +235,7 @@ void DisplayListCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint)
void DisplayListCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top,
const SkPaint* paint) {
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
translate(left, top);
drawBitmap(&bitmap, paint);
restore();
@@ -266,7 +256,7 @@ void DisplayListCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matri
drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
} else {
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
concat(matrix);
drawBitmap(&bitmap, paint);
restore();
@@ -282,7 +272,7 @@ void DisplayListCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float
&& (srcBottom - srcTop == dstBottom - dstTop)
&& (srcRight - srcLeft == dstRight - dstLeft)) {
// transform simple rect to rect drawing case into position bitmap ops, since they merge
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
translate(dstLeft, dstTop);
drawBitmap(&bitmap, paint);
restore();
@@ -296,7 +286,7 @@ void DisplayListCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float
// Apply the scale transform on the canvas, so that the shader
// effectively calculates positions relative to src rect space
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
translate(dstLeft, dstTop);
scale(scaleX, scaleY);
@@ -330,13 +320,14 @@ void DisplayListCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, in
vertices, colors, paint));
}
-void DisplayListCanvas::drawPatch(const SkBitmap& bitmap, const Res_png_9patch* patch,
- float left, float top, float right, float bottom, const SkPaint* paint) {
+void DisplayListCanvas::drawNinePatch(const SkBitmap& bitmap, const Res_png_9patch& patch,
+ float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) {
const SkBitmap* bitmapPtr = refBitmap(bitmap);
- patch = refPatch(patch);
+ const Res_png_9patch* patchPtr = refPatch(&patch);
paint = refPaint(paint);
- addDrawOp(new (alloc()) DrawPatchOp(bitmapPtr, patch, left, top, right, bottom, paint));
+ addDrawOp(new (alloc()) DrawPatchOp(bitmapPtr, patchPtr,
+ dstLeft, dstTop, dstRight, dstBottom, paint));
}
void DisplayListCanvas::drawColor(int color, SkXfermode::Mode mode) {
@@ -366,13 +357,13 @@ void DisplayListCanvas::drawRoundRect(
CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom,
CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry,
CanvasPropertyPaint* paint) {
- mDisplayListData->ref(left);
- mDisplayListData->ref(top);
- mDisplayListData->ref(right);
- mDisplayListData->ref(bottom);
- mDisplayListData->ref(rx);
- mDisplayListData->ref(ry);
- mDisplayListData->ref(paint);
+ mDisplayList->ref(left);
+ mDisplayList->ref(top);
+ mDisplayList->ref(right);
+ mDisplayList->ref(bottom);
+ mDisplayList->ref(rx);
+ mDisplayList->ref(ry);
+ mDisplayList->ref(paint);
refBitmapsInShader(paint->value.getShader());
addDrawOp(new (alloc()) DrawRoundRectPropsOp(&left->value, &top->value,
&right->value, &bottom->value, &rx->value, &ry->value, &paint->value));
@@ -384,10 +375,10 @@ void DisplayListCanvas::drawCircle(float x, float y, float radius, const SkPaint
void DisplayListCanvas::drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y,
CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) {
- mDisplayListData->ref(x);
- mDisplayListData->ref(y);
- mDisplayListData->ref(radius);
- mDisplayListData->ref(paint);
+ mDisplayList->ref(x);
+ mDisplayList->ref(y);
+ mDisplayList->ref(radius);
+ mDisplayList->ref(paint);
refBitmapsInShader(paint->value.getShader());
addDrawOp(new (alloc()) DrawCirclePropsOp(&x->value, &y->value,
&radius->value, &paint->value));
@@ -424,40 +415,24 @@ void DisplayListCanvas::drawPoints(const float* points, int count, const SkPaint
addDrawOp(new (alloc()) DrawPointsOp(points, count, refPaint(&paint)));
}
-void DisplayListCanvas::drawTextOnPath(const uint16_t* glyphs, int count,
+void DisplayListCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
+ mDisplayList->ref(tree);
+ mDisplayList->pushStagingFunctors.push_back(tree->getFunctor());
+ addDrawOp(new (alloc()) DrawVectorDrawableOp(tree, tree->stagingProperties()->getBounds()));
+}
+
+void DisplayListCanvas::drawGlyphsOnPath(const uint16_t* glyphs, int count,
const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) {
if (!glyphs || count <= 0) return;
int bytesCount = 2 * count;
- DrawOp* op = new (alloc()) DrawTextOnPathOp(refText((const char*) glyphs, bytesCount),
+ DrawOp* op = new (alloc()) DrawTextOnPathOp(refBuffer<glyph_t>(glyphs, count),
bytesCount, count, refPath(&path),
hOffset, vOffset, refPaint(&paint));
addDrawOp(op);
}
-void DisplayListCanvas::drawPosText(const uint16_t* text, const float* positions,
- int count, int posCount, const SkPaint& paint) {
- if (!text || count <= 0) return;
-
- int bytesCount = 2 * count;
- positions = refBuffer<float>(positions, count * 2);
-
- DrawOp* op = new (alloc()) DrawPosTextOp(refText((const char*) text, bytesCount),
- bytesCount, count, positions, refPaint(&paint));
- addDrawOp(op);
-}
-
-static void simplifyPaint(int color, SkPaint* paint) {
- paint->setColor(color);
- paint->setShader(nullptr);
- paint->setColorFilter(nullptr);
- paint->setLooper(nullptr);
- paint->setStrokeWidth(4 + 0.04 * paint->getTextSize());
- paint->setStrokeJoin(SkPaint::kRound_Join);
- paint->setLooper(nullptr);
-}
-
-void DisplayListCanvas::drawText(const uint16_t* glyphs, const float* positions,
+void DisplayListCanvas::drawGlyphs(const uint16_t* glyphs, const float* positions,
int count, const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) {
@@ -465,34 +440,38 @@ void DisplayListCanvas::drawText(const uint16_t* glyphs, const float* positions,
if (!glyphs || count <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
int bytesCount = count * 2;
- const char* text = refText((const char*) glyphs, bytesCount);
positions = refBuffer<float>(positions, count * 2);
Rect bounds(boundsLeft, boundsTop, boundsRight, boundsBottom);
- if (CC_UNLIKELY(mHighContrastText)) {
- // high contrast draw path
- int color = paint.getColor();
- int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
- bool darken = channelSum < (128 * 3);
-
- // outline
- SkPaint* outlinePaint = copyPaint(&paint);
- simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, outlinePaint);
- outlinePaint->setStyle(SkPaint::kStrokeAndFill_Style);
- addDrawOp(new (alloc()) DrawTextOp(text, bytesCount, count,
- x, y, positions, outlinePaint, totalAdvance, bounds)); // bounds?
-
- // inner
- SkPaint* innerPaint = copyPaint(&paint);
- simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, innerPaint);
- innerPaint->setStyle(SkPaint::kFill_Style);
- addDrawOp(new (alloc()) DrawTextOp(text, bytesCount, count,
- x, y, positions, innerPaint, totalAdvance, bounds));
+ DrawOp* op = new (alloc()) DrawTextOp(refBuffer<glyph_t>(glyphs, count), bytesCount, count,
+ x, y, positions, refPaint(&paint), totalAdvance, bounds);
+ addDrawOp(op);
+ drawTextDecorations(x, y, totalAdvance, paint);
+}
+
+void DisplayListCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
+ if (paint.getStyle() != SkPaint::kFill_Style ||
+ (paint.isAntiAlias() && !mState.currentTransform()->isSimple())) {
+ SkRegion::Iterator it(region);
+ while (!it.done()) {
+ const SkIRect& r = it.rect();
+ drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint);
+ it.next();
+ }
} else {
- // standard draw path
- DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count,
- x, y, positions, refPaint(&paint), totalAdvance, bounds);
- addDrawOp(op);
+ int count = 0;
+ Vector<float> rects;
+ SkRegion::Iterator it(region);
+ while (!it.done()) {
+ const SkIRect& r = it.rect();
+ rects.push(r.fLeft);
+ rects.push(r.fTop);
+ rects.push(r.fRight);
+ rects.push(r.fBottom);
+ count += 4;
+ it.next();
+ }
+ drawRects(rects.array(), count, &paint);
}
}
@@ -532,21 +511,26 @@ void DisplayListCanvas::flushTranslate() {
}
size_t DisplayListCanvas::addOpAndUpdateChunk(DisplayListOp* op) {
- int insertIndex = mDisplayListData->displayListOps.add(op);
+ int insertIndex = mDisplayList->ops.size();
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("unsupported");
+#else
+ mDisplayList->ops.push_back(op);
+#endif
if (mDeferredBarrierType != kBarrier_None) {
// op is first in new chunk
- mDisplayListData->chunks.push();
- DisplayListData::Chunk& newChunk = mDisplayListData->chunks.editTop();
+ mDisplayList->chunks.emplace_back();
+ DisplayList::Chunk& newChunk = mDisplayList->chunks.back();
newChunk.beginOpIndex = insertIndex;
newChunk.endOpIndex = insertIndex + 1;
newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder);
- int nextChildIndex = mDisplayListData->children().size();
+ int nextChildIndex = mDisplayList->children.size();
newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
mDeferredBarrierType = kBarrier_None;
} else {
// standard case - append to existing chunk
- mDisplayListData->chunks.editTop().endOpIndex = insertIndex + 1;
+ mDisplayList->chunks.back().endOpIndex = insertIndex + 1;
}
return insertIndex;
}
@@ -569,22 +553,24 @@ size_t DisplayListCanvas::addDrawOp(DrawOp* op) {
op->setQuickRejected(rejected);
}
- mDisplayListData->hasDrawOps = true;
+ mDisplayList->hasDrawOps = true;
return flushAndAddOp(op);
}
size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) {
int opIndex = addDrawOp(op);
- int childIndex = mDisplayListData->addChild(op);
+#if !HWUI_NEW_OPS
+ int childIndex = mDisplayList->addChild(op);
// update the chunk's child indices
- DisplayListData::Chunk& chunk = mDisplayListData->chunks.editTop();
+ DisplayList::Chunk& chunk = mDisplayList->chunks.back();
chunk.endChildIndex = childIndex + 1;
- if (op->renderNode()->stagingProperties().isProjectionReceiver()) {
+ if (op->renderNode->stagingProperties().isProjectionReceiver()) {
// use staging property, since recording on UI thread
- mDisplayListData->projectionReceiveIndex = opIndex;
+ mDisplayList->projectionReceiveIndex = opIndex;
}
+#endif
return opIndex;
}
@@ -595,7 +581,7 @@ void DisplayListCanvas::refBitmapsInShader(const SkShader* shader) {
// it to the bitmap pile
SkBitmap bitmap;
SkShader::TileMode xy[2];
- if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) {
+ if (shader->isABitmap(&bitmap, nullptr, xy)) {
refBitmap(bitmap);
return;
}
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index 4982cc919b5e..664f79e283b6 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -17,6 +17,14 @@
#ifndef ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
#define ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
+#include "CanvasState.h"
+#include "DisplayList.h"
+#include "RenderNode.h"
+#include "ResourceCache.h"
+#include "SkiaCanvasProxy.h"
+#include "hwui/Canvas.h"
+#include "utils/Macros.h"
+
#include <SkDrawFilter.h>
#include <SkMatrix.h>
#include <SkPaint.h>
@@ -25,13 +33,6 @@
#include <SkTLazy.h>
#include <cutils/compiler.h>
-#include "Canvas.h"
-#include "CanvasState.h"
-#include "DisplayList.h"
-#include "SkiaCanvasProxy.h"
-#include "RenderNode.h"
-#include "ResourceCache.h"
-
namespace android {
namespace uirenderer {
@@ -54,6 +55,7 @@ class DeferredDisplayList;
class DeferredLayerUpdater;
class DisplayListOp;
class DrawOp;
+class DrawRenderNodeOp;
class RenderNode;
class StateOp;
@@ -62,67 +64,37 @@ class StateOp;
*/
class ANDROID_API DisplayListCanvas: public Canvas, public CanvasStateClient {
public:
- DisplayListCanvas();
+ DisplayListCanvas(int width, int height);
virtual ~DisplayListCanvas();
- void insertReorderBarrier(bool enableReorder);
-
- DisplayListData* finishRecording();
-
-// ----------------------------------------------------------------------------
-// HWUI Frame state operations
-// ----------------------------------------------------------------------------
-
- void prepareDirty(float left, float top, float right, float bottom);
- void prepare() { prepareDirty(0.0f, 0.0f, width(), height()); }
- bool finish();
- void interrupt();
- void resume();
+ virtual void resetRecording(int width, int height) override;
+ virtual WARN_UNUSED_RESULT DisplayList* finishRecording() override;
// ----------------------------------------------------------------------------
// HWUI Canvas state operations
// ----------------------------------------------------------------------------
- void setViewport(int width, int height) { mState.setViewport(width, height); }
-
- const Rect& getRenderTargetClipBounds() const { return mState.getRenderTargetClipBounds(); }
-
- bool isCurrentTransformSimple() {
- return mState.currentTransform()->isSimple();
- }
+ virtual void insertReorderBarrier(bool enableReorder) override;
// ----------------------------------------------------------------------------
// HWUI Canvas draw operations
// ----------------------------------------------------------------------------
- // Bitmap-based
- void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
- // TODO: move drawPatch() to Canvas.h
- void drawPatch(const SkBitmap& bitmap, const Res_png_9patch* patch,
- float left, float top, float right, float bottom, const SkPaint* paint);
-
// Shapes
- void drawRects(const float* rects, int count, const SkPaint* paint);
- void drawRoundRect(CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top,
+ virtual void drawRoundRect(CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top,
CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom,
CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry,
- CanvasPropertyPaint* paint);
- void drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y,
- CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint);
-
+ CanvasPropertyPaint* paint) override;
+ virtual void drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y,
+ CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) override;
// ----------------------------------------------------------------------------
// HWUI Canvas draw operations - special
// ----------------------------------------------------------------------------
- void drawLayer(DeferredLayerUpdater* layerHandle, float x, float y);
- void drawRenderNode(RenderNode* renderNode);
-
- // TODO: rename for consistency
- void callDrawGLFunction(Functor* functor);
-
- void setHighContrastText(bool highContrastText) {
- mHighContrastText = highContrastText;
- }
+ virtual void drawLayer(DeferredLayerUpdater* layerHandle) override;
+ virtual void drawRenderNode(RenderNode* renderNode) override;
+ virtual void callDrawGLFunction(Functor* functor,
+ GlFunctorLifecycleListener* listener) override;
// ----------------------------------------------------------------------------
// CanvasStateClient interface
@@ -144,19 +116,24 @@ public:
virtual int width() override { return mState.getWidth(); }
virtual int height() override { return mState.getHeight(); }
+ virtual void setHighContrastText(bool highContrastText) override {
+ mHighContrastText = highContrastText;
+ }
+ virtual bool isHighContrastText() override { return mHighContrastText; }
+
// ----------------------------------------------------------------------------
// android/graphics/Canvas state operations
// ----------------------------------------------------------------------------
// Save (layer)
virtual int getSaveCount() const override { return mState.getSaveCount(); }
- virtual int save(SkCanvas::SaveFlags flags) override;
+ virtual int save(SaveFlags::Flags flags) override;
virtual void restore() override;
virtual void restoreToCount(int saveCount) override;
virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
- SkCanvas::SaveFlags flags) override;
+ SaveFlags::Flags flags) override;
virtual int saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, SkCanvas::SaveFlags flags) override {
+ int alpha, SaveFlags::Flags flags) override {
SkPaint paint;
paint.setAlpha(alpha);
return saveLayer(left, top, right, bottom, &paint, flags);
@@ -165,7 +142,6 @@ public:
// Matrix
virtual void getMatrix(SkMatrix* outMatrix) const override { mState.getMatrix(outMatrix); }
virtual void setMatrix(const SkMatrix& matrix) override;
- virtual void setLocalMatrix(const SkMatrix& matrix) override;
virtual void concat(const SkMatrix& matrix) override;
virtual void rotate(float degrees) override;
@@ -205,6 +181,7 @@ public:
}
virtual void drawLines(const float* points, int count, const SkPaint& paint) override;
virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override;
+ virtual void drawRegion(const SkRegion& region, const SkPaint& paint) override;
virtual void drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint& paint) override;
virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) override;
@@ -226,18 +203,20 @@ public:
float dstRight, float dstBottom, const SkPaint* paint) override;
virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
const float* vertices, const int* colors, const SkPaint* paint) override;
+ virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ const SkPaint* paint) override;
+
+ virtual void drawVectorDrawable(VectorDrawableRoot* tree) override;
// Text
- virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
+ virtual void drawGlyphs(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) override;
- virtual void drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) override;
- virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+ virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return false; }
-
private:
CanvasState mState;
@@ -249,11 +228,14 @@ private:
kBarrier_OutOfOrder,
};
+ void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
+ void drawRects(const float* rects, int count, const SkPaint* paint);
+
void flushRestoreToCount();
void flushTranslate();
void flushReorderBarrier();
- LinearAllocator& alloc() { return mDisplayListData->allocator; }
+ LinearAllocator& alloc() { return mDisplayList->allocator; }
// Each method returns final index of op
size_t addOpAndUpdateChunk(DisplayListOp* op);
@@ -270,22 +252,18 @@ private:
inline const T* refBuffer(const T* srcBuffer, int32_t count) {
if (!srcBuffer) return nullptr;
- T* dstBuffer = (T*) mDisplayListData->allocator.alloc(count * sizeof(T));
+ T* dstBuffer = (T*) mDisplayList->allocator.alloc<T>(count * sizeof(T));
memcpy(dstBuffer, srcBuffer, count * sizeof(T));
return dstBuffer;
}
- inline char* refText(const char* text, size_t byteLength) {
- return (char*) refBuffer<uint8_t>((uint8_t*)text, byteLength);
- }
-
inline const SkPath* refPath(const SkPath* path) {
if (!path) return nullptr;
// The points/verbs within the path are refcounted so this copy operation
// is inexpensive and maintains the generationID of the original path.
const SkPath* cachedPath = new SkPath(*path);
- mDisplayListData->pathResources.add(cachedPath);
+ mDisplayList->pathResources.push_back(cachedPath);
return cachedPath;
}
@@ -309,7 +287,7 @@ private:
if (cachedPaint == nullptr || *cachedPaint != *paint) {
cachedPaint = new SkPaint(*paint);
std::unique_ptr<const SkPaint> copy(cachedPaint);
- mDisplayListData->paints.push_back(std::move(copy));
+ mDisplayList->paints.push_back(std::move(copy));
// replaceValueFor() performs an add if the entry doesn't exist
mPaintMap.replaceValueFor(key, cachedPaint);
@@ -319,16 +297,6 @@ private:
return cachedPaint;
}
- inline SkPaint* copyPaint(const SkPaint* paint) {
- if (!paint) return nullptr;
-
- SkPaint* returnPaint = new SkPaint(*paint);
- std::unique_ptr<const SkPaint> copy(returnPaint);
- mDisplayListData->paints.push_back(std::move(copy));
-
- return returnPaint;
- }
-
inline const SkRegion* refRegion(const SkRegion* region) {
if (!region) {
return region;
@@ -339,7 +307,7 @@ private:
if (cachedRegion == nullptr) {
std::unique_ptr<const SkRegion> copy(new SkRegion(*region));
cachedRegion = copy.get();
- mDisplayListData->regions.push_back(std::move(copy));
+ mDisplayList->regions.push_back(std::move(copy));
// replaceValueFor() performs an add if the entry doesn't exist
mRegionMap.replaceValueFor(region, cachedRegion);
@@ -353,14 +321,13 @@ private:
// correctly, such as creating the bitmap from scratch, drawing with it, changing its
// contents, and drawing again. The only fix would be to always copy it the first time,
// which doesn't seem worth the extra cycles for this unlikely case.
- SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap);
- alloc().autoDestroy(localBitmap);
- mDisplayListData->bitmapResources.push_back(localBitmap);
+ SkBitmap* localBitmap = alloc().create<SkBitmap>(bitmap);
+ mDisplayList->bitmapResources.push_back(localBitmap);
return localBitmap;
}
inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) {
- mDisplayListData->patchResources.add(patch);
+ mDisplayList->patchResources.push_back(patch);
mResourceCache.incrementRefcount(patch);
return patch;
}
@@ -370,7 +337,7 @@ private:
DefaultKeyedVector<const SkRegion*, const SkRegion*> mRegionMap;
ResourceCache& mResourceCache;
- DisplayListData* mDisplayListData;
+ DisplayList* mDisplayList;
float mTranslateX;
float mTranslateY;
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 8b4b4ba2b79e..59f073ff593c 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -28,6 +28,7 @@
#include "UvMapper.h"
#include "utils/LinearAllocator.h"
#include "utils/PaintUtils.h"
+#include "VectorDrawable.h"
#include <algorithm>
@@ -64,7 +65,9 @@ public:
static void operator delete(void* ptr) { LOG_ALWAYS_FATAL("delete not supported"); }
static void* operator new(size_t size) = delete; /** PURPOSELY OMITTED **/
static void* operator new(size_t size, LinearAllocator& allocator) {
- return allocator.alloc(size);
+ // FIXME: Quick hack to keep old pipeline working, delete this when
+ // we no longer need to support HWUI_NEWOPS := false
+ return allocator.alloc<char>(size);
}
enum OpLogFlag {
@@ -139,7 +142,7 @@ public:
* reducing which operations are tagged as mergeable.
*/
virtual void multiDraw(OpenGLRenderer& renderer, Rect& dirty,
- const Vector<OpStatePair>& ops, const Rect& bounds) {
+ const std::vector<OpStatePair>& ops, const Rect& bounds) {
for (unsigned int i = 0; i < ops.size(); i++) {
renderer.restoreDisplayState(*(ops[i].state), true);
ops[i].op->applyDraw(renderer, dirty);
@@ -172,10 +175,6 @@ public:
void setQuickRejected(bool quickRejected) { mQuickRejected = quickRejected; }
bool getQuickRejected() { return mQuickRejected; }
- inline int getPaintAlpha() const {
- return OpenGLRenderer::getAlphaDirect(mPaint);
- }
-
virtual bool hasTextShadow() const {
return false;
}
@@ -213,7 +212,7 @@ protected:
if (state.mAlpha != 1.0f) return false;
- SkXfermode::Mode mode = OpenGLRenderer::getXfermodeDirect(mPaint);
+ SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(mPaint);
return (mode == SkXfermode::kSrcOver_Mode ||
mode == SkXfermode::kSrc_Mode);
@@ -249,8 +248,8 @@ public:
virtual bool getLocalBounds(Rect& localBounds) override {
localBounds.set(mLocalBounds);
- OpenGLRenderer::TextShadow textShadow;
- if (OpenGLRenderer::getTextShadow(mPaint, &textShadow)) {
+ PaintUtils::TextShadow textShadow;
+ if (PaintUtils::getTextShadow(mPaint, &textShadow)) {
Rect shadow(mLocalBounds);
shadow.translate(textShadow.dx, textShadow.dx);
shadow.outset(textShadow.radius);
@@ -372,8 +371,8 @@ public:
private:
bool isSaveLayerAlpha() const {
- SkXfermode::Mode mode = OpenGLRenderer::getXfermodeDirect(mPaint);
- int alpha = OpenGLRenderer::getAlphaDirect(mPaint);
+ SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(mPaint);
+ int alpha = PaintUtils::getAlphaDirect(mPaint);
return alpha < 255 && mode == SkXfermode::kSrcOver_Mode;
}
@@ -472,7 +471,9 @@ public:
: mMatrix(matrix) {}
virtual void applyState(OpenGLRenderer& renderer, int saveCount) const override {
- renderer.setMatrix(mMatrix);
+ // Setting a matrix on a Canvas isn't equivalent to setting a total matrix on the scene.
+ // Set a canvas-relative matrix on the renderer instead.
+ renderer.setLocalMatrix(mMatrix);
}
virtual void output(int level, uint32_t logFlags) const override {
@@ -489,25 +490,6 @@ private:
const SkMatrix mMatrix;
};
-class SetLocalMatrixOp : public StateOp {
-public:
- SetLocalMatrixOp(const SkMatrix& matrix)
- : mMatrix(matrix) {}
-
- virtual void applyState(OpenGLRenderer& renderer, int saveCount) const override {
- renderer.setLocalMatrix(mMatrix);
- }
-
- virtual void output(int level, uint32_t logFlags) const override {
- OP_LOG("SetLocalMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(&mMatrix));
- }
-
- virtual const char* name() override { return "SetLocalMatrix"; }
-
-private:
- const SkMatrix mMatrix;
-};
-
class ConcatMatrixOp : public StateOp {
public:
ConcatMatrixOp(const SkMatrix& matrix)
@@ -633,7 +615,7 @@ public:
AssetAtlas::Entry* getAtlasEntry(OpenGLRenderer& renderer) {
if (!mEntryValid) {
mEntryValid = true;
- mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap);
+ mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap->pixelRef());
}
return mEntry;
}
@@ -648,7 +630,7 @@ public:
* the current layer, if any.
*/
virtual void multiDraw(OpenGLRenderer& renderer, Rect& dirty,
- const Vector<OpStatePair>& ops, const Rect& bounds) override {
+ const std::vector<OpStatePair>& ops, const Rect& bounds) override {
const DeferredDisplayState& firstState = *(ops[0].state);
renderer.restoreDisplayState(firstState, true); // restore all but the clip
@@ -708,7 +690,7 @@ public:
// TODO: support clipped bitmaps by handling them in SET_TEXTURE
deferInfo.mergeable = state.mMatrix.isSimple() && state.mMatrix.positiveScale() &&
!state.mClipSideFlags &&
- OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode &&
+ PaintUtils::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode &&
(mBitmap->colorType() != kAlpha_8_SkColorType);
}
@@ -798,7 +780,7 @@ public:
AssetAtlas::Entry* getAtlasEntry(OpenGLRenderer& renderer) {
if (!mEntryValid) {
mEntryValid = true;
- mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap);
+ mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap->pixelRef());
}
return mEntry;
}
@@ -819,7 +801,7 @@ public:
* is also responsible for dirtying the current layer, if any.
*/
virtual void multiDraw(OpenGLRenderer& renderer, Rect& dirty,
- const Vector<OpStatePair>& ops, const Rect& bounds) override {
+ const std::vector<OpStatePair>& ops, const Rect& bounds) override {
const DeferredDisplayState& firstState = *(ops[0].state);
renderer.restoreDisplayState(firstState, true); // restore all but the clip
@@ -912,7 +894,7 @@ public:
deferInfo.batchId = DeferredDisplayList::kOpBatch_Patch;
deferInfo.mergeId = getAtlasEntry(renderer) ? (mergeid_t) mEntry->getMergeId() : (mergeid_t) mBitmap;
deferInfo.mergeable = state.mMatrix.isPureTranslate() &&
- OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode;
+ PaintUtils::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode;
deferInfo.opaqueOverBounds = isOpaqueOverBounds(state) && mBitmap->isOpaque();
}
@@ -1126,6 +1108,30 @@ private:
float* mRadius;
};
+class DrawVectorDrawableOp : public DrawOp {
+public:
+ DrawVectorDrawableOp(VectorDrawableRoot* tree, const SkRect& bounds)
+ : DrawOp(nullptr), mTree(tree), mDst(bounds) {}
+
+ virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override {
+ const SkBitmap& bitmap = mTree->getBitmapUpdateIfDirty();
+ SkPaint* paint = mTree->getPaint();
+ renderer.drawBitmap(&bitmap, Rect(0, 0, bitmap.width(), bitmap.height()),
+ mDst, paint);
+ }
+
+ virtual void output(int level, uint32_t logFlags) const override {
+ OP_LOG("Draw Vector Drawable %p", mTree);
+ }
+
+ virtual const char* name() override { return "DrawVectorDrawable"; }
+
+private:
+ VectorDrawableRoot* mTree;
+ SkRect mDst;
+
+};
+
class DrawOvalOp : public DrawStrokableOp {
public:
DrawOvalOp(float left, float top, float right, float bottom, const SkPaint* paint)
@@ -1250,7 +1256,7 @@ public:
class DrawSomeTextOp : public DrawOp {
public:
- DrawSomeTextOp(const char* text, int bytesCount, int count, const SkPaint* paint)
+ DrawSomeTextOp(const glyph_t* text, int bytesCount, int count, const SkPaint* paint)
: DrawOp(paint), mText(text), mBytesCount(bytesCount), mCount(count) {};
virtual void output(int level, uint32_t logFlags) const override {
@@ -1258,12 +1264,12 @@ public:
}
virtual bool hasTextShadow() const override {
- return OpenGLRenderer::hasTextShadow(mPaint);
+ return PaintUtils::hasTextShadow(mPaint);
}
virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
const DeferredDisplayState& state) override {
- FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(mPaint);
+ FontRenderer& fontRenderer = renderer.getCaches().fontRenderer.getFontRenderer();
fontRenderer.precache(mPaint, mText, mCount, SkMatrix::I());
deferInfo.batchId = mPaint->getColor() == SK_ColorBLACK ?
@@ -1272,14 +1278,14 @@ public:
}
protected:
- const char* mText;
+ const glyph_t* mText;
int mBytesCount;
int mCount;
};
class DrawTextOnPathOp : public DrawSomeTextOp {
public:
- DrawTextOnPathOp(const char* text, int bytesCount, int count,
+ DrawTextOnPathOp(const glyph_t* text, int bytesCount, int count,
const SkPath* path, float hOffset, float vOffset, const SkPaint* paint)
: DrawSomeTextOp(text, bytesCount, count, paint),
mPath(path), mHOffset(hOffset), mVOffset(vOffset) {
@@ -1299,27 +1305,9 @@ private:
float mVOffset;
};
-class DrawPosTextOp : public DrawSomeTextOp {
-public:
- DrawPosTextOp(const char* text, int bytesCount, int count,
- const float* positions, const SkPaint* paint)
- : DrawSomeTextOp(text, bytesCount, count, paint), mPositions(positions) {
- /* TODO: inherit from DrawBounded and init mLocalBounds */
- }
-
- virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override {
- renderer.drawPosText(mText, mBytesCount, mCount, mPositions, mPaint);
- }
-
- virtual const char* name() override { return "DrawPosText"; }
-
-private:
- const float* mPositions;
-};
-
class DrawTextOp : public DrawStrokableOp {
public:
- DrawTextOp(const char* text, int bytesCount, int count, float x, float y,
+ DrawTextOp(const glyph_t* text, int bytesCount, int count, float x, float y,
const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds)
: DrawStrokableOp(bounds, paint), mText(text), mBytesCount(bytesCount), mCount(count),
mX(x), mY(y), mPositions(positions), mTotalAdvance(totalAdvance) {
@@ -1328,7 +1316,7 @@ public:
virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
const DeferredDisplayState& state) override {
- FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(mPaint);
+ FontRenderer& fontRenderer = renderer.getCaches().fontRenderer.getFontRenderer();
SkMatrix transform;
renderer.findBestFontTransform(state.mMatrix, &transform);
if (mPrecacheTransform != transform) {
@@ -1347,7 +1335,7 @@ public:
deferInfo.mergeable = state.mMatrix.isPureTranslate()
&& !hasDecorations
- && OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode;
+ && PaintUtils::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode;
}
virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override {
@@ -1358,7 +1346,7 @@ public:
}
virtual void multiDraw(OpenGLRenderer& renderer, Rect& dirty,
- const Vector<OpStatePair>& ops, const Rect& bounds) override {
+ const std::vector<OpStatePair>& ops, const Rect& bounds) override {
for (unsigned int i = 0; i < ops.size(); i++) {
const DeferredDisplayState& state = *(ops[i].state);
DrawOpMode drawOpMode = (i == ops.size() - 1) ? DrawOpMode::kFlush : DrawOpMode::kDefer;
@@ -1380,7 +1368,7 @@ public:
virtual const char* name() override { return "DrawText"; }
private:
- const char* mText;
+ const glyph_t* mText;
int mBytesCount;
int mCount;
float mX;
@@ -1417,26 +1405,31 @@ private:
class DrawRenderNodeOp : public DrawBoundedOp {
friend class RenderNode; // grant RenderNode access to info of child
- friend class DisplayListData; // grant DisplayListData access to info of child
+ friend class DisplayList; // grant DisplayList access to info of child
+ friend class DisplayListCanvas;
+ friend class TestUtils;
public:
DrawRenderNodeOp(RenderNode* renderNode, const mat4& transformFromParent, bool clipIsSimple)
- : DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr)
- , mRenderNode(renderNode)
+ : DrawBoundedOp(0, 0,
+ renderNode->stagingProperties().getWidth(),
+ renderNode->stagingProperties().getHeight(),
+ nullptr)
+ , renderNode(renderNode)
, mRecordedWithPotentialStencilClip(!clipIsSimple || !transformFromParent.isSimple())
- , mTransformFromParent(transformFromParent)
- , mSkipInOrderDraw(false) {}
+ , localMatrix(transformFromParent)
+ , skipInOrderDraw(false) {}
virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
bool useQuickReject) override {
- if (mRenderNode->isRenderable() && !mSkipInOrderDraw) {
- mRenderNode->defer(deferStruct, level + 1);
+ if (renderNode->isRenderable() && !skipInOrderDraw) {
+ renderNode->defer(deferStruct, level + 1);
}
}
virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level,
bool useQuickReject) override {
- if (mRenderNode->isRenderable() && !mSkipInOrderDraw) {
- mRenderNode->replay(replayStruct, level + 1);
+ if (renderNode->isRenderable() && !skipInOrderDraw) {
+ renderNode->replay(replayStruct, level + 1);
}
}
@@ -1445,18 +1438,16 @@ public:
}
virtual void output(int level, uint32_t logFlags) const override {
- OP_LOG("Draw RenderNode %p %s", mRenderNode, mRenderNode->getName());
- if (mRenderNode && (logFlags & kOpLogFlag_Recurse)) {
- mRenderNode->output(level + 1);
+ OP_LOG("Draw RenderNode %p %s", renderNode, renderNode->getName());
+ if (renderNode && (logFlags & kOpLogFlag_Recurse)) {
+ renderNode->output(level + 1);
}
}
virtual const char* name() override { return "DrawRenderNode"; }
- RenderNode* renderNode() { return mRenderNode; }
-
private:
- RenderNode* mRenderNode;
+ RenderNode* renderNode;
/**
* This RenderNode was drawn into a DisplayList with the canvas in a state that will likely
@@ -1478,7 +1469,7 @@ private:
/**
* Records transform vs parent, used for computing total transform without rerunning DL contents
*/
- const mat4 mTransformFromParent;
+ const mat4 localMatrix;
/**
* Holds the transformation between the projection surface ViewGroup and this RenderNode
@@ -1488,8 +1479,8 @@ private:
*
* Note: doesn't include transformation within the RenderNode, or its properties.
*/
- mat4 mTransformFromCompositingAncestor;
- bool mSkipInOrderDraw;
+ mat4 transformFromCompositingAncestor;
+ bool skipInOrderDraw;
};
/**
@@ -1541,23 +1532,21 @@ private:
class DrawLayerOp : public DrawOp {
public:
- DrawLayerOp(Layer* layer, float x, float y)
- : DrawOp(nullptr), mLayer(layer), mX(x), mY(y) {}
+ DrawLayerOp(Layer* layer)
+ : DrawOp(nullptr), mLayer(layer) {}
virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override {
- renderer.drawLayer(mLayer, mX, mY);
+ renderer.drawLayer(mLayer);
}
virtual void output(int level, uint32_t logFlags) const override {
- OP_LOG("Draw Layer %p at %f %f", mLayer, mX, mY);
+ OP_LOG("Draw Layer %p", mLayer);
}
virtual const char* name() override { return "DrawLayer"; }
private:
Layer* mLayer;
- float mX;
- float mY;
};
}; // namespace uirenderer
diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp
index 1ba651174c8a..ec2013e27401 100644
--- a/libs/hwui/Dither.cpp
+++ b/libs/hwui/Dither.cpp
@@ -54,7 +54,6 @@ void Dither::bindDitherTexture() {
15 * dither, 7 * dither, 13 * dither, 5 * dither
};
- glPixelStorei(GL_UNPACK_ALIGNMENT, sizeof(GLfloat));
glTexImage2D(GL_TEXTURE_2D, 0, GL_R16F, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0,
GL_RED, GL_FLOAT, &pattern);
} else {
@@ -65,7 +64,6 @@ void Dither::bindDitherTexture() {
15, 7, 13, 5
};
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0,
GL_ALPHA, GL_UNSIGNED_BYTE, &pattern);
}
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 2a82216eb145..02caaa49e99c 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -14,29 +14,19 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include "Extensions.h"
#include "Debug.h"
#include "Properties.h"
+#include "utils/StringUtils.h"
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <utils/Log.h>
namespace android {
-
-using namespace uirenderer;
-ANDROID_SINGLETON_STATIC_INSTANCE(Extensions);
-
namespace uirenderer {
-///////////////////////////////////////////////////////////////////////////////
-// Defines
-///////////////////////////////////////////////////////////////////////////////
-
// Debug
#if DEBUG_EXTENSIONS
#define EXT_LOGD(...) ALOGD(__VA_ARGS__)
@@ -44,30 +34,16 @@ namespace uirenderer {
#define EXT_LOGD(...)
#endif
-///////////////////////////////////////////////////////////////////////////////
-// Constructors
-///////////////////////////////////////////////////////////////////////////////
Extensions::Extensions() {
- // Query GL extensions
- findExtensions((const char*) glGetString(GL_EXTENSIONS), mGlExtensionList);
- mHasNPot = hasGlExtension("GL_OES_texture_npot");
- mHasFramebufferFetch = hasGlExtension("GL_NV_shader_framebuffer_fetch");
- mHasDiscardFramebuffer = hasGlExtension("GL_EXT_discard_framebuffer");
- mHasDebugMarker = hasGlExtension("GL_EXT_debug_marker");
- mHasTiledRendering = hasGlExtension("GL_QCOM_tiled_rendering");
- mHas1BitStencil = hasGlExtension("GL_OES_stencil1");
- mHas4BitStencil = hasGlExtension("GL_OES_stencil4");
-
- // Query EGL extensions
- findExtensions(eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS), mEglExtensionList);
-
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_DEBUG_NV_PROFILING, property, nullptr) > 0) {
- mHasNvSystemTime = !strcmp(property, "true") && hasEglExtension("EGL_NV_system_time");
- } else {
- mHasNvSystemTime = false;
- }
+ auto extensions = StringUtils::split((const char*) glGetString(GL_EXTENSIONS));
+ mHasNPot = extensions.has("GL_OES_texture_npot");
+ mHasFramebufferFetch = extensions.has("GL_NV_shader_framebuffer_fetch");
+ mHasDiscardFramebuffer = extensions.has("GL_EXT_discard_framebuffer");
+ mHasDebugMarker = extensions.has("GL_EXT_debug_marker");
+ mHas1BitStencil = extensions.has("GL_OES_stencil1");
+ mHas4BitStencil = extensions.has("GL_OES_stencil4");
+ mHasUnpackSubImage = extensions.has("GL_EXT_unpack_subimage");
const char* version = (const char*) glGetString(GL_VERSION);
@@ -90,40 +66,5 @@ Extensions::Extensions() {
}
}
-///////////////////////////////////////////////////////////////////////////////
-// Methods
-///////////////////////////////////////////////////////////////////////////////
-
-bool Extensions::hasGlExtension(const char* extension) const {
- const String8 s(extension);
- return mGlExtensionList.indexOf(s) >= 0;
-}
-
-bool Extensions::hasEglExtension(const char* extension) const {
- const String8 s(extension);
- return mEglExtensionList.indexOf(s) >= 0;
-}
-
-void Extensions::findExtensions(const char* extensions, SortedVector<String8>& list) const {
- const char* current = extensions;
- const char* head = current;
- EXT_LOGD("Available extensions:");
- do {
- head = strchr(current, ' ');
- String8 s(current, head ? head - current : strlen(current));
- if (s.length()) {
- list.add(s);
- EXT_LOGD(" %s", s.string());
- }
- current = head + 1;
- } while (head);
-}
-
-void Extensions::dump() const {
- ALOGD("%s", (const char*) glGetString(GL_VERSION));
- ALOGD("Supported GL extensions:\n%s", (const char*) glGetString(GL_EXTENSIONS));
- ALOGD("Supported EGL extensions:\n%s", eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS));
-}
-
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index e7d317d21941..67cc747015e0 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -19,11 +19,8 @@
#include <cutils/compiler.h>
-#include <utils/Singleton.h>
-#include <utils/SortedVector.h>
-#include <utils/String8.h>
-
-#include <GLES2/gl2.h>
+#include <string>
+#include <unordered_set>
namespace android {
namespace uirenderer {
@@ -32,7 +29,7 @@ namespace uirenderer {
// Classes
///////////////////////////////////////////////////////////////////////////////
-class ANDROID_API Extensions {
+class Extensions {
public:
Extensions();
@@ -40,11 +37,9 @@ public:
inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
inline bool hasDiscardFramebuffer() const { return mHasDiscardFramebuffer; }
inline bool hasDebugMarker() const { return mHasDebugMarker; }
- inline bool hasTiledRendering() const { return mHasTiledRendering; }
inline bool has1BitStencil() const { return mHas1BitStencil; }
inline bool has4BitStencil() const { return mHas4BitStencil; }
- inline bool hasNvSystemTime() const { return mHasNvSystemTime; }
- inline bool hasUnpackRowLength() const { return mVersionMajor >= 3; }
+ inline bool hasUnpackRowLength() const { return mVersionMajor >= 3 || mHasUnpackSubImage; }
inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; }
inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; }
inline bool hasFloatTextures() const { return mVersionMajor >= 3; }
@@ -52,25 +47,14 @@ public:
inline int getMajorGlVersion() const { return mVersionMajor; }
inline int getMinorGlVersion() const { return mVersionMinor; }
- bool hasGlExtension(const char* extension) const;
- bool hasEglExtension(const char* extension) const;
-
- void dump() const;
-
private:
- void findExtensions(const char* extensions, SortedVector<String8>& list) const;
-
- SortedVector<String8> mGlExtensionList;
- SortedVector<String8> mEglExtensionList;
-
bool mHasNPot;
bool mHasFramebufferFetch;
bool mHasDiscardFramebuffer;
bool mHasDebugMarker;
- bool mHasTiledRendering;
bool mHas1BitStencil;
bool mHas4BitStencil;
- bool mHasNvSystemTime;
+ bool mHasUnpackSubImage;
int mVersionMajor;
int mVersionMinor;
diff --git a/libs/hwui/FboCache.cpp b/libs/hwui/FboCache.cpp
index b54d53233a65..b2181b60054f 100644
--- a/libs/hwui/FboCache.cpp
+++ b/libs/hwui/FboCache.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <stdlib.h>
#include "Debug.h"
@@ -29,15 +27,8 @@ namespace uirenderer {
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
-FboCache::FboCache(): mMaxSize(DEFAULT_FBO_CACHE_SIZE) {
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_FBO_CACHE_SIZE, property, nullptr) > 0) {
- INIT_LOGD(" Setting fbo cache size to %s", property);
- mMaxSize = atoi(property);
- } else {
- INIT_LOGD(" Using default fbo cache size of %d", DEFAULT_FBO_CACHE_SIZE);
- }
-}
+FboCache::FboCache()
+ : mMaxSize(Properties::fboCacheSize) {}
FboCache::~FboCache() {
clear();
diff --git a/libs/hwui/FloatColor.h b/libs/hwui/FloatColor.h
index 97dec88e709b..9a39ec28aa3d 100644
--- a/libs/hwui/FloatColor.h
+++ b/libs/hwui/FloatColor.h
@@ -17,6 +17,7 @@
#define FLOATCOLOR_H
#include "utils/Macros.h"
+#include "utils/MathUtils.h"
#include <stdint.h>
@@ -38,6 +39,17 @@ struct FloatColor {
|| b > 0.0f;
}
+ bool operator==(const FloatColor& other) const {
+ return MathUtils::areEqual(r, other.r)
+ && MathUtils::areEqual(g, other.g)
+ && MathUtils::areEqual(b, other.b)
+ && MathUtils::areEqual(a, other.a);
+ }
+
+ bool operator!=(const FloatColor& other) const {
+ return !(*this == other);
+ }
+
float r;
float g;
float b;
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 35051b7cccea..276c18d0d3f9 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -21,19 +21,25 @@
#include "Extensions.h"
#include "Glop.h"
#include "GlopBuilder.h"
-#include "OpenGLRenderer.h"
#include "PixelBuffer.h"
#include "Rect.h"
#include "renderstate/RenderState.h"
#include "utils/Blur.h"
-#include "utils/MathUtils.h"
#include "utils/Timing.h"
-#include <SkGlyph.h>
-#include <SkUtils.h>
-#include <cutils/properties.h>
+#if HWUI_NEW_OPS
+#include "BakedOpDispatcher.h"
+#include "BakedOpRenderer.h"
+#include "BakedOpState.h"
+#else
+#include "OpenGLRenderer.h"
+#endif
+#include <algorithm>
+#include <cutils/properties.h>
+#include <SkGlyph.h>
+#include <SkUtils.h>
#include <utils/Log.h>
#ifdef ANDROID_ENABLE_RENDERSCRIPT
@@ -61,14 +67,26 @@ void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) {
int transformFlags = pureTranslate
? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None;
Glop glop;
+#if HWUI_NEW_OPS
+ GlopBuilder(renderer->renderState(), renderer->caches(), &glop)
+ .setRoundRectClipState(bakedState->roundRectClipState)
+ .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
+ .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha)
+ .setTransform(bakedState->computedState.transform, transformFlags)
+ .setModelViewIdentityEmptyBounds()
+ .build();
+ // Note: don't pass dirty bounds here, so user must manage passing dirty bounds to renderer
+ renderer->renderGlop(nullptr, clip, glop);
+#else
GlopBuilder(renderer->mRenderState, renderer->mCaches, &glop)
+ .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState)
.setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
.setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, renderer->currentSnapshot()->alpha)
.setTransform(*(renderer->currentSnapshot()), transformFlags)
- .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
- .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState)
+ .setModelViewOffsetRect(0, 0, Rect())
.build();
renderer->renderGlop(glop);
+#endif
}
///////////////////////////////////////////////////////////////////////////////
@@ -77,8 +95,8 @@ void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) {
static bool sLogFontRendererCreate = true;
-FontRenderer::FontRenderer()
- : mGammaTable(nullptr)
+FontRenderer::FontRenderer(const uint8_t* gammaTable)
+ : mGammaTable(gammaTable)
, mCurrentFont(nullptr)
, mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity)
, mCurrentCacheTexture(nullptr)
@@ -94,34 +112,22 @@ FontRenderer::FontRenderer()
INIT_LOGD("Creating FontRenderer");
}
- mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH;
- mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT;
- mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH;
- mLargeCacheHeight = DEFAULT_TEXT_LARGE_CACHE_HEIGHT;
-
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_TEXT_SMALL_CACHE_WIDTH, property, nullptr) > 0) {
- mSmallCacheWidth = atoi(property);
- }
+ mSmallCacheWidth = property_get_int32(PROPERTY_TEXT_SMALL_CACHE_WIDTH,
+ DEFAULT_TEXT_SMALL_CACHE_WIDTH);
+ mSmallCacheHeight = property_get_int32(PROPERTY_TEXT_SMALL_CACHE_HEIGHT,
+ DEFAULT_TEXT_SMALL_CACHE_HEIGHT);
- if (property_get(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, property, nullptr) > 0) {
- mSmallCacheHeight = atoi(property);
- }
-
- if (property_get(PROPERTY_TEXT_LARGE_CACHE_WIDTH, property, nullptr) > 0) {
- mLargeCacheWidth = atoi(property);
- }
-
- if (property_get(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, property, nullptr) > 0) {
- mLargeCacheHeight = atoi(property);
- }
+ mLargeCacheWidth = property_get_int32(PROPERTY_TEXT_LARGE_CACHE_WIDTH,
+ DEFAULT_TEXT_LARGE_CACHE_WIDTH);
+ mLargeCacheHeight = property_get_int32(PROPERTY_TEXT_LARGE_CACHE_HEIGHT,
+ DEFAULT_TEXT_LARGE_CACHE_HEIGHT);
uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize;
- mSmallCacheWidth = MathUtils::min(mSmallCacheWidth, maxTextureSize);
- mSmallCacheHeight = MathUtils::min(mSmallCacheHeight, maxTextureSize);
- mLargeCacheWidth = MathUtils::min(mLargeCacheWidth, maxTextureSize);
- mLargeCacheHeight = MathUtils::min(mLargeCacheHeight, maxTextureSize);
+ mSmallCacheWidth = std::min(mSmallCacheWidth, maxTextureSize);
+ mSmallCacheHeight = std::min(mSmallCacheHeight, maxTextureSize);
+ mLargeCacheWidth = std::min(mLargeCacheWidth, maxTextureSize);
+ mLargeCacheHeight = std::min(mLargeCacheHeight, maxTextureSize);
if (sLogFontRendererCreate) {
INIT_LOGD(" Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i",
@@ -134,7 +140,7 @@ FontRenderer::FontRenderer()
sLogFontRendererCreate = false;
}
-void clearCacheTextures(Vector<CacheTexture*>& cacheTextures) {
+void clearCacheTextures(std::vector<CacheTexture*>& cacheTextures) {
for (uint32_t i = 0; i < cacheTextures.size(); i++) {
delete cacheTextures[i];
}
@@ -171,7 +177,7 @@ void FontRenderer::flushAllAndInvalidate() {
mDrawn = false;
}
-void FontRenderer::flushLargeCaches(Vector<CacheTexture*>& cacheTextures) {
+void FontRenderer::flushLargeCaches(std::vector<CacheTexture*>& cacheTextures) {
// Start from 1; don't deallocate smallest/default texture
for (uint32_t i = 1; i < cacheTextures.size(); i++) {
CacheTexture* cacheTexture = cacheTextures[i];
@@ -191,7 +197,7 @@ void FontRenderer::flushLargeCaches() {
flushLargeCaches(mRGBACacheTextures);
}
-CacheTexture* FontRenderer::cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures,
+CacheTexture* FontRenderer::cacheBitmapInTexture(std::vector<CacheTexture*>& cacheTextures,
const SkGlyph& glyph, uint32_t* startX, uint32_t* startY) {
for (uint32_t i = 0; i < cacheTextures.size(); i++) {
if (cacheTextures[i]->fitBitmap(glyph, startX, startY)) {
@@ -218,7 +224,7 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp
// choose an appropriate cache texture list for this glyph format
SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
- Vector<CacheTexture*>* cacheTextures = nullptr;
+ std::vector<CacheTexture*>* cacheTextures = nullptr;
switch (format) {
case SkMask::kA8_Format:
case SkMask::kBW_Format:
@@ -399,17 +405,17 @@ void FontRenderer::initTextTexture() {
clearCacheTextures(mRGBACacheTextures);
mUploadTexture = false;
- mACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight,
+ mACacheTextures.push_back(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight,
GL_ALPHA, true));
- mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
+ mACacheTextures.push_back(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
GL_ALPHA, false));
- mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
+ mACacheTextures.push_back(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
GL_ALPHA, false));
- mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight,
+ mACacheTextures.push_back(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight,
GL_ALPHA, false));
- mRGBACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight,
+ mRGBACacheTextures.push_back(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight,
GL_RGBA, false));
- mRGBACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
+ mRGBACacheTextures.push_back(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1,
GL_RGBA, false));
mCurrentCacheTexture = mACacheTextures[0];
}
@@ -425,7 +431,7 @@ void FontRenderer::checkInit() {
mInitialized = true;
}
-void checkTextureUpdateForCache(Caches& caches, Vector<CacheTexture*>& cacheTextures,
+void checkTextureUpdateForCache(Caches& caches, std::vector<CacheTexture*>& cacheTextures,
bool& resetPixelStore, GLuint& lastTextureId) {
for (uint32_t i = 0; i < cacheTextures.size(); i++) {
CacheTexture* cacheTexture = cacheTextures[i];
@@ -452,7 +458,6 @@ void FontRenderer::checkTextureUpdate() {
GLuint lastTextureId = 0;
bool resetPixelStore = false;
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Iterate over all the cache textures and see which ones need to be updated
checkTextureUpdateForCache(caches, mACacheTextures, resetPixelStore, lastTextureId);
@@ -470,11 +475,10 @@ void FontRenderer::checkTextureUpdate() {
mUploadTexture = false;
}
-void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) {
+void FontRenderer::issueDrawCommand(std::vector<CacheTexture*>& cacheTextures) {
if (!mFunctor) return;
bool first = true;
- bool forceRebind = false;
for (uint32_t i = 0; i < cacheTextures.size(); i++) {
CacheTexture* texture = cacheTextures[i];
if (texture->canDraw()) {
@@ -487,7 +491,6 @@ void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) {
mFunctor->draw(*texture, mLinearFiltering);
texture->resetMesh();
- forceRebind = false;
}
}
}
@@ -554,8 +557,8 @@ void FontRenderer::setFont(const SkPaint* paint, const SkMatrix& matrix) {
mCurrentFont = Font::create(this, paint, matrix);
}
-FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, float radius, const float* positions) {
+FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const glyph_t *glyphs,
+ int numGlyphs, float radius, const float* positions) {
checkInit();
DropShadow image;
@@ -574,7 +577,7 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, co
mBounds = nullptr;
Rect bounds;
- mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
+ mCurrentFont->measure(paint, glyphs, numGlyphs, &bounds, positions);
uint32_t intRadius = Blur::convertRadiusToInt(radius);
uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * intRadius;
@@ -606,7 +609,7 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, co
// text has non-whitespace, so draw and blur to create the shadow
// NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted
// TODO: don't draw pure whitespace in the first place, and avoid needing this check
- mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
+ mCurrentFont->render(paint, glyphs, numGlyphs, penX, penY,
Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, nullptr, positions);
// Unbind any PBO we might have used
@@ -640,26 +643,26 @@ void FontRenderer::finishRender() {
issueDrawCommand();
}
-void FontRenderer::precache(const SkPaint* paint, const char* text, int numGlyphs,
+void FontRenderer::precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs,
const SkMatrix& matrix) {
Font* font = Font::create(this, paint, matrix);
- font->precache(paint, text, numGlyphs);
+ font->precache(paint, glyphs, numGlyphs);
}
void FontRenderer::endPrecaching() {
checkTextureUpdate();
}
-bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
- const float* positions, Rect* bounds, TextDrawFunctor* functor, bool forceFinish) {
+bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs,
+ int numGlyphs, int x, int y, const float* positions,
+ Rect* bounds, TextDrawFunctor* functor, bool forceFinish) {
if (!mCurrentFont) {
ALOGE("No font set");
return false;
}
initRender(clip, bounds, functor);
- mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
+ mCurrentFont->render(paint, glyphs, numGlyphs, x, y, positions);
if (forceFinish) {
finishRender();
@@ -668,33 +671,25 @@ bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const c
return mDrawn;
}
-bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path,
- float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor) {
+bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs,
+ int numGlyphs, const SkPath* path, float hOffset, float vOffset,
+ Rect* bounds, TextDrawFunctor* functor) {
if (!mCurrentFont) {
ALOGE("No font set");
return false;
}
initRender(clip, bounds, functor);
- mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
+ mCurrentFont->render(paint, glyphs, numGlyphs, path, hOffset, vOffset);
finishRender();
return mDrawn;
}
-void FontRenderer::removeFont(const Font* font) {
- mActiveFonts.remove(font->getDescription());
-
- if (mCurrentFont == font) {
- mCurrentFont = nullptr;
- }
-}
-
void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, float radius) {
uint32_t intRadius = Blur::convertRadiusToInt(radius);
#ifdef ANDROID_ENABLE_RENDERSCRIPT
- if (width * height * intRadius >= RS_MIN_INPUT_CUTOFF) {
+ if (width * height * intRadius >= RS_MIN_INPUT_CUTOFF && radius <= 25.0f) {
uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height);
if (mRs == nullptr) {
@@ -734,14 +729,14 @@ void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, flo
#endif
std::unique_ptr<float[]> gaussian(new float[2 * intRadius + 1]);
- Blur::generateGaussianWeights(gaussian.get(), intRadius);
+ Blur::generateGaussianWeights(gaussian.get(), radius);
std::unique_ptr<uint8_t[]> scratch(new uint8_t[width * height]);
Blur::horizontal(gaussian.get(), intRadius, *image, scratch.get(), width, height);
Blur::vertical(gaussian.get(), intRadius, scratch.get(), *image, width, height);
}
-static uint32_t calculateCacheSize(const Vector<CacheTexture*>& cacheTextures) {
+static uint32_t calculateCacheSize(const std::vector<CacheTexture*>& cacheTextures) {
uint32_t size = 0;
for (uint32_t i = 0; i < cacheTextures.size(); i++) {
CacheTexture* cacheTexture = cacheTextures[i];
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index dfb107c99bc5..e10a81b8ccd8 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -21,16 +21,16 @@
#include "font/CacheTexture.h"
#include "font/CachedGlyphInfo.h"
#include "font/Font.h"
-#include "utils/SortedList.h"
#include <utils/LruCache.h>
-#include <utils/Vector.h>
#include <utils/StrongPointer.h>
#include <SkPaint.h>
#include <GLES2/gl2.h>
+#include <vector>
+
#ifdef ANDROID_ENABLE_RENDERSCRIPT
#include "RenderScript.h"
namespace RSC {
@@ -44,13 +44,31 @@ namespace RSC {
namespace android {
namespace uirenderer {
+#if HWUI_NEW_OPS
+class BakedOpState;
+class BakedOpRenderer;
+struct ClipBase;
+#else
class OpenGLRenderer;
+#endif
class TextDrawFunctor {
public:
- TextDrawFunctor(OpenGLRenderer* renderer, float x, float y, bool pureTranslate,
+ TextDrawFunctor(
+#if HWUI_NEW_OPS
+ BakedOpRenderer* renderer,
+ const BakedOpState* bakedState,
+ const ClipBase* clip,
+#else
+ OpenGLRenderer* renderer,
+#endif
+ float x, float y, bool pureTranslate,
int alpha, SkXfermode::Mode mode, const SkPaint* paint)
: renderer(renderer)
+#if HWUI_NEW_OPS
+ , bakedState(bakedState)
+ , clip(clip)
+#endif
, x(x)
, y(y)
, pureTranslate(pureTranslate)
@@ -61,7 +79,13 @@ public:
void draw(CacheTexture& texture, bool linearFiltering);
+#if HWUI_NEW_OPS
+ BakedOpRenderer* renderer;
+ const BakedOpState* bakedState;
+ const ClipBase* clip;
+#else
OpenGLRenderer* renderer;
+#endif
float x;
float y;
bool pureTranslate;
@@ -72,30 +96,24 @@ public:
class FontRenderer {
public:
- FontRenderer();
+ FontRenderer(const uint8_t* gammaTable);
~FontRenderer();
- void flushLargeCaches(Vector<CacheTexture*>& cacheTextures);
+ void flushLargeCaches(std::vector<CacheTexture*>& cacheTextures);
void flushLargeCaches();
- void setGammaTable(const uint8_t* gammaTable) {
- mGammaTable = gammaTable;
- }
-
void setFont(const SkPaint* paint, const SkMatrix& matrix);
- void precache(const SkPaint* paint, const char* text, int numGlyphs, const SkMatrix& matrix);
+ void precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, const SkMatrix& matrix);
void endPrecaching();
- // bounds is an out parameter
- bool renderPosText(const SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, const float* positions,
- Rect* bounds, TextDrawFunctor* functor, bool forceFinish = true);
+ bool renderPosText(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs,
+ int numGlyphs, int x, int y, const float* positions,
+ Rect* outBounds, TextDrawFunctor* functor, bool forceFinish = true);
- // bounds is an out parameter
- bool renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path,
- float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor);
+ bool renderTextOnPath(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs,
+ int numGlyphs, const SkPath* path,
+ float hOffset, float vOffset, Rect* outBounds, TextDrawFunctor* functor);
struct DropShadow {
uint32_t width;
@@ -107,8 +125,8 @@ public:
// After renderDropShadow returns, the called owns the memory in DropShadow.image
// and is responsible for releasing it when it's done with it
- DropShadow renderDropShadow(const SkPaint* paint, const char *text, uint32_t startIndex,
- uint32_t len, int numGlyphs, float radius, const float* positions);
+ DropShadow renderDropShadow(const SkPaint* paint, const glyph_t *glyphs, int numGlyphs,
+ float radius, const float* positions);
void setTextureFiltering(bool linearFiltering) {
mLinearFiltering = linearFiltering;
@@ -127,7 +145,7 @@ private:
CacheTexture* createCacheTexture(int width, int height, GLenum format, bool allocate);
void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
uint32_t *retOriginX, uint32_t *retOriginY, bool precaching);
- CacheTexture* cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures, const SkGlyph& glyph,
+ CacheTexture* cacheBitmapInTexture(std::vector<CacheTexture*>& cacheTextures, const SkGlyph& glyph,
uint32_t* startX, uint32_t* startY);
void flushAllAndInvalidate();
@@ -136,7 +154,7 @@ private:
void initRender(const Rect* clip, Rect* bounds, TextDrawFunctor* functor);
void finishRender();
- void issueDrawCommand(Vector<CacheTexture*>& cacheTextures);
+ void issueDrawCommand(std::vector<CacheTexture*>& cacheTextures);
void issueDrawCommand();
void appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
float x2, float y2, float u2, float v2,
@@ -151,8 +169,6 @@ private:
float x3, float y3, float u3, float v3,
float x4, float y4, float u4, float v4, CacheTexture* texture);
- void removeFont(const Font* font);
-
void checkTextureUpdate();
void setTextureDirty() {
@@ -164,8 +180,8 @@ private:
uint32_t mLargeCacheWidth;
uint32_t mLargeCacheHeight;
- Vector<CacheTexture*> mACacheTextures;
- Vector<CacheTexture*> mRGBACacheTextures;
+ std::vector<CacheTexture*> mACacheTextures;
+ std::vector<CacheTexture*> mRGBACacheTextures;
Font* mCurrentFont;
LruCache<Font::FontDescription, Font*> mActiveFonts;
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
new file mode 100644
index 000000000000..502f027029a7
--- /dev/null
+++ b/libs/hwui/FrameBuilder.cpp
@@ -0,0 +1,954 @@
+/*
+ * Copyright (C) 2016 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 "FrameBuilder.h"
+
+#include "LayerUpdateQueue.h"
+#include "RenderNode.h"
+#include "VectorDrawable.h"
+#include "renderstate/OffscreenBufferPool.h"
+#include "hwui/Canvas.h"
+#include "utils/FatVector.h"
+#include "utils/PaintUtils.h"
+#include "utils/TraceUtils.h"
+
+#include <SkPathOps.h>
+#include <utils/TypeHelpers.h>
+
+namespace android {
+namespace uirenderer {
+
+FrameBuilder::FrameBuilder(const SkRect& clip,
+ uint32_t viewportWidth, uint32_t viewportHeight,
+ const LightGeometry& lightGeometry, Caches& caches)
+ : mStdAllocator(mAllocator)
+ , mLayerBuilders(mStdAllocator)
+ , mLayerStack(mStdAllocator)
+ , mCanvasState(*this)
+ , mCaches(caches)
+ , mLightRadius(lightGeometry.radius)
+ , mDrawFbo0(true) {
+
+ // Prepare to defer Fbo0
+ auto fbo0 = mAllocator.create<LayerBuilder>(viewportWidth, viewportHeight, Rect(clip));
+ mLayerBuilders.push_back(fbo0);
+ mLayerStack.push_back(0);
+ mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
+ clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
+ lightGeometry.center);
+}
+
+FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers,
+ const LightGeometry& lightGeometry, Caches& caches)
+ : mStdAllocator(mAllocator)
+ , mLayerBuilders(mStdAllocator)
+ , mLayerStack(mStdAllocator)
+ , mCanvasState(*this)
+ , mCaches(caches)
+ , mLightRadius(lightGeometry.radius)
+ , mDrawFbo0(false) {
+ // TODO: remove, with each layer on its own save stack
+
+ // Prepare to defer Fbo0 (which will be empty)
+ auto fbo0 = mAllocator.create<LayerBuilder>(1, 1, Rect(1, 1));
+ mLayerBuilders.push_back(fbo0);
+ mLayerStack.push_back(0);
+ mCanvasState.initializeSaveStack(1, 1,
+ 0, 0, 1, 1,
+ lightGeometry.center);
+
+ deferLayers(layers);
+}
+
+void FrameBuilder::deferLayers(const LayerUpdateQueue& layers) {
+ // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
+ // updated in the order they're passed in (mLayerBuilders are issued to Renderer in reverse)
+ for (int i = layers.entries().size() - 1; i >= 0; i--) {
+ RenderNode* layerNode = layers.entries()[i].renderNode;
+ // only schedule repaint if node still on layer - possible it may have been
+ // removed during a dropped frame, but layers may still remain scheduled so
+ // as not to lose info on what portion is damaged
+ if (CC_LIKELY(layerNode->getLayer() != nullptr)) {
+ const Rect& layerDamage = layers.entries()[i].damage;
+ layerNode->computeOrdering();
+
+ // map current light center into RenderNode's coordinate space
+ Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter();
+ layerNode->getLayer()->inverseTransformInWindow.mapPoint3d(lightCenter);
+
+ saveForLayer(layerNode->getWidth(), layerNode->getHeight(), 0, 0,
+ layerDamage, lightCenter, nullptr, layerNode);
+
+ if (layerNode->getDisplayList()) {
+ deferNodeOps(*layerNode);
+ }
+ restoreForLayer();
+ }
+ }
+}
+
+void FrameBuilder::deferRenderNode(RenderNode& renderNode) {
+ renderNode.computeOrdering();
+
+ mCanvasState.save(SaveFlags::MatrixClip);
+ deferNodePropsAndOps(renderNode);
+ mCanvasState.restore();
+}
+
+void FrameBuilder::deferRenderNode(float tx, float ty, Rect clipRect, RenderNode& renderNode) {
+ renderNode.computeOrdering();
+
+ mCanvasState.save(SaveFlags::MatrixClip);
+ mCanvasState.translate(tx, ty);
+ mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
+ SkRegion::kIntersect_Op);
+ deferNodePropsAndOps(renderNode);
+ mCanvasState.restore();
+}
+
+static Rect nodeBounds(RenderNode& node) {
+ auto& props = node.properties();
+ return Rect(props.getLeft(), props.getTop(),
+ props.getRight(), props.getBottom());
+}
+
+void FrameBuilder::deferRenderNodeScene(const std::vector< sp<RenderNode> >& nodes,
+ const Rect& contentDrawBounds) {
+ if (nodes.size() < 1) return;
+ if (nodes.size() == 1) {
+ if (!nodes[0]->nothingToDraw()) {
+ deferRenderNode(*nodes[0]);
+ }
+ return;
+ }
+ // It there are multiple render nodes, they are laid out as follows:
+ // #0 - backdrop (content + caption)
+ // #1 - content (local bounds are at (0,0), will be translated and clipped to backdrop)
+ // #2 - additional overlay nodes
+ // Usually the backdrop cannot be seen since it will be entirely covered by the content. While
+ // resizing however it might become partially visible. The following render loop will crop the
+ // backdrop against the content and draw the remaining part of it. It will then draw the content
+ // cropped to the backdrop (since that indicates a shrinking of the window).
+ //
+ // Additional nodes will be drawn on top with no particular clipping semantics.
+
+ // Usually the contents bounds should be mContentDrawBounds - however - we will
+ // move it towards the fixed edge to give it a more stable appearance (for the moment).
+ // If there is no content bounds we ignore the layering as stated above and start with 2.
+
+ // Backdrop bounds in render target space
+ const Rect backdrop = nodeBounds(*nodes[0]);
+
+ // Bounds that content will fill in render target space (note content node bounds may be bigger)
+ Rect content(contentDrawBounds.getWidth(), contentDrawBounds.getHeight());
+ content.translate(backdrop.left, backdrop.top);
+ if (!content.contains(backdrop) && !nodes[0]->nothingToDraw()) {
+ // Content doesn't entirely overlap backdrop, so fill around content (right/bottom)
+
+ // Note: in the future, if content doesn't snap to backdrop's left/top, this may need to
+ // also fill left/top. Currently, both 2up and freeform position content at the top/left of
+ // the backdrop, so this isn't necessary.
+ if (content.right < backdrop.right) {
+ // draw backdrop to right side of content
+ deferRenderNode(0, 0, Rect(content.right, backdrop.top,
+ backdrop.right, backdrop.bottom), *nodes[0]);
+ }
+ if (content.bottom < backdrop.bottom) {
+ // draw backdrop to bottom of content
+ // Note: bottom fill uses content left/right, to avoid overdrawing left/right fill
+ deferRenderNode(0, 0, Rect(content.left, content.bottom,
+ content.right, backdrop.bottom), *nodes[0]);
+ }
+ }
+
+ if (!backdrop.isEmpty()) {
+ // content node translation to catch up with backdrop
+ float dx = contentDrawBounds.left - backdrop.left;
+ float dy = contentDrawBounds.top - backdrop.top;
+
+ Rect contentLocalClip = backdrop;
+ contentLocalClip.translate(dx, dy);
+ deferRenderNode(-dx, -dy, contentLocalClip, *nodes[1]);
+ } else {
+ deferRenderNode(*nodes[1]);
+ }
+
+ // remaining overlay nodes, simply defer
+ for (size_t index = 2; index < nodes.size(); index++) {
+ if (!nodes[index]->nothingToDraw()) {
+ deferRenderNode(*nodes[index]);
+ }
+ }
+}
+
+void FrameBuilder::onViewportInitialized() {}
+
+void FrameBuilder::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+
+void FrameBuilder::deferNodePropsAndOps(RenderNode& node) {
+ const RenderProperties& properties = node.properties();
+ const Outline& outline = properties.getOutline();
+ if (properties.getAlpha() <= 0
+ || (outline.getShouldClip() && outline.isEmpty())
+ || properties.getScaleX() == 0
+ || properties.getScaleY() == 0) {
+ return; // rejected
+ }
+
+ if (properties.getLeft() != 0 || properties.getTop() != 0) {
+ mCanvasState.translate(properties.getLeft(), properties.getTop());
+ }
+ if (properties.getStaticMatrix()) {
+ mCanvasState.concatMatrix(*properties.getStaticMatrix());
+ } else if (properties.getAnimationMatrix()) {
+ mCanvasState.concatMatrix(*properties.getAnimationMatrix());
+ }
+ if (properties.hasTransformMatrix()) {
+ if (properties.isTransformTranslateOnly()) {
+ mCanvasState.translate(properties.getTranslationX(), properties.getTranslationY());
+ } else {
+ mCanvasState.concatMatrix(*properties.getTransformMatrix());
+ }
+ }
+
+ const int width = properties.getWidth();
+ const int height = properties.getHeight();
+
+ Rect saveLayerBounds; // will be set to non-empty if saveLayer needed
+ const bool isLayer = properties.effectiveLayerType() != LayerType::None;
+ int clipFlags = properties.getClippingFlags();
+ if (properties.getAlpha() < 1) {
+ if (isLayer) {
+ clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
+ }
+ if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) {
+ // simply scale rendering content's alpha
+ mCanvasState.scaleAlpha(properties.getAlpha());
+ } else {
+ // schedule saveLayer by initializing saveLayerBounds
+ saveLayerBounds.set(0, 0, width, height);
+ if (clipFlags) {
+ properties.getClippingRectForFlags(clipFlags, &saveLayerBounds);
+ clipFlags = 0; // all clipping done by savelayer
+ }
+ }
+
+ if (CC_UNLIKELY(ATRACE_ENABLED() && properties.promotedToLayer())) {
+ // pretend alpha always causes savelayer to warn about
+ // performance problem affecting old versions
+ ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", node.getName(), width, height);
+ }
+ }
+ if (clipFlags) {
+ Rect clipRect;
+ properties.getClippingRectForFlags(clipFlags, &clipRect);
+ mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
+ SkRegion::kIntersect_Op);
+ }
+
+ if (properties.getRevealClip().willClip()) {
+ Rect bounds;
+ properties.getRevealClip().getBounds(&bounds);
+ mCanvasState.setClippingRoundRect(mAllocator,
+ bounds, properties.getRevealClip().getRadius());
+ } else if (properties.getOutline().willClip()) {
+ mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline()));
+ }
+
+ bool quickRejected = mCanvasState.currentSnapshot()->getRenderTargetClip().isEmpty()
+ || (properties.getClipToBounds()
+ && mCanvasState.quickRejectConservative(0, 0, width, height));
+ if (!quickRejected) {
+ // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer)
+ if (node.getLayer()) {
+ // HW layer
+ LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>(node);
+ BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
+ if (bakedOpState) {
+ // Node's layer already deferred, schedule it to render into parent layer
+ currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
+ }
+ } else if (CC_UNLIKELY(!saveLayerBounds.isEmpty())) {
+ // draw DisplayList contents within temporary, since persisted layer could not be used.
+ // (temp layers are clipped to viewport, since they don't persist offscreen content)
+ SkPaint saveLayerPaint;
+ saveLayerPaint.setAlpha(properties.getAlpha());
+ deferBeginLayerOp(*mAllocator.create_trivial<BeginLayerOp>(
+ saveLayerBounds,
+ Matrix4::identity(),
+ nullptr, // no record-time clip - need only respect defer-time one
+ &saveLayerPaint));
+ deferNodeOps(node);
+ deferEndLayerOp(*mAllocator.create_trivial<EndLayerOp>());
+ } else {
+ deferNodeOps(node);
+ }
+ }
+}
+
+typedef key_value_pair_t<float, const RenderNodeOp*> ZRenderNodeOpPair;
+
+template <typename V>
+static void buildZSortedChildList(V* zTranslatedNodes,
+ const DisplayList& displayList, const DisplayList::Chunk& chunk) {
+ if (chunk.beginChildIndex == chunk.endChildIndex) return;
+
+ for (size_t i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) {
+ RenderNodeOp* childOp = displayList.getChildren()[i];
+ RenderNode* child = childOp->renderNode;
+ float childZ = child->properties().getZ();
+
+ if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
+ zTranslatedNodes->push_back(ZRenderNodeOpPair(childZ, childOp));
+ childOp->skipInOrderDraw = true;
+ } else if (!child->properties().getProjectBackwards()) {
+ // regular, in order drawing DisplayList
+ childOp->skipInOrderDraw = false;
+ }
+ }
+
+ // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order)
+ std::stable_sort(zTranslatedNodes->begin(), zTranslatedNodes->end());
+}
+
+template <typename V>
+static size_t findNonNegativeIndex(const V& zTranslatedNodes) {
+ for (size_t i = 0; i < zTranslatedNodes.size(); i++) {
+ if (zTranslatedNodes[i].key >= 0.0f) return i;
+ }
+ return zTranslatedNodes.size();
+}
+
+template <typename V>
+void FrameBuilder::defer3dChildren(const ClipBase* reorderClip, ChildrenSelectMode mode,
+ const V& zTranslatedNodes) {
+ const int size = zTranslatedNodes.size();
+ if (size == 0
+ || (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f)
+ || (mode == ChildrenSelectMode::Positive && zTranslatedNodes[size - 1].key < 0.0f)) {
+ // no 3d children to draw
+ return;
+ }
+
+ /**
+ * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
+ * with very similar Z heights to draw together.
+ *
+ * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are
+ * underneath both, and neither's shadow is drawn on top of the other.
+ */
+ const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes);
+ size_t drawIndex, shadowIndex, endIndex;
+ if (mode == ChildrenSelectMode::Negative) {
+ drawIndex = 0;
+ endIndex = nonNegativeIndex;
+ shadowIndex = endIndex; // draw no shadows
+ } else {
+ drawIndex = nonNegativeIndex;
+ endIndex = size;
+ shadowIndex = drawIndex; // potentially draw shadow for each pos Z child
+ }
+
+ float lastCasterZ = 0.0f;
+ while (shadowIndex < endIndex || drawIndex < endIndex) {
+ if (shadowIndex < endIndex) {
+ const RenderNodeOp* casterNodeOp = zTranslatedNodes[shadowIndex].value;
+ const float casterZ = zTranslatedNodes[shadowIndex].key;
+ // attempt to render the shadow if the caster about to be drawn is its caster,
+ // OR if its caster's Z value is similar to the previous potential caster
+ if (shadowIndex == drawIndex || casterZ - lastCasterZ < 0.1f) {
+ deferShadow(reorderClip, *casterNodeOp);
+
+ lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
+ shadowIndex++;
+ continue;
+ }
+ }
+
+ const RenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
+ deferRenderNodeOpImpl(*childOp);
+ drawIndex++;
+ }
+}
+
+void FrameBuilder::deferShadow(const ClipBase* reorderClip, const RenderNodeOp& casterNodeOp) {
+ auto& node = *casterNodeOp.renderNode;
+ auto& properties = node.properties();
+
+ if (properties.getAlpha() <= 0.0f
+ || properties.getOutline().getAlpha() <= 0.0f
+ || !properties.getOutline().getPath()
+ || properties.getScaleX() == 0
+ || properties.getScaleY() == 0) {
+ // no shadow to draw
+ return;
+ }
+
+ const SkPath* casterOutlinePath = properties.getOutline().getPath();
+ const SkPath* revealClipPath = properties.getRevealClip().getPath();
+ if (revealClipPath && revealClipPath->isEmpty()) return;
+
+ float casterAlpha = properties.getAlpha() * properties.getOutline().getAlpha();
+
+ // holds temporary SkPath to store the result of intersections
+ SkPath* frameAllocatedPath = nullptr;
+ const SkPath* casterPath = casterOutlinePath;
+
+ // intersect the shadow-casting path with the reveal, if present
+ if (revealClipPath) {
+ frameAllocatedPath = createFrameAllocatedPath();
+
+ Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath);
+ casterPath = frameAllocatedPath;
+ }
+
+ // intersect the shadow-casting path with the clipBounds, if present
+ if (properties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS) {
+ if (!frameAllocatedPath) {
+ frameAllocatedPath = createFrameAllocatedPath();
+ }
+ Rect clipBounds;
+ properties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
+ SkPath clipBoundsPath;
+ clipBoundsPath.addRect(clipBounds.left, clipBounds.top,
+ clipBounds.right, clipBounds.bottom);
+
+ Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath);
+ casterPath = frameAllocatedPath;
+ }
+
+ // apply reorder clip to shadow, so it respects clip at beginning of reorderable chunk
+ int restoreTo = mCanvasState.save(SaveFlags::MatrixClip);
+ mCanvasState.writableSnapshot()->applyClip(reorderClip,
+ *mCanvasState.currentSnapshot()->transform);
+ if (CC_LIKELY(!mCanvasState.getRenderTargetClipBounds().isEmpty())) {
+ Matrix4 shadowMatrixXY(casterNodeOp.localMatrix);
+ Matrix4 shadowMatrixZ(casterNodeOp.localMatrix);
+ node.applyViewPropertyTransforms(shadowMatrixXY, false);
+ node.applyViewPropertyTransforms(shadowMatrixZ, true);
+
+ sp<TessellationCache::ShadowTask> task = mCaches.tessellationCache.getShadowTask(
+ mCanvasState.currentTransform(),
+ mCanvasState.getLocalClipBounds(),
+ casterAlpha >= 1.0f,
+ casterPath,
+ &shadowMatrixXY, &shadowMatrixZ,
+ mCanvasState.currentSnapshot()->getRelativeLightCenter(),
+ mLightRadius);
+ ShadowOp* shadowOp = mAllocator.create<ShadowOp>(task, casterAlpha);
+ BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
+ mAllocator, *mCanvasState.writableSnapshot(), shadowOp);
+ if (CC_LIKELY(bakedOpState)) {
+ currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
+ }
+ }
+ mCanvasState.restoreToCount(restoreTo);
+}
+
+void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) {
+ int count = mCanvasState.save(SaveFlags::MatrixClip);
+ const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
+
+ SkPath transformedMaskPath; // on stack, since BakedOpState makes a deep copy
+ if (projectionReceiverOutline) {
+ // transform the mask for this projector into render target space
+ // TODO: consider combining both transforms by stashing transform instead of applying
+ SkMatrix skCurrentTransform;
+ mCanvasState.currentTransform()->copyTo(skCurrentTransform);
+ projectionReceiverOutline->transform(
+ skCurrentTransform,
+ &transformedMaskPath);
+ mCanvasState.setProjectionPathMask(mAllocator, &transformedMaskPath);
+ }
+
+ for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
+ RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
+ RenderNode& childNode = *childOp->renderNode;
+
+ // Draw child if it has content, but ignore state in childOp - matrix already applied to
+ // transformFromCompositingAncestor, and record-time clip is ignored when projecting
+ if (!childNode.nothingToDraw()) {
+ int restoreTo = mCanvasState.save(SaveFlags::MatrixClip);
+
+ // Apply transform between ancestor and projected descendant
+ mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
+
+ deferNodePropsAndOps(childNode);
+
+ mCanvasState.restoreToCount(restoreTo);
+ }
+ }
+ mCanvasState.restoreToCount(count);
+}
+
+/**
+ * Used to define a list of lambdas referencing private FrameBuilder::onXX::defer() methods.
+ *
+ * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas.
+ * E.g. a BitmapOp op then would be dispatched to FrameBuilder::onBitmapOp(const BitmapOp&)
+ */
+#define OP_RECEIVER(Type) \
+ [](FrameBuilder& frameBuilder, const RecordedOp& op) { frameBuilder.defer##Type(static_cast<const Type&>(op)); },
+void FrameBuilder::deferNodeOps(const RenderNode& renderNode) {
+ typedef void (*OpDispatcher) (FrameBuilder& frameBuilder, const RecordedOp& op);
+ static OpDispatcher receivers[] = BUILD_DEFERRABLE_OP_LUT(OP_RECEIVER);
+
+ // can't be null, since DL=null node rejection happens before deferNodePropsAndOps
+ const DisplayList& displayList = *(renderNode.getDisplayList());
+ for (auto& chunk : displayList.getChunks()) {
+ FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
+ buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
+
+ defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Negative, zTranslatedNodes);
+ for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
+ const RecordedOp* op = displayList.getOps()[opIndex];
+ receivers[op->opId](*this, *op);
+
+ if (CC_UNLIKELY(!renderNode.mProjectedNodes.empty()
+ && displayList.projectionReceiveIndex >= 0
+ && static_cast<int>(opIndex) == displayList.projectionReceiveIndex)) {
+ deferProjectedChildren(renderNode);
+ }
+ }
+ defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Positive, zTranslatedNodes);
+ }
+}
+
+void FrameBuilder::deferRenderNodeOpImpl(const RenderNodeOp& op) {
+ if (op.renderNode->nothingToDraw()) return;
+ int count = mCanvasState.save(SaveFlags::MatrixClip);
+
+ // apply state from RecordedOp (clip first, since op's clip is transformed by current matrix)
+ mCanvasState.writableSnapshot()->applyClip(op.localClip,
+ *mCanvasState.currentSnapshot()->transform);
+ mCanvasState.concatMatrix(op.localMatrix);
+
+ // then apply state from node properties, and defer ops
+ deferNodePropsAndOps(*op.renderNode);
+
+ mCanvasState.restoreToCount(count);
+}
+
+void FrameBuilder::deferRenderNodeOp(const RenderNodeOp& op) {
+ if (!op.skipInOrderDraw) {
+ deferRenderNodeOpImpl(op);
+ }
+}
+
+/**
+ * Defers an unmergeable, strokeable op, accounting correctly
+ * for paint's style on the bounds being computed.
+ */
+BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+ BakedOpState::StrokeBehavior strokeBehavior) {
+ // Note: here we account for stroke when baking the op
+ BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
+ mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior);
+ if (!bakedState) return nullptr; // quick rejected
+
+ if (op.opId == RecordedOpId::RectOp && op.paint->getStyle() != SkPaint::kStroke_Style) {
+ bakedState->setupOpacity(op.paint);
+ }
+
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
+ return bakedState;
+}
+
+/**
+ * Returns batch id for tessellatable shapes, based on paint. Checks to see if path effect/AA will
+ * be used, since they trigger significantly different rendering paths.
+ *
+ * Note: not used for lines/points, since they don't currently support path effects.
+ */
+static batchid_t tessBatchId(const RecordedOp& op) {
+ const SkPaint& paint = *(op.paint);
+ return paint.getPathEffect()
+ ? OpBatchType::AlphaMaskTexture
+ : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
+}
+
+void FrameBuilder::deferArcOp(const ArcOp& op) {
+ deferStrokeableOp(op, tessBatchId(op));
+}
+
+static bool hasMergeableClip(const BakedOpState& state) {
+ return state.computedState.clipState
+ || state.computedState.clipState->mode == ClipMode::Rectangle;
+}
+
+void FrameBuilder::deferBitmapOp(const BitmapOp& op) {
+ BakedOpState* bakedState = tryBakeOpState(op);
+ if (!bakedState) return; // quick rejected
+
+ if (op.bitmap->isOpaque()) {
+ bakedState->setupOpacity(op.paint);
+ }
+
+ // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation
+ // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in
+ // MergingDrawBatch::canMergeWith()
+ if (bakedState->computedState.transform.isSimple()
+ && bakedState->computedState.transform.positiveScale()
+ && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
+ && op.bitmap->colorType() != kAlpha_8_SkColorType
+ && hasMergeableClip(*bakedState)) {
+ mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID());
+ // TODO: AssetAtlas in mergeId
+ currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId);
+ } else {
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
+ }
+}
+
+void FrameBuilder::deferBitmapMeshOp(const BitmapMeshOp& op) {
+ BakedOpState* bakedState = tryBakeOpState(op);
+ if (!bakedState) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
+}
+
+void FrameBuilder::deferBitmapRectOp(const BitmapRectOp& op) {
+ BakedOpState* bakedState = tryBakeOpState(op);
+ if (!bakedState) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
+}
+
+void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) {
+ const SkBitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty();
+ SkPaint* paint = op.vectorDrawable->getPaint();
+ const BitmapRectOp* resolvedOp = mAllocator.create_trivial<BitmapRectOp>(op.unmappedBounds,
+ op.localMatrix,
+ op.localClip,
+ paint,
+ &bitmap,
+ Rect(bitmap.width(), bitmap.height()));
+ deferBitmapRectOp(*resolvedOp);
+}
+
+void FrameBuilder::deferCirclePropsOp(const CirclePropsOp& op) {
+ // allocate a temporary oval op (with mAllocator, so it persists until render), so the
+ // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
+ float x = *(op.x);
+ float y = *(op.y);
+ float radius = *(op.radius);
+ Rect unmappedBounds(x - radius, y - radius, x + radius, y + radius);
+ const OvalOp* resolvedOp = mAllocator.create_trivial<OvalOp>(
+ unmappedBounds,
+ op.localMatrix,
+ op.localClip,
+ op.paint);
+ deferOvalOp(*resolvedOp);
+}
+
+void FrameBuilder::deferColorOp(const ColorOp& op) {
+ BakedOpState* bakedState = tryBakeUnboundedOpState(op);
+ if (!bakedState) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
+}
+
+void FrameBuilder::deferFunctorOp(const FunctorOp& op) {
+ BakedOpState* bakedState = tryBakeUnboundedOpState(op);
+ if (!bakedState) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Functor);
+}
+
+void FrameBuilder::deferLinesOp(const LinesOp& op) {
+ batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices;
+ deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
+}
+
+void FrameBuilder::deferOvalOp(const OvalOp& op) {
+ deferStrokeableOp(op, tessBatchId(op));
+}
+
+void FrameBuilder::deferPatchOp(const PatchOp& op) {
+ BakedOpState* bakedState = tryBakeOpState(op);
+ if (!bakedState) return; // quick rejected
+
+ if (bakedState->computedState.transform.isPureTranslate()
+ && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
+ && hasMergeableClip(*bakedState)) {
+ mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID());
+ // TODO: AssetAtlas in mergeId
+
+ // Only use the MergedPatch batchId when merged, so Bitmap+Patch don't try to merge together
+ currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::MergedPatch, mergeId);
+ } else {
+ // Use Bitmap batchId since Bitmap+Patch use same shader
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
+ }
+}
+
+void FrameBuilder::deferPathOp(const PathOp& op) {
+ auto state = deferStrokeableOp(op, OpBatchType::AlphaMaskTexture);
+ if (CC_LIKELY(state)) {
+ mCaches.pathCache.precache(op.path, op.paint);
+ }
+}
+
+void FrameBuilder::deferPointsOp(const PointsOp& op) {
+ batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices;
+ deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
+}
+
+void FrameBuilder::deferRectOp(const RectOp& op) {
+ deferStrokeableOp(op, tessBatchId(op));
+}
+
+void FrameBuilder::deferRoundRectOp(const RoundRectOp& op) {
+ auto state = deferStrokeableOp(op, tessBatchId(op));
+ if (CC_LIKELY(state && !op.paint->getPathEffect())) {
+ // TODO: consider storing tessellation task in BakedOpState
+ mCaches.tessellationCache.precacheRoundRect(state->computedState.transform, *(op.paint),
+ op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.rx, op.ry);
+ }
+}
+
+void FrameBuilder::deferRoundRectPropsOp(const RoundRectPropsOp& op) {
+ // allocate a temporary round rect op (with mAllocator, so it persists until render), so the
+ // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
+ const RoundRectOp* resolvedOp = mAllocator.create_trivial<RoundRectOp>(
+ Rect(*(op.left), *(op.top), *(op.right), *(op.bottom)),
+ op.localMatrix,
+ op.localClip,
+ op.paint, *op.rx, *op.ry);
+ deferRoundRectOp(*resolvedOp);
+}
+
+void FrameBuilder::deferSimpleRectsOp(const SimpleRectsOp& op) {
+ BakedOpState* bakedState = tryBakeOpState(op);
+ if (!bakedState) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
+}
+
+static batchid_t textBatchId(const SkPaint& paint) {
+ // TODO: better handling of shader (since we won't care about color then)
+ return paint.getColor() == SK_ColorBLACK ? OpBatchType::Text : OpBatchType::ColorText;
+}
+
+void FrameBuilder::deferTextOp(const TextOp& op) {
+ BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
+ mAllocator, *mCanvasState.writableSnapshot(), op,
+ BakedOpState::StrokeBehavior::StyleDefined);
+ if (!bakedState) return; // quick rejected
+
+ batchid_t batchId = textBatchId(*(op.paint));
+ if (bakedState->computedState.transform.isPureTranslate()
+ && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
+ && hasMergeableClip(*bakedState)) {
+ mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
+ currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId);
+ } else {
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
+ }
+
+ FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer();
+ auto& totalTransform = bakedState->computedState.transform;
+ if (totalTransform.isPureTranslate() || totalTransform.isPerspective()) {
+ fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::I());
+ } else {
+ // Partial transform case, see BakedOpDispatcher::renderTextOp
+ float sx, sy;
+ totalTransform.decomposeScale(sx, sy);
+ fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::MakeScale(
+ roundf(std::max(1.0f, sx)),
+ roundf(std::max(1.0f, sy))));
+ }
+}
+
+void FrameBuilder::deferTextOnPathOp(const TextOnPathOp& op) {
+ BakedOpState* bakedState = tryBakeUnboundedOpState(op);
+ if (!bakedState) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint)));
+
+ mCaches.fontRenderer.getFontRenderer().precache(
+ op.paint, op.glyphs, op.glyphCount, SkMatrix::I());
+}
+
+void FrameBuilder::deferTextureLayerOp(const TextureLayerOp& op) {
+ if (CC_UNLIKELY(!op.layer->isRenderable())) return;
+
+ const TextureLayerOp* textureLayerOp = &op;
+ // Now safe to access transform (which was potentially unready at record time)
+ if (!op.layer->getTransform().isIdentity()) {
+ // non-identity transform present, so 'inject it' into op by copying + replacing matrix
+ Matrix4 combinedMatrix(op.localMatrix);
+ combinedMatrix.multiply(op.layer->getTransform());
+ textureLayerOp = mAllocator.create<TextureLayerOp>(op, combinedMatrix);
+ }
+ BakedOpState* bakedState = tryBakeOpState(*textureLayerOp);
+
+ if (!bakedState) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::TextureLayer);
+}
+
+void FrameBuilder::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+ float contentTranslateX, float contentTranslateY,
+ const Rect& repaintRect,
+ const Vector3& lightCenter,
+ const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
+ mCanvasState.save(SaveFlags::MatrixClip);
+ mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
+ mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
+ mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter);
+ mCanvasState.writableSnapshot()->transform->loadTranslate(
+ contentTranslateX, contentTranslateY, 0);
+ mCanvasState.writableSnapshot()->setClip(
+ repaintRect.left, repaintRect.top, repaintRect.right, repaintRect.bottom);
+
+ // create a new layer repaint, and push its index on the stack
+ mLayerStack.push_back(mLayerBuilders.size());
+ auto newFbo = mAllocator.create<LayerBuilder>(layerWidth, layerHeight,
+ repaintRect, beginLayerOp, renderNode);
+ mLayerBuilders.push_back(newFbo);
+}
+
+void FrameBuilder::restoreForLayer() {
+ // restore canvas, and pop finished layer off of the stack
+ mCanvasState.restore();
+ mLayerStack.pop_back();
+}
+
+// TODO: defer time rejection (when bounds become empty) + tests
+// Option - just skip layers with no bounds at playback + defer?
+void FrameBuilder::deferBeginLayerOp(const BeginLayerOp& op) {
+ uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
+ uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
+
+ auto previous = mCanvasState.currentSnapshot();
+ Vector3 lightCenter = previous->getRelativeLightCenter();
+
+ // Combine all transforms used to present saveLayer content:
+ // parent content transform * canvas transform * bounds offset
+ Matrix4 contentTransform(*(previous->transform));
+ contentTransform.multiply(op.localMatrix);
+ contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top);
+
+ Matrix4 inverseContentTransform;
+ inverseContentTransform.loadInverse(contentTransform);
+
+ // map the light center into layer-relative space
+ inverseContentTransform.mapPoint3d(lightCenter);
+
+ // Clip bounds of temporary layer to parent's clip rect, so:
+ Rect saveLayerBounds(layerWidth, layerHeight);
+ // 1) transform Rect(width, height) into parent's space
+ // note: left/top offsets put in contentTransform above
+ contentTransform.mapRect(saveLayerBounds);
+ // 2) intersect with parent's clip
+ saveLayerBounds.doIntersect(previous->getRenderTargetClip());
+ // 3) and transform back
+ inverseContentTransform.mapRect(saveLayerBounds);
+ saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight));
+ saveLayerBounds.roundOut();
+
+ // if bounds are reduced, will clip the layer's area by reducing required bounds...
+ layerWidth = saveLayerBounds.getWidth();
+ layerHeight = saveLayerBounds.getHeight();
+ // ...and shifting drawing content to account for left/top side clipping
+ float contentTranslateX = -saveLayerBounds.left;
+ float contentTranslateY = -saveLayerBounds.top;
+
+ saveForLayer(layerWidth, layerHeight,
+ contentTranslateX, contentTranslateY,
+ Rect(layerWidth, layerHeight),
+ lightCenter,
+ &op, nullptr);
+}
+
+void FrameBuilder::deferEndLayerOp(const EndLayerOp& /* ignored */) {
+ const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
+ int finishedLayerIndex = mLayerStack.back();
+
+ restoreForLayer();
+
+ // record the draw operation into the previous layer's list of draw commands
+ // uses state from the associated beginLayerOp, since it has all the state needed for drawing
+ LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>(
+ beginLayerOp.unmappedBounds,
+ beginLayerOp.localMatrix,
+ beginLayerOp.localClip,
+ beginLayerOp.paint,
+ &(mLayerBuilders[finishedLayerIndex]->offscreenBuffer));
+ BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
+
+ if (bakedOpState) {
+ // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
+ currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
+ } else {
+ // Layer won't be drawn - delete its drawing batches to prevent it from doing any work
+ // TODO: need to prevent any render work from being done
+ // - create layerop earlier for reject purposes?
+ mLayerBuilders[finishedLayerIndex]->clear();
+ return;
+ }
+}
+
+void FrameBuilder::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) {
+ Matrix4 boundsTransform(*(mCanvasState.currentSnapshot()->transform));
+ boundsTransform.multiply(op.localMatrix);
+
+ Rect dstRect(op.unmappedBounds);
+ boundsTransform.mapRect(dstRect);
+ dstRect.doIntersect(mCanvasState.currentSnapshot()->getRenderTargetClip());
+
+ if (dstRect.isEmpty()) {
+ // Unclipped layer rejected - push a null op, so next EndUnclippedLayerOp is ignored
+ currentLayer().activeUnclippedSaveLayers.push_back(nullptr);
+ } else {
+ // Allocate a holding position for the layer object (copyTo will produce, copyFrom will consume)
+ OffscreenBuffer** layerHandle = mAllocator.create<OffscreenBuffer*>(nullptr);
+
+ /**
+ * First, defer an operation to copy out the content from the rendertarget into a layer.
+ */
+ auto copyToOp = mAllocator.create_trivial<CopyToLayerOp>(op, layerHandle);
+ BakedOpState* bakedState = BakedOpState::directConstruct(mAllocator,
+ &(currentLayer().repaintClip), dstRect, *copyToOp);
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::CopyToLayer);
+
+ /**
+ * Defer a clear rect, so that clears from multiple unclipped layers can be drawn
+ * both 1) simultaneously, and 2) as long after the copyToLayer executes as possible
+ */
+ currentLayer().deferLayerClear(dstRect);
+
+ /**
+ * And stash an operation to copy that layer back under the rendertarget until
+ * a balanced EndUnclippedLayerOp is seen
+ */
+ auto copyFromOp = mAllocator.create_trivial<CopyFromLayerOp>(op, layerHandle);
+ bakedState = BakedOpState::directConstruct(mAllocator,
+ &(currentLayer().repaintClip), dstRect, *copyFromOp);
+ currentLayer().activeUnclippedSaveLayers.push_back(bakedState);
+ }
+}
+
+void FrameBuilder::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& /* ignored */) {
+ LOG_ALWAYS_FATAL_IF(currentLayer().activeUnclippedSaveLayers.empty(), "no layer to end!");
+
+ BakedOpState* copyFromLayerOp = currentLayer().activeUnclippedSaveLayers.back();
+ currentLayer().activeUnclippedSaveLayers.pop_back();
+ if (copyFromLayerOp) {
+ currentLayer().deferUnmergeableOp(mAllocator, copyFromLayerOp, OpBatchType::CopyFromLayer);
+ }
+}
+
+void FrameBuilder::finishDefer() {
+ mCaches.fontRenderer.endPrecaching();
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h
new file mode 100644
index 000000000000..b9154435c1e5
--- /dev/null
+++ b/libs/hwui/FrameBuilder.h
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2016 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 "BakedOpState.h"
+#include "CanvasState.h"
+#include "DisplayList.h"
+#include "LayerBuilder.h"
+#include "RecordedOp.h"
+#include "utils/GLUtils.h"
+
+#include <vector>
+#include <unordered_map>
+
+struct SkRect;
+
+namespace android {
+namespace uirenderer {
+
+class BakedOpState;
+class LayerUpdateQueue;
+class OffscreenBuffer;
+class Rect;
+
+/**
+ * Processes, optimizes, and stores rendering commands from RenderNodes and
+ * LayerUpdateQueue, building content needed to render a frame.
+ *
+ * Resolves final drawing state for each operation (including clip, alpha and matrix), and then
+ * reorder and merge each op as it is resolved for drawing efficiency. Each layer of content (either
+ * from the LayerUpdateQueue, or temporary layers created by saveLayer operations in the
+ * draw stream) will create different reorder contexts, each in its own LayerBuilder.
+ *
+ * Then the prepared or 'baked' drawing commands can be issued by calling the templated
+ * replayBakedOps() function, which will dispatch them (including any created merged op collections)
+ * to a Dispatcher and Renderer. See BakedOpDispatcher for how these baked drawing operations are
+ * resolved into Glops and rendered via BakedOpRenderer.
+ *
+ * This class is also the authoritative source for traversing RenderNodes, both for standard op
+ * traversal within a DisplayList, and for out of order RenderNode traversal for Z and projection.
+ */
+class FrameBuilder : public CanvasStateClient {
+public:
+ struct LightGeometry {
+ Vector3 center;
+ float radius;
+ };
+
+ FrameBuilder(const SkRect& clip,
+ uint32_t viewportWidth, uint32_t viewportHeight,
+ const LightGeometry& lightGeometry, Caches& caches);
+
+ FrameBuilder(const LayerUpdateQueue& layerUpdateQueue,
+ const LightGeometry& lightGeometry, Caches& caches);
+
+ void deferLayers(const LayerUpdateQueue& layers);
+
+ void deferRenderNode(RenderNode& renderNode);
+
+ void deferRenderNode(float tx, float ty, Rect clipRect, RenderNode& renderNode);
+
+ void deferRenderNodeScene(const std::vector< sp<RenderNode> >& nodes,
+ const Rect& contentDrawBounds);
+
+ virtual ~FrameBuilder() {}
+
+ /**
+ * replayBakedOps() is templated based on what class will receive ops being replayed.
+ *
+ * It constructs a lookup array of lambdas, which allows a recorded BakeOpState to use
+ * state->op->opId to lookup a receiver that will be called when the op is replayed.
+ */
+ template <typename StaticDispatcher, typename Renderer>
+ void replayBakedOps(Renderer& renderer) {
+ std::vector<OffscreenBuffer*> temporaryLayers;
+ finishDefer();
+ /**
+ * Defines a LUT of lambdas which allow a recorded BakedOpState to use state->op->opId to
+ * dispatch the op via a method on a static dispatcher when the op is replayed.
+ *
+ * For example a BitmapOp would resolve, via the lambda lookup, to calling:
+ *
+ * StaticDispatcher::onBitmapOp(Renderer& renderer, const BitmapOp& op, const BakedOpState& state);
+ */
+ #define X(Type) \
+ [](void* renderer, const BakedOpState& state) { \
+ StaticDispatcher::on##Type(*(static_cast<Renderer*>(renderer)), \
+ static_cast<const Type&>(*(state.op)), state); \
+ },
+ static BakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X);
+ #undef X
+
+ /**
+ * Defines a LUT of lambdas which allow merged arrays of BakedOpState* to be passed to a
+ * static dispatcher when the group of merged ops is replayed.
+ */
+ #define X(Type) \
+ [](void* renderer, const MergedBakedOpList& opList) { \
+ StaticDispatcher::onMerged##Type##s(*(static_cast<Renderer*>(renderer)), opList); \
+ },
+ static MergedOpReceiver mergedReceivers[] = BUILD_MERGEABLE_OP_LUT(X);
+ #undef X
+
+ // Relay through layers in reverse order, since layers
+ // later in the list will be drawn by earlier ones
+ for (int i = mLayerBuilders.size() - 1; i >= 1; i--) {
+ GL_CHECKPOINT(MODERATE);
+ LayerBuilder& layer = *(mLayerBuilders[i]);
+ if (layer.renderNode) {
+ // cached HW layer - can't skip layer if empty
+ renderer.startRepaintLayer(layer.offscreenBuffer, layer.repaintRect);
+ GL_CHECKPOINT(MODERATE);
+ layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
+ GL_CHECKPOINT(MODERATE);
+ renderer.endLayer();
+ } else if (!layer.empty()) {
+ // save layer - skip entire layer if empty (in which case, LayerOp has null layer).
+ layer.offscreenBuffer = renderer.startTemporaryLayer(layer.width, layer.height);
+ temporaryLayers.push_back(layer.offscreenBuffer);
+ GL_CHECKPOINT(MODERATE);
+ layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
+ GL_CHECKPOINT(MODERATE);
+ renderer.endLayer();
+ }
+ }
+
+ GL_CHECKPOINT(MODERATE);
+ if (CC_LIKELY(mDrawFbo0)) {
+ const LayerBuilder& fbo0 = *(mLayerBuilders[0]);
+ renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
+ GL_CHECKPOINT(MODERATE);
+ fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
+ GL_CHECKPOINT(MODERATE);
+ renderer.endFrame(fbo0.repaintRect);
+ }
+
+ for (auto& temporaryLayer : temporaryLayers) {
+ renderer.recycleTemporaryLayer(temporaryLayer);
+ }
+ }
+
+ void dump() const {
+ for (auto&& layer : mLayerBuilders) {
+ layer->dump();
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ /// CanvasStateClient interface
+ ///////////////////////////////////////////////////////////////////
+ virtual void onViewportInitialized() override;
+ virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override;
+ virtual GLuint getTargetFbo() const override { return 0; }
+
+private:
+ void finishDefer();
+ enum class ChildrenSelectMode {
+ Negative,
+ Positive
+ };
+ void saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+ float contentTranslateX, float contentTranslateY,
+ const Rect& repaintRect,
+ const Vector3& lightCenter,
+ const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+ void restoreForLayer();
+
+ LayerBuilder& currentLayer() { return *(mLayerBuilders[mLayerStack.back()]); }
+
+ BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) {
+ return BakedOpState::tryConstruct(mAllocator, *mCanvasState.writableSnapshot(), recordedOp);
+ }
+ BakedOpState* tryBakeUnboundedOpState(const RecordedOp& recordedOp) {
+ return BakedOpState::tryConstructUnbounded(mAllocator, *mCanvasState.writableSnapshot(), recordedOp);
+ }
+
+
+ // should always be surrounded by a save/restore pair, and not called if DisplayList is null
+ void deferNodePropsAndOps(RenderNode& node);
+
+ template <typename V>
+ void defer3dChildren(const ClipBase* reorderClip, ChildrenSelectMode mode,
+ const V& zTranslatedNodes);
+
+ void deferShadow(const ClipBase* reorderClip, const RenderNodeOp& casterOp);
+
+ void deferProjectedChildren(const RenderNode& renderNode);
+
+ void deferNodeOps(const RenderNode& renderNode);
+
+ void deferRenderNodeOpImpl(const RenderNodeOp& op);
+
+ void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers);
+
+ SkPath* createFrameAllocatedPath() {
+ return mAllocator.create<SkPath>();
+ }
+
+ BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+ BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined);
+
+ /**
+ * Declares all FrameBuilder::deferXXXXOp() methods for every RecordedOp type.
+ *
+ * These private methods are called from within deferImpl to defer each individual op
+ * type differently.
+ */
+#define X(Type) void defer##Type(const Type& op);
+ MAP_DEFERRABLE_OPS(X)
+#undef X
+
+ // contains single-frame objects, such as BakedOpStates, LayerBuilders, Batches
+ LinearAllocator mAllocator;
+ LinearStdAllocator<void*> mStdAllocator;
+
+ // List of every deferred layer's render state. Replayed in reverse order to render a frame.
+ LsaVector<LayerBuilder*> mLayerBuilders;
+
+ /*
+ * Stack of indices within mLayerBuilders representing currently active layers. If drawing
+ * layerA within a layerB, will contain, in order:
+ * - 0 (representing FBO 0, always present)
+ * - layerB's index
+ * - layerA's index
+ *
+ * Note that this doesn't vector doesn't always map onto all values of mLayerBuilders. When a
+ * layer is finished deferring, it will still be represented in mLayerBuilders, but it's index
+ * won't be in mLayerStack. This is because it can be replayed, but can't have any more drawing
+ * ops added to it.
+ */
+ LsaVector<size_t> mLayerStack;
+
+ CanvasState mCanvasState;
+
+ Caches& mCaches;
+
+ float mLightRadius;
+
+ const bool mDrawFbo0;
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index f8013ab6b6c4..0baca391be79 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -118,6 +118,10 @@ public:
set(FrameInfoIndex::Flags) |= static_cast<uint64_t>(frameInfoFlag);
}
+ const int64_t* data() const {
+ return mFrameInfo;
+ }
+
inline int64_t operator[](FrameInfoIndex index) const {
return get(index);
}
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index b416615c20e1..adadd32a2fc0 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -15,7 +15,12 @@
*/
#include "FrameInfoVisualizer.h"
+#if HWUI_NEW_OPS
+#include "BakedOpRenderer.h"
+#else
#include "OpenGLRenderer.h"
+#endif
+#include "utils/Color.h"
#include <cutils/compiler.h>
#include <array>
@@ -27,19 +32,19 @@
#define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2
#define PROFILE_DRAW_DP_PER_MS 7
+namespace android {
+namespace uirenderer {
+
// Must be NUM_ELEMENTS in size
-static const SkColor THRESHOLD_COLOR = 0xff5faa4d;
-static const SkColor BAR_FAST_ALPHA = 0x8F000000;
-static const SkColor BAR_JANKY_ALPHA = 0xDF000000;
+static const SkColor THRESHOLD_COLOR = Color::Green_500;
+static const SkColor BAR_FAST_MASK = 0x8FFFFFFF;
+static const SkColor BAR_JANKY_MASK = 0xDFFFFFFF;
// We could get this from TimeLord and use the actual frame interval, but
// this is good enough
#define FRAME_THRESHOLD 16
#define FRAME_THRESHOLD_NS 16000000
-namespace android {
-namespace uirenderer {
-
struct BarSegment {
FrameInfoIndex start;
FrameInfoIndex end;
@@ -47,13 +52,13 @@ struct BarSegment {
};
static const std::array<BarSegment,7> Bar {{
- { FrameInfoIndex::IntendedVsync, FrameInfoIndex::HandleInputStart, 0x00796B },
- { FrameInfoIndex::HandleInputStart, FrameInfoIndex::PerformTraversalsStart, 0x388E3C },
- { FrameInfoIndex::PerformTraversalsStart, FrameInfoIndex::DrawStart, 0x689F38},
- { FrameInfoIndex::DrawStart, FrameInfoIndex::SyncStart, 0x2196F3},
- { FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart, 0x4FC3F7},
- { FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers, 0xF44336},
- { FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted, 0xFF9800},
+ { FrameInfoIndex::IntendedVsync, FrameInfoIndex::HandleInputStart, Color::Teal_700 },
+ { FrameInfoIndex::HandleInputStart, FrameInfoIndex::PerformTraversalsStart, Color::Green_700 },
+ { FrameInfoIndex::PerformTraversalsStart, FrameInfoIndex::DrawStart, Color::LightGreen_700 },
+ { FrameInfoIndex::DrawStart, FrameInfoIndex::SyncStart, Color::Blue_500 },
+ { FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart, Color::LightBlue_300 },
+ { FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers, Color::Red_500},
+ { FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted, Color::Orange_500},
}};
static int dpToPx(int dp, float density) {
@@ -87,7 +92,7 @@ void FrameInfoVisualizer::unionDirty(SkRect* dirty) {
}
}
-void FrameInfoVisualizer::draw(OpenGLRenderer* canvas) {
+void FrameInfoVisualizer::draw(ContentRenderer* renderer) {
RETURN_IF_DISABLED();
if (mShowDirtyRegions) {
@@ -95,7 +100,7 @@ void FrameInfoVisualizer::draw(OpenGLRenderer* canvas) {
if (mFlashToggle) {
SkPaint paint;
paint.setColor(0x7fff0000);
- canvas->drawRect(mDirtyRegion.fLeft, mDirtyRegion.fTop,
+ renderer->drawRect(mDirtyRegion.fLeft, mDirtyRegion.fTop,
mDirtyRegion.fRight, mDirtyRegion.fBottom, &paint);
}
}
@@ -110,9 +115,9 @@ void FrameInfoVisualizer::draw(OpenGLRenderer* canvas) {
info.markSwapBuffers();
info.markFrameCompleted();
- initializeRects(canvas->getViewportHeight(), canvas->getViewportWidth());
- drawGraph(canvas);
- drawThreshold(canvas);
+ initializeRects(renderer->getViewportHeight(), renderer->getViewportWidth());
+ drawGraph(renderer);
+ drawThreshold(renderer);
}
}
@@ -193,27 +198,26 @@ void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex en
}
}
-void FrameInfoVisualizer::drawGraph(OpenGLRenderer* canvas) {
+void FrameInfoVisualizer::drawGraph(ContentRenderer* renderer) {
SkPaint paint;
for (size_t i = 0; i < Bar.size(); i++) {
nextBarSegment(Bar[i].start, Bar[i].end);
- paint.setColor(Bar[i].color | BAR_FAST_ALPHA);
- canvas->drawRects(mFastRects.get(), mNumFastRects * 4, &paint);
- paint.setColor(Bar[i].color | BAR_JANKY_ALPHA);
- canvas->drawRects(mJankyRects.get(), mNumJankyRects * 4, &paint);
+ paint.setColor(Bar[i].color & BAR_FAST_MASK);
+ renderer->drawRects(mFastRects.get(), mNumFastRects * 4, &paint);
+ paint.setColor(Bar[i].color & BAR_JANKY_MASK);
+ renderer->drawRects(mJankyRects.get(), mNumJankyRects * 4, &paint);
}
}
-void FrameInfoVisualizer::drawThreshold(OpenGLRenderer* canvas) {
+void FrameInfoVisualizer::drawThreshold(ContentRenderer* renderer) {
SkPaint paint;
paint.setColor(THRESHOLD_COLOR);
- paint.setStrokeWidth(mThresholdStroke);
-
- float pts[4];
- pts[0] = 0.0f;
- pts[1] = pts[3] = canvas->getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit);
- pts[2] = canvas->getViewportWidth();
- canvas->drawLines(pts, 4, &paint);
+ float yLocation = renderer->getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit);
+ renderer->drawRect(0.0f,
+ yLocation - mThresholdStroke/2,
+ renderer->getViewportWidth(),
+ yLocation + mThresholdStroke/2,
+ &paint);
}
bool FrameInfoVisualizer::consumeProperties() {
diff --git a/libs/hwui/FrameInfoVisualizer.h b/libs/hwui/FrameInfoVisualizer.h
index cf877c4d8827..83adf1985c72 100644
--- a/libs/hwui/FrameInfoVisualizer.h
+++ b/libs/hwui/FrameInfoVisualizer.h
@@ -28,7 +28,13 @@
namespace android {
namespace uirenderer {
+#if HWUI_NEW_OPS
+class BakedOpRenderer;
+typedef BakedOpRenderer ContentRenderer;
+#else
class OpenGLRenderer;
+typedef OpenGLRenderer ContentRenderer;
+#endif
// TODO: This is a bit awkward as it needs to match the thing in CanvasContext
// A better abstraction here would be nice but iterators are painful
@@ -46,7 +52,7 @@ public:
void setDensity(float density);
void unionDirty(SkRect* dirty);
- void draw(OpenGLRenderer* canvas);
+ void draw(ContentRenderer* renderer);
void dumpData(int fd);
@@ -56,8 +62,8 @@ private:
void initializeRects(const int baseline, const int width);
void nextBarSegment(FrameInfoIndex start, FrameInfoIndex end);
- void drawGraph(OpenGLRenderer* canvas);
- void drawThreshold(OpenGLRenderer* canvas);
+ void drawGraph(ContentRenderer* renderer);
+ void drawThreshold(ContentRenderer* renderer);
inline float durationMS(size_t index, FrameInfoIndex start, FrameInfoIndex end) {
float duration = mFrameSource[index].duration(start, end) * 0.000001f;
diff --git a/libs/hwui/FrameMetricsObserver.h b/libs/hwui/FrameMetricsObserver.h
new file mode 100644
index 000000000000..4f81c8681fc8
--- /dev/null
+++ b/libs/hwui/FrameMetricsObserver.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 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 <utils/RefBase.h>
+
+namespace android {
+namespace uirenderer {
+
+class FrameMetricsObserver : public VirtualLightRefBase {
+public:
+ virtual void notify(const int64_t* buffer);
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/FrameMetricsReporter.h b/libs/hwui/FrameMetricsReporter.h
new file mode 100644
index 000000000000..c1cd0a9224b6
--- /dev/null
+++ b/libs/hwui/FrameMetricsReporter.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 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 <utils/RefBase.h>
+#include <utils/Log.h>
+
+#include "FrameInfo.h"
+#include "FrameMetricsObserver.h"
+
+#include <string.h>
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+class FrameMetricsReporter {
+public:
+ FrameMetricsReporter() {}
+
+ void addObserver(FrameMetricsObserver* observer) {
+ mObservers.push_back(observer);
+ }
+
+ bool removeObserver(FrameMetricsObserver* observer) {
+ for (size_t i = 0; i < mObservers.size(); i++) {
+ if (mObservers[i].get() == observer) {
+ mObservers.erase(mObservers.begin() + i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool hasObservers() {
+ return mObservers.size() > 0;
+ }
+
+ void reportFrameMetrics(const int64_t* stats) {
+ for (size_t i = 0; i < mObservers.size(); i++) {
+ mObservers[i]->notify(stats);
+ }
+ }
+
+private:
+ std::vector< sp<FrameMetricsObserver> > mObservers;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
diff --git a/libs/hwui/GammaFontRenderer.cpp b/libs/hwui/GammaFontRenderer.cpp
index 070c3d70a069..96cac86386b5 100644
--- a/libs/hwui/GammaFontRenderer.cpp
+++ b/libs/hwui/GammaFontRenderer.cpp
@@ -21,231 +21,22 @@
namespace android {
namespace uirenderer {
-///////////////////////////////////////////////////////////////////////////////
-// Utils
-///////////////////////////////////////////////////////////////////////////////
-
-static int luminance(const SkPaint* paint) {
- uint32_t c = paint->getColor();
- const int r = (c >> 16) & 0xFF;
- const int g = (c >> 8) & 0xFF;
- const int b = (c ) & 0xFF;
- return (r * 2 + g * 5 + b) >> 3;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Base class GammaFontRenderer
-///////////////////////////////////////////////////////////////////////////////
-
-GammaFontRenderer* GammaFontRenderer::createRenderer() {
- // Choose the best renderer
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_TEXT_GAMMA_METHOD, property, DEFAULT_TEXT_GAMMA_METHOD) > 0) {
- if (!strcasecmp(property, "lookup")) {
- return new LookupGammaFontRenderer();
- } else if (!strcasecmp(property, "shader")) {
- return new ShaderGammaFontRenderer(false);
- } else if (!strcasecmp(property, "shader3")) {
- return new ShaderGammaFontRenderer(true);
- }
- }
-
- return new Lookup3GammaFontRenderer();
-}
-
GammaFontRenderer::GammaFontRenderer() {
- // Get the renderer properties
- char property[PROPERTY_VALUE_MAX];
-
- // Get the gamma
- mGamma = DEFAULT_TEXT_GAMMA;
- if (property_get(PROPERTY_TEXT_GAMMA, property, nullptr) > 0) {
- INIT_LOGD(" Setting text gamma to %s", property);
- mGamma = atof(property);
- } else {
- INIT_LOGD(" Using default text gamma of %.2f", DEFAULT_TEXT_GAMMA);
- }
-
- // Get the black gamma threshold
- mBlackThreshold = DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD;
- if (property_get(PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD, property, nullptr) > 0) {
- INIT_LOGD(" Setting text black gamma threshold to %s", property);
- mBlackThreshold = atoi(property);
- } else {
- INIT_LOGD(" Using default text black gamma threshold of %d",
- DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD);
- }
-
- // Get the white gamma threshold
- mWhiteThreshold = DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD;
- if (property_get(PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD, property, nullptr) > 0) {
- INIT_LOGD(" Setting text white gamma threshold to %s", property);
- mWhiteThreshold = atoi(property);
- } else {
- INIT_LOGD(" Using default white black gamma threshold of %d",
- DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD);
- }
-}
-
-GammaFontRenderer::~GammaFontRenderer() {
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Shader-based renderer
-///////////////////////////////////////////////////////////////////////////////
-
-ShaderGammaFontRenderer::ShaderGammaFontRenderer(bool multiGamma)
- : GammaFontRenderer() {
- INIT_LOGD("Creating shader gamma font renderer");
- mRenderer = nullptr;
- mMultiGamma = multiGamma;
-}
-
-void ShaderGammaFontRenderer::describe(ProgramDescription& description,
- const SkPaint* paint) const {
- if (paint->getShader() == nullptr) {
- if (mMultiGamma) {
- const int l = luminance(paint);
-
- if (l <= mBlackThreshold) {
- description.hasGammaCorrection = true;
- description.gamma = mGamma;
- } else if (l >= mWhiteThreshold) {
- description.hasGammaCorrection = true;
- description.gamma = 1.0f / mGamma;
- }
- } else {
- description.hasGammaCorrection = true;
- description.gamma = 1.0f / mGamma;
- }
- }
-}
-
-void ShaderGammaFontRenderer::setupProgram(ProgramDescription& description,
- Program& program) const {
- if (description.hasGammaCorrection) {
- glUniform1f(program.getUniform("gamma"), description.gamma);
- }
-}
-
-void ShaderGammaFontRenderer::endPrecaching() {
- if (mRenderer) {
- mRenderer->endPrecaching();
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Lookup-based renderer
-///////////////////////////////////////////////////////////////////////////////
-
-LookupGammaFontRenderer::LookupGammaFontRenderer()
- : GammaFontRenderer() {
INIT_LOGD("Creating lookup gamma font renderer");
// Compute the gamma tables
- const float gamma = 1.0f / mGamma;
+ const float gamma = 1.0f / Properties::textGamma;
for (uint32_t i = 0; i <= 255; i++) {
mGammaTable[i] = uint8_t((float)::floor(pow(i / 255.0f, gamma) * 255.0f + 0.5f));
}
-
- mRenderer = nullptr;
}
-void LookupGammaFontRenderer::endPrecaching() {
+void GammaFontRenderer::endPrecaching() {
if (mRenderer) {
mRenderer->endPrecaching();
}
}
-///////////////////////////////////////////////////////////////////////////////
-// Lookup-based renderer, using 3 different correction tables
-///////////////////////////////////////////////////////////////////////////////
-
-Lookup3GammaFontRenderer::Lookup3GammaFontRenderer()
- : GammaFontRenderer() {
- INIT_LOGD("Creating lookup3 gamma font renderer");
-
- // Compute the gamma tables
- const float blackGamma = mGamma;
- const float whiteGamma = 1.0f / mGamma;
-
- for (uint32_t i = 0; i <= 255; i++) {
- const float v = i / 255.0f;
- const float black = pow(v, blackGamma);
- const float white = pow(v, whiteGamma);
-
- mGammaTable[i] = i;
- mGammaTable[256 + i] = uint8_t((float)::floor(black * 255.0f + 0.5f));
- mGammaTable[512 + i] = uint8_t((float)::floor(white * 255.0f + 0.5f));
- }
-
- memset(mRenderers, 0, sizeof(FontRenderer*) * kGammaCount);
- memset(mRenderersUsageCount, 0, sizeof(uint32_t) * kGammaCount);
-}
-
-void Lookup3GammaFontRenderer::endPrecaching() {
- for (int i = 0; i < kGammaCount; i++) {
- if (mRenderers[i]) {
- mRenderers[i]->endPrecaching();
- }
- }
-}
-
-void Lookup3GammaFontRenderer::clear() {
- for (int i = 0; i < kGammaCount; i++) {
- mRenderers[i].reset(nullptr);
- }
-}
-
-void Lookup3GammaFontRenderer::flush() {
- int count = 0;
- int min = -1;
- uint32_t minCount = UINT_MAX;
-
- for (int i = 0; i < kGammaCount; i++) {
- if (mRenderers[i]) {
- count++;
- if (mRenderersUsageCount[i] < minCount) {
- minCount = mRenderersUsageCount[i];
- min = i;
- }
- }
- }
-
- if (count <= 1 || min < 0) return;
-
- mRenderers[min].reset(nullptr);
-
- // Also eliminate the caches for large glyphs, as they consume significant memory
- for (int i = 0; i < kGammaCount; ++i) {
- if (mRenderers[i]) {
- mRenderers[i]->flushLargeCaches();
- }
- }
-}
-
-FontRenderer* Lookup3GammaFontRenderer::getRenderer(Gamma gamma) {
- if (!mRenderers[gamma]) {
- mRenderers[gamma].reset(new FontRenderer());
- mRenderers[gamma]->setGammaTable(&mGammaTable[gamma * 256]);
- }
- mRenderersUsageCount[gamma]++;
- return mRenderers[gamma].get();
-}
-
-FontRenderer& Lookup3GammaFontRenderer::getFontRenderer(const SkPaint* paint) {
- if (paint->getShader() == nullptr) {
- const int l = luminance(paint);
-
- if (l <= mBlackThreshold) {
- return *getRenderer(kGammaBlack);
- } else if (l >= mWhiteThreshold) {
- return *getRenderer(kGammaWhite);
- }
- }
- return *getRenderer(kGammaDefault);
-}
-
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h
index ca55bf1e74e0..5813e7f717ee 100644
--- a/libs/hwui/GammaFontRenderer.h
+++ b/libs/hwui/GammaFontRenderer.h
@@ -17,183 +17,44 @@
#ifndef ANDROID_HWUI_GAMMA_FONT_RENDERER_H
#define ANDROID_HWUI_GAMMA_FONT_RENDERER_H
-#include <SkPaint.h>
-
#include "FontRenderer.h"
#include "Program.h"
+#include <SkPaint.h>
+
namespace android {
namespace uirenderer {
class GammaFontRenderer {
public:
- virtual ~GammaFontRenderer();
-
- virtual void clear() = 0;
- virtual void flush() = 0;
-
- virtual FontRenderer& getFontRenderer(const SkPaint* paint) = 0;
-
- virtual uint32_t getFontRendererCount() const = 0;
- virtual uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const = 0;
-
- virtual void describe(ProgramDescription& description, const SkPaint* paint) const = 0;
- virtual void setupProgram(ProgramDescription& description, Program& program) const = 0;
-
- virtual void endPrecaching() = 0;
-
- static GammaFontRenderer* createRenderer();
-
-protected:
GammaFontRenderer();
- int mBlackThreshold;
- int mWhiteThreshold;
-
- float mGamma;
-};
-
-class ShaderGammaFontRenderer: public GammaFontRenderer {
-public:
- ~ShaderGammaFontRenderer() {
- delete mRenderer;
- }
-
- void clear() override {
- delete mRenderer;
- mRenderer = nullptr;
- }
-
- void flush() override {
- if (mRenderer) {
- mRenderer->flushLargeCaches();
- }
- }
-
- FontRenderer& getFontRenderer(const SkPaint* paint) override {
- if (!mRenderer) {
- mRenderer = new FontRenderer;
- }
- return *mRenderer;
- }
-
- uint32_t getFontRendererCount() const override {
- return 1;
- }
-
- uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const override {
- return mRenderer ? mRenderer->getCacheSize(format) : 0;
- }
-
- void describe(ProgramDescription& description, const SkPaint* paint) const override;
- void setupProgram(ProgramDescription& description, Program& program) const override;
-
- void endPrecaching() override;
-
-private:
- ShaderGammaFontRenderer(bool multiGamma);
-
- FontRenderer* mRenderer;
- bool mMultiGamma;
-
- friend class GammaFontRenderer;
-};
-
-class LookupGammaFontRenderer: public GammaFontRenderer {
-public:
- ~LookupGammaFontRenderer() {
- delete mRenderer;
- }
-
- void clear() override {
- delete mRenderer;
- mRenderer = nullptr;
+ void clear() {
+ mRenderer.reset(nullptr);
}
- void flush() override {
+ void flush() {
if (mRenderer) {
mRenderer->flushLargeCaches();
}
}
- FontRenderer& getFontRenderer(const SkPaint* paint) override {
+ FontRenderer& getFontRenderer() {
if (!mRenderer) {
- mRenderer = new FontRenderer;
- mRenderer->setGammaTable(&mGammaTable[0]);
+ mRenderer.reset(new FontRenderer(&mGammaTable[0]));
}
return *mRenderer;
}
- uint32_t getFontRendererCount() const override {
- return 1;
- }
-
- uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const override {
+ uint32_t getFontRendererSize(GLenum format) const {
return mRenderer ? mRenderer->getCacheSize(format) : 0;
}
- void describe(ProgramDescription& description, const SkPaint* paint) const override {
- }
-
- void setupProgram(ProgramDescription& description, Program& program) const override {
- }
-
- void endPrecaching() override;
+ void endPrecaching();
private:
- LookupGammaFontRenderer();
-
- FontRenderer* mRenderer;
+ std::unique_ptr<FontRenderer> mRenderer;
uint8_t mGammaTable[256];
-
- friend class GammaFontRenderer;
-};
-
-class Lookup3GammaFontRenderer: public GammaFontRenderer {
-public:
- void clear() override;
- void flush() override;
-
- FontRenderer& getFontRenderer(const SkPaint* paint) override;
-
- uint32_t getFontRendererCount() const override {
- return kGammaCount;
- }
-
- uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const override {
- if (fontRenderer >= kGammaCount) return 0;
-
- if (!mRenderers[fontRenderer]) return 0;
-
- return mRenderers[fontRenderer]->getCacheSize(format);
- }
-
- void describe(ProgramDescription& description, const SkPaint* paint) const override {
- }
-
- void setupProgram(ProgramDescription& description, Program& program) const override {
- }
-
- void endPrecaching() override;
-
-private:
- Lookup3GammaFontRenderer();
-
- enum Gamma {
- kGammaDefault = 0,
- kGammaBlack = 1,
- kGammaWhite = 2,
- kGammaCount = 3
- };
-
- FontRenderer* getRenderer(Gamma gamma);
-
- uint32_t mRenderersUsageCount[kGammaCount];
- std::unique_ptr<FontRenderer> mRenderers[kGammaCount];
-
- uint8_t mGammaTable[256 * kGammaCount];
-
- friend class GammaFontRenderer;
};
}; // namespace uirenderer
diff --git a/libs/hwui/GlFunctorLifecycleListener.h b/libs/hwui/GlFunctorLifecycleListener.h
new file mode 100644
index 000000000000..357090eb31ca
--- /dev/null
+++ b/libs/hwui/GlFunctorLifecycleListener.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 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 <utils/Functor.h>
+#include <utils/RefBase.h>
+
+namespace android {
+namespace uirenderer {
+
+class GlFunctorLifecycleListener : public VirtualLightRefBase {
+public:
+ virtual ~GlFunctorLifecycleListener() {}
+ virtual void onGlFunctorReleased(Functor* functor) = 0;
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h
index fa20b0807a88..6a9663412a00 100644
--- a/libs/hwui/Glop.h
+++ b/libs/hwui/Glop.h
@@ -64,7 +64,7 @@ namespace TransformFlags {
// Canvas transform isn't applied to the mesh at draw time,
//since it's already built in.
- MeshIgnoresCanvasTransform = 1 << 1,
+ MeshIgnoresCanvasTransform = 1 << 1, // TODO: remove for HWUI_NEW_OPS
};
};
@@ -81,8 +81,10 @@ namespace TransformFlags {
* vertex/index/Texture/RoundRectClipState pointers prevent this from
* being safe.
*/
-// TODO: PREVENT_COPY_AND_ASSIGN(...) or similar
struct Glop {
+ PREVENT_COPY_AND_ASSIGN(Glop);
+public:
+ Glop() { }
struct Mesh {
GLuint primitiveMode; // GL_TRIANGLES and GL_TRIANGLE_STRIP supported
@@ -135,10 +137,6 @@ struct Glop {
} fill;
struct Transform {
- // Orthographic projection matrix for current FBO
- // TODO: move out of Glop, since this is static per FBO
- Matrix4 ortho;
-
// modelView transform, accounting for delta between mesh transform and content of the mesh
// often represents x/y offsets within command, or scaling for mesh unit size
Matrix4 modelView;
@@ -153,7 +151,7 @@ struct Glop {
}
} transform;
- const RoundRectClipState* roundRectClipState;
+ const RoundRectClipState* roundRectClipState = nullptr;
/**
* Blending to be used by this draw - both GL_NONE if blending is disabled.
@@ -165,11 +163,13 @@ struct Glop {
GLenum dst;
} blend;
+#if !HWUI_NEW_OPS
/**
* Bounds of the drawing command in layer space. Only mapped into layer
* space once GlopBuilder::build() is called.
*/
- Rect bounds;
+ Rect bounds; // TODO: remove for HWUI_NEW_OPS
+#endif
/**
* Additional render state to enumerate:
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index 288fed360162..0a8e3f3b4db3 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -70,6 +70,20 @@ GlopBuilder::GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop
// Mesh
////////////////////////////////////////////////////////////////////////////////
+GlopBuilder& GlopBuilder::setMeshTexturedIndexedVbo(GLuint vbo, GLsizei elementCount) {
+ TRIGGER_STAGE(kMeshStage);
+
+ mOutGlop->mesh.primitiveMode = GL_TRIANGLES;
+ mOutGlop->mesh.indices = { mRenderState.meshState().getQuadListIBO(), nullptr };
+ mOutGlop->mesh.vertices = {
+ vbo,
+ VertexAttribFlags::TextureCoord,
+ nullptr, (const void*) kMeshTextureOffset, nullptr,
+ kTextureVertexStride };
+ mOutGlop->mesh.elementCount = elementCount;
+ return *this;
+}
+
GlopBuilder& GlopBuilder::setMeshUnitQuad() {
TRIGGER_STAGE(kMeshStage);
@@ -87,7 +101,7 @@ GlopBuilder& GlopBuilder::setMeshUnitQuad() {
GlopBuilder& GlopBuilder::setMeshTexturedUnitQuad(const UvMapper* uvMapper) {
if (uvMapper) {
// can't use unit quad VBO, so build UV vertices manually
- return setMeshTexturedUvQuad(uvMapper, Rect(0, 0, 1, 1));
+ return setMeshTexturedUvQuad(uvMapper, Rect(1, 1));
}
TRIGGER_STAGE(kMeshStage);
@@ -274,7 +288,7 @@ void GlopBuilder::setFill(int color, float alphaScale,
SkXfermode::Mode mode;
SkScalar srcColorMatrix[20];
if (colorFilter->asColorMode(&color, &mode)) {
- mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::kColorBlend;
+ mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Blend;
mDescription.colorMode = mode;
const float alpha = SkColorGetA(color) / 255.0f;
@@ -286,7 +300,7 @@ void GlopBuilder::setFill(int color, float alphaScale,
alpha,
};
} else if (colorFilter->asColorMatrix(srcColorMatrix)) {
- mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::kColorMatrix;
+ mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Matrix;
float* colorMatrix = mOutGlop->fill.filter.matrix.matrix;
memcpy(colorMatrix, srcColorMatrix, 4 * sizeof(float));
@@ -305,7 +319,7 @@ void GlopBuilder::setFill(int color, float alphaScale,
LOG_ALWAYS_FATAL("unsupported ColorFilter");
}
} else {
- mOutGlop->fill.filterMode = ProgramDescription::kColorNone;
+ mOutGlop->fill.filterMode = ProgramDescription::ColorFilterMode::None;
}
}
@@ -435,7 +449,6 @@ GlopBuilder& GlopBuilder::setFillLayer(Texture& texture, const SkColorFilter* co
mOutGlop->fill.texture = { &texture,
GL_TEXTURE_2D, GL_LINEAR, GL_CLAMP_TO_EDGE, nullptr };
- mOutGlop->fill.color = { alpha, alpha, alpha, alpha };
setFill(SK_ColorWHITE, alpha, mode, modeUsage, nullptr, colorFilter);
@@ -449,7 +462,6 @@ GlopBuilder& GlopBuilder::setFillTextureLayer(Layer& layer, float alpha) {
mOutGlop->fill.texture = { &(layer.getTexture()),
layer.getRenderTarget(), GL_LINEAR, GL_CLAMP_TO_EDGE, &layer.getTexTransform() };
- mOutGlop->fill.color = { alpha, alpha, alpha, alpha };
setFill(SK_ColorWHITE, alpha, layer.getMode(), Blend::ModeOrderSwap::NoSwap,
nullptr, layer.getColorFilter());
@@ -459,17 +471,32 @@ GlopBuilder& GlopBuilder::setFillTextureLayer(Layer& layer, float alpha) {
return *this;
}
+GlopBuilder& GlopBuilder::setFillExternalTexture(Texture& texture, Matrix4& textureTransform) {
+ TRIGGER_STAGE(kFillStage);
+ REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
+
+ mOutGlop->fill.texture = { &texture,
+ GL_TEXTURE_EXTERNAL_OES, GL_LINEAR, GL_CLAMP_TO_EDGE,
+ &textureTransform };
+
+ setFill(SK_ColorWHITE, 1.0f, SkXfermode::kSrc_Mode, Blend::ModeOrderSwap::NoSwap,
+ nullptr, nullptr);
+
+ mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
+ mDescription.hasTextureTransform = true;
+ return *this;
+}
+
////////////////////////////////////////////////////////////////////////////////
// Transform
////////////////////////////////////////////////////////////////////////////////
-void GlopBuilder::setTransform(const Matrix4& ortho, const Matrix4& canvas,
- const int transformFlags) {
+GlopBuilder& GlopBuilder::setTransform(const Matrix4& canvas, const int transformFlags) {
TRIGGER_STAGE(kTransformStage);
- mOutGlop->transform.ortho.load(ortho);
- mOutGlop->transform.canvas.load(canvas);
+ mOutGlop->transform.canvas = canvas;
mOutGlop->transform.transformFlags = transformFlags;
+ return *this;
}
////////////////////////////////////////////////////////////////////////////////
@@ -481,7 +508,9 @@ GlopBuilder& GlopBuilder::setModelViewMapUnitToRect(const Rect destination) {
mOutGlop->transform.modelView.loadTranslate(destination.left, destination.top, 0.0f);
mOutGlop->transform.modelView.scale(destination.getWidth(), destination.getHeight(), 1.0f);
+#if !HWUI_NEW_OPS
mOutGlop->bounds = destination;
+#endif
return *this;
}
@@ -505,7 +534,9 @@ GlopBuilder& GlopBuilder::setModelViewMapUnitToRectSnap(const Rect destination)
mOutGlop->transform.modelView.loadTranslate(left, top, 0.0f);
mOutGlop->transform.modelView.scale(destination.getWidth(), destination.getHeight(), 1.0f);
+#if !HWUI_NEW_OPS
mOutGlop->bounds = destination;
+#endif
return *this;
}
@@ -513,8 +544,10 @@ GlopBuilder& GlopBuilder::setModelViewOffsetRect(float offsetX, float offsetY, c
TRIGGER_STAGE(kModelViewStage);
mOutGlop->transform.modelView.loadTranslate(offsetX, offsetY, 0.0f);
+#if !HWUI_NEW_OPS
mOutGlop->bounds = source;
mOutGlop->bounds.translate(offsetX, offsetY);
+#endif
return *this;
}
@@ -534,8 +567,10 @@ GlopBuilder& GlopBuilder::setModelViewOffsetRectSnap(float offsetX, float offset
}
mOutGlop->transform.modelView.loadTranslate(offsetX, offsetY, 0.0f);
+#if !HWUI_NEW_OPS
mOutGlop->bounds = source;
mOutGlop->bounds.translate(offsetX, offsetY);
+#endif
return *this;
}
@@ -615,7 +650,7 @@ void GlopBuilder::build() {
shaderMatrix.loadInverse(mOutGlop->transform.canvas);
shaderMatrix.multiply(mOutGlop->transform.modelView);
} else {
- shaderMatrix.load(mOutGlop->transform.modelView);
+ shaderMatrix = mOutGlop->transform.modelView;
}
SkiaShader::store(mCaches, *mShader, shaderMatrix,
&textureUnit, &mDescription, &(mOutGlop->fill.skiaShaderData));
@@ -632,7 +667,51 @@ void GlopBuilder::build() {
// Final step: populate program and map bounds into render target space
mOutGlop->fill.program = mCaches.programCache.get(mDescription);
+#if !HWUI_NEW_OPS
mOutGlop->transform.meshTransform().mapRect(mOutGlop->bounds);
+#endif
+}
+
+void GlopBuilder::dump(const Glop& glop) {
+ ALOGD("Glop Mesh");
+ const Glop::Mesh& mesh = glop.mesh;
+ ALOGD(" primitive mode: %d", mesh.primitiveMode);
+ ALOGD(" indices: buffer obj %x, indices %p", mesh.indices.bufferObject, mesh.indices.indices);
+
+ const Glop::Mesh::Vertices& vertices = glop.mesh.vertices;
+ ALOGD(" vertices: buffer obj %x, flags %x, pos %p, tex %p, clr %p, stride %d",
+ vertices.bufferObject, vertices.attribFlags,
+ vertices.position, vertices.texCoord, vertices.color, vertices.stride);
+ ALOGD(" element count: %d", mesh.elementCount);
+
+ ALOGD("Glop Fill");
+ const Glop::Fill& fill = glop.fill;
+ ALOGD(" program %p", fill.program);
+ if (fill.texture.texture) {
+ ALOGD(" texture %p, target %d, filter %d, clamp %d",
+ fill.texture.texture, fill.texture.target, fill.texture.filter, fill.texture.clamp);
+ if (fill.texture.textureTransform) {
+ fill.texture.textureTransform->dump("texture transform");
+ }
+ }
+ ALOGD_IF(fill.colorEnabled, " color (argb) %.2f %.2f %.2f %.2f",
+ fill.color.a, fill.color.r, fill.color.g, fill.color.b);
+ ALOGD_IF(fill.filterMode != ProgramDescription::ColorFilterMode::None,
+ " filterMode %d", (int)fill.filterMode);
+ ALOGD_IF(fill.skiaShaderData.skiaShaderType, " shader type %d",
+ fill.skiaShaderData.skiaShaderType);
+
+ ALOGD("Glop transform");
+ glop.transform.modelView.dump(" model view");
+ glop.transform.canvas.dump(" canvas");
+ ALOGD_IF(glop.transform.transformFlags, " transformFlags 0x%x", glop.transform.transformFlags);
+
+ ALOGD_IF(glop.roundRectClipState, "Glop RRCS %p", glop.roundRectClipState);
+
+ ALOGD("Glop blend %d %d", glop.blend.src, glop.blend.dst);
+#if !HWUI_NEW_OPS
+ ALOGD("Glop bounds " RECT_STRING, RECT_ARGS(glop.bounds));
+#endif
}
} /* namespace uirenderer */
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 549bb21e5f8d..a9dd56f385b1 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -47,12 +47,13 @@ class GlopBuilder {
public:
GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop);
+ GlopBuilder& setMeshTexturedIndexedVbo(GLuint vbo, GLsizei elementCount);
GlopBuilder& setMeshUnitQuad();
GlopBuilder& setMeshTexturedUnitQuad(const UvMapper* uvMapper);
GlopBuilder& setMeshTexturedUvQuad(const UvMapper* uvMapper, const Rect uvs);
GlopBuilder& setMeshVertexBuffer(const VertexBuffer& vertexBuffer, bool shadowInterp);
GlopBuilder& setMeshIndexedQuads(Vertex* vertexData, int quadCount);
- GlopBuilder& setMeshTexturedMesh(TextureVertex* vertexData, int elementCount); // TODO: use indexed quads
+ GlopBuilder& setMeshTexturedMesh(TextureVertex* vertexData, int elementCount); // TODO: delete
GlopBuilder& setMeshColoredTexturedMesh(ColorTextureVertex* vertexData, int elementCount); // TODO: use indexed quads
GlopBuilder& setMeshTexturedIndexedQuads(TextureVertex* vertexData, int elementCount); // TODO: take quadCount
GlopBuilder& setMeshPatchQuads(const Patch& patch);
@@ -69,11 +70,15 @@ public:
GlopBuilder& setFillLayer(Texture& texture, const SkColorFilter* colorFilter,
float alpha, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage);
GlopBuilder& setFillTextureLayer(Layer& layer, float alpha);
+ // TODO: Texture should probably know and own its target.
+ // setFillLayer() forces it to GL_TEXTURE which isn't always correct.
+ // Similarly setFillLayer normally forces its own wrap & filter mode
+ GlopBuilder& setFillExternalTexture(Texture& texture, Matrix4& textureTransform);
GlopBuilder& setTransform(const Snapshot& snapshot, const int transformFlags) {
- setTransform(snapshot.getOrthoMatrix(), *snapshot.transform, transformFlags);
- return *this;
+ return setTransform(*snapshot.transform, transformFlags);
}
+ GlopBuilder& setTransform(const Matrix4& canvas, const int transformFlags);
GlopBuilder& setModelViewMapUnitToRect(const Rect destination);
GlopBuilder& setModelViewMapUnitToRectSnap(const Rect destination);
@@ -94,16 +99,20 @@ public:
return setModelViewOffsetRect(offsetX, offsetY, source);
}
}
+ GlopBuilder& setModelViewIdentityEmptyBounds() {
+ // pass empty rect since not needed for damage / snap
+ return setModelViewOffsetRect(0, 0, Rect());
+ }
GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState);
void build();
+
+ static void dump(const Glop& glop);
private:
void setFill(int color, float alphaScale,
SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage,
const SkShader* shader, const SkColorFilter* colorFilter);
- void setTransform(const Matrix4& ortho, const Matrix4& canvas,
- const int transformFlags);
enum StageFlags {
kInitialStage = 0,
diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp
new file mode 100644
index 000000000000..4fb57019264d
--- /dev/null
+++ b/libs/hwui/GpuMemoryTracker.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2016 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/StringUtils.h"
+#include "Texture.h"
+
+#include <cutils/compiler.h>
+#include <GpuMemoryTracker.h>
+#include <utils/Trace.h>
+#include <array>
+#include <sstream>
+#include <unordered_set>
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+pthread_t gGpuThread = 0;
+
+#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount)
+
+const char* TYPE_NAMES[] = {
+ "Texture",
+ "OffscreenBuffer",
+ "Layer",
+};
+
+struct TypeStats {
+ int totalSize = 0;
+ int count = 0;
+};
+
+static std::array<TypeStats, NUM_TYPES> gObjectStats;
+static std::unordered_set<GpuMemoryTracker*> gObjectSet;
+
+void GpuMemoryTracker::notifySizeChanged(int newSize) {
+ int delta = newSize - mSize;
+ mSize = newSize;
+ gObjectStats[static_cast<int>(mType)].totalSize += delta;
+}
+
+void GpuMemoryTracker::startTrackingObject() {
+ auto result = gObjectSet.insert(this);
+ LOG_ALWAYS_FATAL_IF(!result.second,
+ "startTrackingObject() on %p failed, already being tracked!", this);
+ gObjectStats[static_cast<int>(mType)].count++;
+}
+
+void GpuMemoryTracker::stopTrackingObject() {
+ size_t removed = gObjectSet.erase(this);
+ LOG_ALWAYS_FATAL_IF(removed != 1,
+ "stopTrackingObject removed %zd, is %p not being tracked?",
+ removed, this);
+ gObjectStats[static_cast<int>(mType)].count--;
+}
+
+void GpuMemoryTracker::onGLContextCreated() {
+ LOG_ALWAYS_FATAL_IF(gGpuThread != 0, "We already have a GL thread? "
+ "current = %lu, gl thread = %lu", pthread_self(), gGpuThread);
+ gGpuThread = pthread_self();
+}
+
+void GpuMemoryTracker::onGLContextDestroyed() {
+ gGpuThread = 0;
+ if (CC_UNLIKELY(gObjectSet.size() > 0)) {
+ std::stringstream os;
+ dump(os);
+ ALOGE("%s", os.str().c_str());
+ LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size());
+ }
+}
+
+void GpuMemoryTracker::dump() {
+ std::stringstream strout;
+ dump(strout);
+ ALOGD("%s", strout.str().c_str());
+}
+
+void GpuMemoryTracker::dump(std::ostream& stream) {
+ for (int type = 0; type < NUM_TYPES; type++) {
+ const TypeStats& stats = gObjectStats[type];
+ stream << TYPE_NAMES[type];
+ stream << " is using " << SizePrinter{stats.totalSize};
+ stream << ", count = " << stats.count;
+ stream << std::endl;
+ }
+}
+
+int GpuMemoryTracker::getInstanceCount(GpuObjectType type) {
+ return gObjectStats[static_cast<int>(type)].count;
+}
+
+int GpuMemoryTracker::getTotalSize(GpuObjectType type) {
+ return gObjectStats[static_cast<int>(type)].totalSize;
+}
+
+void GpuMemoryTracker::onFrameCompleted() {
+ if (ATRACE_ENABLED()) {
+ char buf[128];
+ for (int type = 0; type < NUM_TYPES; type++) {
+ snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]);
+ const TypeStats& stats = gObjectStats[type];
+ ATRACE_INT(buf, stats.totalSize);
+ snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]);
+ ATRACE_INT(buf, stats.count);
+ }
+ }
+
+ std::vector<const Texture*> freeList;
+ for (const auto& obj : gObjectSet) {
+ if (obj->objectType() == GpuObjectType::Texture) {
+ const Texture* texture = static_cast<Texture*>(obj);
+ if (texture->cleanup) {
+ ALOGE("Leaked texture marked for cleanup! id=%u, size %ux%u",
+ texture->id(), texture->width(), texture->height());
+ freeList.push_back(texture);
+ }
+ }
+ }
+ for (auto& texture : freeList) {
+ const_cast<Texture*>(texture)->deleteTexture();
+ delete texture;
+ }
+}
+
+} // namespace uirenderer
+} // namespace android;
diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h
new file mode 100644
index 000000000000..851aeae8352c
--- /dev/null
+++ b/libs/hwui/GpuMemoryTracker.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 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 <cutils/log.h>
+#include <pthread.h>
+#include <ostream>
+
+namespace android {
+namespace uirenderer {
+
+extern pthread_t gGpuThread;
+
+#define ASSERT_GPU_THREAD() LOG_ALWAYS_FATAL_IF( \
+ !pthread_equal(gGpuThread, pthread_self()), \
+ "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \
+ "!= gpu thread %lu", this, static_cast<int>(mType), mSize, \
+ pthread_self(), gGpuThread)
+
+enum class GpuObjectType {
+ Texture = 0,
+ OffscreenBuffer,
+ Layer,
+
+ TypeCount,
+};
+
+class GpuMemoryTracker {
+public:
+ GpuObjectType objectType() { return mType; }
+ int objectSize() { return mSize; }
+
+ static void onGLContextCreated();
+ static void onGLContextDestroyed();
+ static void dump();
+ static void dump(std::ostream& stream);
+ static int getInstanceCount(GpuObjectType type);
+ static int getTotalSize(GpuObjectType type);
+ static void onFrameCompleted();
+
+protected:
+ GpuMemoryTracker(GpuObjectType type) : mType(type) {
+ ASSERT_GPU_THREAD();
+ startTrackingObject();
+ }
+
+ ~GpuMemoryTracker() {
+ notifySizeChanged(0);
+ stopTrackingObject();
+ }
+
+ void notifySizeChanged(int newSize);
+
+private:
+ void startTrackingObject();
+ void stopTrackingObject();
+
+ int mSize = 0;
+ GpuObjectType mType;
+};
+
+} // namespace uirenderer
+} // namespace android;
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index ea93e7f9716b..c8f5e9435594 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <utils/JenkinsHash.h>
#include "Caches.h"
@@ -23,6 +21,8 @@
#include "GradientCache.h"
#include "Properties.h"
+#include <cutils/properties.h>
+
namespace android {
namespace uirenderer {
@@ -65,17 +65,9 @@ int GradientCacheEntry::compare(const GradientCacheEntry& lhs, const GradientCac
GradientCache::GradientCache(Extensions& extensions)
: mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity)
, mSize(0)
- , mMaxSize(MB(DEFAULT_GRADIENT_CACHE_SIZE))
+ , mMaxSize(Properties::gradientCacheSize)
, mUseFloatTexture(extensions.hasFloatTextures())
, mHasNpot(extensions.hasNPot()){
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_GRADIENT_CACHE_SIZE, property, nullptr) > 0) {
- INIT_LOGD(" Setting gradient cache size to %sMB", property);
- setMaxSize(MB(atof(property)));
- } else {
- INIT_LOGD(" Using default gradient cache size of %.2fMB", DEFAULT_GRADIENT_CACHE_SIZE);
- }
-
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
mCache.setOnEntryRemovedListener(this);
@@ -97,22 +89,13 @@ uint32_t GradientCache::getMaxSize() {
return mMaxSize;
}
-void GradientCache::setMaxSize(uint32_t maxSize) {
- mMaxSize = maxSize;
- while (mSize > mMaxSize) {
- mCache.removeOldest();
- }
-}
-
///////////////////////////////////////////////////////////////////////////////
// Callbacks
///////////////////////////////////////////////////////////////////////////////
void GradientCache::operator()(GradientCacheEntry&, Texture*& texture) {
if (texture) {
- const uint32_t size = texture->width * texture->height * bytesPerPixel();
- mSize -= size;
-
+ mSize -= texture->objectSize();
texture->deleteTexture();
delete texture;
}
@@ -167,20 +150,25 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient,
getGradientInfo(colors, count, info);
Texture* texture = new Texture(Caches::getInstance());
- texture->width = info.width;
- texture->height = 2;
texture->blend = info.hasAlpha;
texture->generation = 1;
- // Asume the cache is always big enough
- const uint32_t size = texture->width * texture->height * bytesPerPixel();
+ // Assume the cache is always big enough
+ const uint32_t size = info.width * 2 * bytesPerPixel();
while (getSize() + size > mMaxSize) {
- mCache.removeOldest();
+ LOG_ALWAYS_FATAL_IF(!mCache.removeOldest(),
+ "Ran out of things to remove from the cache? getSize() = %" PRIu32
+ ", size = %" PRIu32 ", mMaxSize = %" PRIu32 ", width = %" PRIu32,
+ getSize(), size, mMaxSize, info.width);
}
- generateTexture(colors, positions, texture);
+ generateTexture(colors, positions, info.width, 2, texture);
mSize += size;
+ LOG_ALWAYS_FATAL_IF((int)size != texture->objectSize(),
+ "size != texture->objectSize(), size %" PRIu32 ", objectSize %d"
+ " width = %" PRIu32 " bytesPerPixel() = %zu",
+ size, texture->objectSize(), info.width, bytesPerPixel());
mCache.put(gradient, texture);
return texture;
@@ -231,10 +219,10 @@ void GradientCache::mixFloats(GradientColor& start, GradientColor& end, float am
dst += 4 * sizeof(float);
}
-void GradientCache::generateTexture(uint32_t* colors, float* positions, Texture* texture) {
- const uint32_t width = texture->width;
+void GradientCache::generateTexture(uint32_t* colors, float* positions,
+ const uint32_t width, const uint32_t height, Texture* texture) {
const GLsizei rowBytes = width * bytesPerPixel();
- uint8_t pixels[rowBytes * texture->height];
+ uint8_t pixels[rowBytes * height];
static ChannelSplitter gSplitters[] = {
&android::uirenderer::GradientCache::splitToBytes,
@@ -277,17 +265,11 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, Texture*
memcpy(pixels + rowBytes, pixels, rowBytes);
- glGenTextures(1, &texture->id);
- Caches::getInstance().textureState().bindTexture(texture->id);
- glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
-
if (mUseFloatTexture) {
// We have to use GL_RGBA16F because GL_RGBA32F does not support filtering
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, texture->height, 0,
- GL_RGBA, GL_FLOAT, pixels);
+ texture->upload(GL_RGBA16F, width, height, GL_RGBA, GL_FLOAT, pixels);
} else {
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0,
- GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+ texture->upload(GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
texture->setFilter(GL_LINEAR);
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
index 08319ea1ec9b..dccd45072522 100644
--- a/libs/hwui/GradientCache.h
+++ b/libs/hwui/GradientCache.h
@@ -25,7 +25,6 @@
#include <utils/LruCache.h>
#include <utils/Mutex.h>
-#include <utils/Vector.h>
namespace android {
namespace uirenderer {
@@ -124,10 +123,6 @@ public:
void clear();
/**
- * Sets the maximum size of the cache in bytes.
- */
- void setMaxSize(uint32_t maxSize);
- /**
* Returns the maximum size of the cache in bytes.
*/
uint32_t getMaxSize();
@@ -144,7 +139,8 @@ private:
Texture* addLinearGradient(GradientCacheEntry& gradient,
uint32_t* colors, float* positions, int count);
- void generateTexture(uint32_t* colors, float* positions, Texture* texture);
+ void generateTexture(uint32_t* colors, float* positions,
+ const uint32_t width, const uint32_t height, Texture* texture);
struct GradientInfo {
uint32_t width;
@@ -177,13 +173,12 @@ private:
LruCache<GradientCacheEntry, Texture*> mCache;
uint32_t mSize;
- uint32_t mMaxSize;
+ const uint32_t mMaxSize;
GLint mMaxTextureSize;
bool mUseFloatTexture;
bool mHasNpot;
- Vector<SkShader*> mGarbage;
mutable Mutex mLock;
}; // class GradientCache
diff --git a/libs/hwui/Image.cpp b/libs/hwui/Image.cpp
index a31c54675f8a..68a356ba1be0 100644
--- a/libs/hwui/Image.cpp
+++ b/libs/hwui/Image.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <utils/Log.h>
#include "Caches.h"
diff --git a/libs/hwui/Interpolator.cpp b/libs/hwui/Interpolator.cpp
index e1b0fc3937c5..cc47f0052b73 100644
--- a/libs/hwui/Interpolator.cpp
+++ b/libs/hwui/Interpolator.cpp
@@ -16,11 +16,11 @@
#include "Interpolator.h"
-#include <cmath>
-#include <cutils/log.h>
-
#include "utils/MathUtils.h"
+#include <algorithm>
+#include <cutils/log.h>
+
namespace android {
namespace uirenderer {
@@ -106,7 +106,7 @@ float LUTInterpolator::interpolate(float input) {
weight = modff(lutpos, &ipart);
int i1 = (int) ipart;
- int i2 = MathUtils::min(i1 + 1, (int) mSize - 1);
+ int i2 = std::min(i1 + 1, (int) mSize - 1);
LOG_ALWAYS_FATAL_IF(i1 < 0 || i2 < 0, "negatives in interpolation!"
" i1=%d, i2=%d, input=%f, lutpos=%f, size=%zu, values=%p, ipart=%f, weight=%f",
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index eb9b55f196bb..ebe9c4240221 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -15,12 +15,16 @@
*/
#include "JankTracker.h"
+#include "Properties.h"
+
#include <algorithm>
#include <cutils/ashmem.h>
#include <cutils/log.h>
#include <cstdio>
#include <errno.h>
#include <inttypes.h>
+#include <limits>
+#include <cmath>
#include <sys/mman.h>
namespace android {
@@ -52,32 +56,35 @@ static const Comparison COMPARISONS[] = {
static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10);
/*
- * Frames that are exempt from jank metrics.
- * First-draw frames, for example, are expected to
- * be slow, this is hidden from the user with window animations and
- * other tricks
- *
- * Similarly, we don't track direct-drawing via Surface:lockHardwareCanvas()
+ * We don't track direct-drawing via Surface:lockHardwareCanvas()
* for now
*
* TODO: kSurfaceCanvas can negatively impact other drawing by using up
* time on the RenderThread, figure out how to attribute that as a jank-causer
*/
-static const int64_t EXEMPT_FRAMES_FLAGS
- = FrameInfoFlags::WindowLayoutChanged
- | FrameInfoFlags::SurfaceCanvas;
+static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::SurfaceCanvas;
// The bucketing algorithm controls so to speak
// If a frame is <= to this it goes in bucket 0
-static const uint32_t kBucketMinThreshold = 7;
+static const uint32_t kBucketMinThreshold = 5;
// If a frame is > this, start counting in increments of 2ms
static const uint32_t kBucket2msIntervals = 32;
// If a frame is > this, start counting in increments of 4ms
static const uint32_t kBucket4msIntervals = 48;
+// For testing purposes to try and eliminate test infra overhead we will
+// consider any unknown delay of frame start as part of the test infrastructure
+// and filter it out of the frame profile data
+static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync;
+
+// The interval of the slow frame histogram
+static const uint32_t kSlowFrameBucketIntervalMs = 50;
+// The start point of the slow frame bucket in ms
+static const uint32_t kSlowFrameBucketStartMs = 150;
+
// This will be called every frame, performance sensitive
// Uses bit twiddling to avoid branching while achieving the packing desired
-static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime, uint32_t max) {
+static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) {
uint32_t index = static_cast<uint32_t>(ns2ms(frameTime));
// If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result
// of negating 1 (twos compliment, yaay) else mask will be 0
@@ -95,7 +102,7 @@ static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime, uint32_t max) {
// be a pretty garbage value right now. However, mask is 0 so we'll end
// up with the desired result of 0.
index = (index - kBucketMinThreshold) & mask;
- return index < max ? index : max;
+ return index;
}
// Only called when dumping stats, less performance sensitive
@@ -205,21 +212,30 @@ void JankTracker::setFrameInterval(nsecs_t frameInterval) {
void JankTracker::addFrame(const FrameInfo& frame) {
mData->totalFrameCount++;
// Fast-path for jank-free frames
- int64_t totalDuration =
- frame[FrameInfoIndex::FrameCompleted] - frame[FrameInfoIndex::IntendedVsync];
- uint32_t framebucket = frameCountIndexForFrameTime(
- totalDuration, mData->frameCounts.size());
+ int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted);
+ uint32_t framebucket = frameCountIndexForFrameTime(totalDuration);
// Keep the fast path as fast as possible.
if (CC_LIKELY(totalDuration < mFrameInterval)) {
mData->frameCounts[framebucket]++;
return;
}
+ // Only things like Surface.lockHardwareCanvas() are exempt from tracking
if (frame[FrameInfoIndex::Flags] & EXEMPT_FRAMES_FLAGS) {
return;
}
- mData->frameCounts[framebucket]++;
+ if (framebucket <= mData->frameCounts.size()) {
+ mData->frameCounts[framebucket]++;
+ } else {
+ framebucket = (ns2ms(totalDuration) - kSlowFrameBucketStartMs)
+ / kSlowFrameBucketIntervalMs;
+ framebucket = std::min(framebucket,
+ static_cast<uint32_t>(mData->slowFrameCounts.size() - 1));
+ framebucket = std::max(framebucket, 0u);
+ mData->slowFrameCounts[framebucket]++;
+ }
+
mData->jankFrameCount++;
for (int i = 0; i < NUM_BUCKETS; i++) {
@@ -239,30 +255,53 @@ void JankTracker::dumpBuffer(const void* buffer, size_t bufsize, int fd) {
}
void JankTracker::dumpData(const ProfileData* data, int fd) {
+ if (sFrameStart != FrameInfoIndex::IntendedVsync) {
+ dprintf(fd, "\nNote: Data has been filtered!");
+ }
dprintf(fd, "\nStats since: %" PRIu64 "ns", data->statStartTime);
dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount);
dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount,
(float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f);
+ dprintf(fd, "\n50th percentile: %ums", findPercentile(data, 50));
dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90));
dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95));
dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99));
for (int i = 0; i < NUM_BUCKETS; i++) {
dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]);
}
+ dprintf(fd, "\nHISTOGRAM:");
+ for (size_t i = 0; i < data->frameCounts.size(); i++) {
+ dprintf(fd, " %ums=%u", frameTimeForFrameCountIndex(i),
+ data->frameCounts[i]);
+ }
+ for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
+ dprintf(fd, " %zums=%u", (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs,
+ data->slowFrameCounts[i]);
+ }
dprintf(fd, "\n");
}
void JankTracker::reset() {
mData->jankTypeCounts.fill(0);
mData->frameCounts.fill(0);
+ mData->slowFrameCounts.fill(0);
mData->totalFrameCount = 0;
mData->jankFrameCount = 0;
mData->statStartTime = systemTime(CLOCK_MONOTONIC);
+ sFrameStart = Properties::filterOutTestOverhead
+ ? FrameInfoIndex::HandleInputStart
+ : FrameInfoIndex::IntendedVsync;
}
uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) {
int pos = percentile * data->totalFrameCount / 100;
int remaining = data->totalFrameCount - pos;
+ for (int i = data->slowFrameCounts.size() - 1; i >= 0; i--) {
+ remaining -= data->slowFrameCounts[i];
+ if (remaining <= 0) {
+ return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
+ }
+ }
for (int i = data->frameCounts.size() - 1; i >= 0; i--) {
remaining -= data->frameCounts[i];
if (remaining <= 0) {
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index 3887e5e65d60..84b8c3f3f155 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -44,7 +44,9 @@ enum JankType {
struct ProfileData {
std::array<uint32_t, NUM_BUCKETS> jankTypeCounts;
// See comments on kBucket* constants for what this holds
- std::array<uint32_t, 55> frameCounts;
+ std::array<uint32_t, 57> frameCounts;
+ // Holds a histogram of frame times in 50ms increments from 150ms to 5s
+ std::array<uint16_t, 97> slowFrameCounts;
uint32_t totalFrameCount;
uint32_t jankFrameCount;
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index 62eeb43a2e2e..cdbbbab7730d 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include "Layer.h"
#include "Caches.h"
@@ -38,7 +36,8 @@ namespace android {
namespace uirenderer {
Layer::Layer(Type layerType, RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight)
- : state(kState_Uncached)
+ : GpuMemoryTracker(GpuObjectType::Layer)
+ , state(State::Uncached)
, caches(Caches::getInstance())
, renderState(renderState)
, texture(caches)
@@ -47,8 +46,8 @@ Layer::Layer(Type layerType, RenderState& renderState, uint32_t layerWidth, uint
// preserves the old inc/dec ref locations. This should be changed...
incStrong(nullptr);
renderTarget = GL_TEXTURE_2D;
- texture.width = layerWidth;
- texture.height = layerHeight;
+ texture.mWidth = layerWidth;
+ texture.mHeight = layerHeight;
renderState.registerLayer(this);
}
@@ -56,10 +55,9 @@ Layer::~Layer() {
renderState.unregisterLayer(this);
SkSafeUnref(colorFilter);
- if (stencil || fbo || texture.id) {
- renderState.requireGLContext();
+ if (stencil || fbo || texture.mId) {
removeFbo();
- deleteTexture();
+ texture.deleteTexture();
}
delete[] mesh;
@@ -67,7 +65,7 @@ Layer::~Layer() {
void Layer::onGlContextLost() {
removeFbo();
- deleteTexture();
+ texture.deleteTexture();
}
uint32_t Layer::computeIdealWidth(uint32_t layerWidth) {
@@ -157,8 +155,7 @@ void Layer::removeFbo(bool flush) {
if (fbo) {
if (flush) LayerRenderer::flushLayer(renderState, this);
- // If put fails the cache will delete the FBO
- caches.fboCache.put(fbo);
+ renderState.deleteFramebuffer(fbo);
fbo = 0;
}
}
@@ -172,7 +169,8 @@ void Layer::updateDeferred(RenderNode* renderNode, int left, int top, int right,
}
void Layer::setPaint(const SkPaint* paint) {
- OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode);
+ alpha = PaintUtils::getAlphaDirect(paint);
+ mode = PaintUtils::getXfermodeDirect(paint);
setColorFilter((paint) ? paint->getColorFilter() : nullptr);
}
@@ -181,8 +179,8 @@ void Layer::setColorFilter(SkColorFilter* filter) {
}
void Layer::bindTexture() const {
- if (texture.id) {
- caches.textureState().bindTexture(renderTarget, texture.id);
+ if (texture.mId) {
+ caches.textureState().bindTexture(renderTarget, texture.mId);
}
}
@@ -193,29 +191,27 @@ void Layer::bindStencilRenderBuffer() const {
}
void Layer::generateTexture() {
- if (!texture.id) {
- glGenTextures(1, &texture.id);
- }
-}
-
-void Layer::deleteTexture() {
- if (texture.id) {
- texture.deleteTexture();
- texture.id = 0;
+ if (!texture.mId) {
+ glGenTextures(1, &texture.mId);
}
}
void Layer::clearTexture() {
- caches.textureState().unbindTexture(texture.id);
- texture.id = 0;
+ // There's a rare possibility that Caches could have been destroyed already
+ // since this method is queued up as a task.
+ // Since this is a reset method, treat this as non-fatal.
+ if (caches.isInitialized()) {
+ caches.textureState().unbindTexture(texture.mId);
+ }
+ texture.mId = 0;
}
void Layer::allocateTexture() {
#if DEBUG_LAYERS
ALOGD(" Allocate layer: %dx%d", getWidth(), getHeight());
#endif
- if (texture.id) {
- glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ if (texture.mId) {
+ texture.updateSize(getWidth(), getHeight(), GL_RGBA);
glTexImage2D(renderTarget, 0, GL_RGBA, getWidth(), getHeight(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
}
@@ -238,8 +234,7 @@ void Layer::defer(const OpenGLRenderer& rootRenderer) {
DeferStateStruct deferredState(*deferredList, *renderer,
RenderNode::kReplayFlag_ClipChildren);
- renderer->setViewport(width, height);
- renderer->setupFrameState(dirtyRect.left, dirtyRect.top,
+ renderer->setupFrameState(width, height, dirtyRect.left, dirtyRect.top,
dirtyRect.right, dirtyRect.bottom, !isBlend());
renderNode->computeOrdering();
@@ -260,9 +255,8 @@ void Layer::flush() {
ATRACE_LAYER_WORK("Issue");
renderer->startMark((renderNode.get() != nullptr) ? renderNode->getName() : "Layer");
- renderer->setViewport(layer.getWidth(), layer.getHeight());
- renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom,
- !isBlend());
+ renderer->prepareDirty(layer.getWidth(), layer.getHeight(),
+ dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend());
deferredList->flush(*renderer, dirtyRect);
@@ -279,9 +273,8 @@ void Layer::render(const OpenGLRenderer& rootRenderer) {
ATRACE_LAYER_WORK("Direct-Issue");
updateLightPosFromRenderer(rootRenderer);
- renderer->setViewport(layer.getWidth(), layer.getHeight());
- renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom,
- !isBlend());
+ renderer->prepareDirty(layer.getWidth(), layer.getHeight(),
+ dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend());
renderer->drawRenderNode(renderNode.get(), dirtyRect, RenderNode::kReplayFlag_ClipChildren);
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index b670870ca55f..1e5498bb3d21 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -24,6 +24,7 @@
#include <memory>
#include <GLES2/gl2.h>
+#include <GpuMemoryTracker.h>
#include <ui/Region.h>
@@ -54,21 +55,21 @@ struct DeferStateStruct;
/**
* A layer has dimensions and is backed by an OpenGL texture or FBO.
*/
-class Layer : public VirtualLightRefBase {
+class Layer : public VirtualLightRefBase, GpuMemoryTracker {
public:
- enum Type {
- kType_Texture,
- kType_DisplayList,
+ enum class Type {
+ Texture,
+ DisplayList,
};
// layer lifecycle, controlled from outside
- enum State {
- kState_Uncached = 0,
- kState_InCache = 1,
- kState_FailedToCache = 2,
- kState_RemovedFromCache = 3,
- kState_DeletedFromCache = 4,
- kState_InGarbageList = 5,
+ enum class State {
+ Uncached = 0,
+ InCache = 1,
+ FailedToCache = 2,
+ RemovedFromCache = 3,
+ DeletedFromCache = 4,
+ InGarbageList = 5,
};
State state; // public for logging/debugging purposes
@@ -94,8 +95,8 @@ public:
regionRect.set(bounds.leftTop().x, bounds.leftTop().y,
bounds.rightBottom().x, bounds.rightBottom().y);
- const float texX = 1.0f / float(texture.width);
- const float texY = 1.0f / float(texture.height);
+ const float texX = 1.0f / float(texture.mWidth);
+ const float texY = 1.0f / float(texture.mHeight);
const float height = layer.getHeight();
texCoords.set(
regionRect.left * texX, (height - regionRect.top) * texY,
@@ -112,11 +113,11 @@ public:
void updateDeferred(RenderNode* renderNode, int left, int top, int right, int bottom);
inline uint32_t getWidth() const {
- return texture.width;
+ return texture.mWidth;
}
inline uint32_t getHeight() const {
- return texture.height;
+ return texture.mHeight;
}
/**
@@ -131,8 +132,7 @@ public:
bool resize(const uint32_t width, const uint32_t height);
void setSize(uint32_t width, uint32_t height) {
- texture.width = width;
- texture.height = height;
+ texture.updateSize(width, height, texture.format());
}
ANDROID_API void setPaint(const SkPaint* paint);
@@ -201,7 +201,7 @@ public:
}
inline GLuint getTextureId() const {
- return texture.id;
+ return texture.id();
}
inline Texture& getTexture() {
@@ -216,6 +216,10 @@ public:
this->renderTarget = renderTarget;
}
+ inline bool isRenderable() const {
+ return renderTarget != GL_NONE;
+ }
+
void setWrap(GLenum wrap, bool bindTexture = false, bool force = false) {
texture.setWrap(wrap, bindTexture, force, renderTarget);
}
@@ -241,7 +245,7 @@ public:
}
inline bool isTextureLayer() const {
- return type == kType_Texture;
+ return type == Type::Texture;
}
inline SkColorFilter* getColorFilter() const {
@@ -263,7 +267,6 @@ public:
void bindTexture() const;
void generateTexture();
void allocateTexture();
- void deleteTexture();
/**
* When the caller frees the texture itself, the caller
diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp
new file mode 100644
index 000000000000..30007772b1bc
--- /dev/null
+++ b/libs/hwui/LayerBuilder.cpp
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2016 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 "LayerBuilder.h"
+
+#include "BakedOpState.h"
+#include "RenderNode.h"
+#include "utils/PaintUtils.h"
+#include "utils/TraceUtils.h"
+
+#include <utils/TypeHelpers.h>
+
+namespace android {
+namespace uirenderer {
+
+class BatchBase {
+public:
+ BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
+ : mBatchId(batchId)
+ , mMerging(merging) {
+ mBounds = op->computedState.clippedBounds;
+ mOps.push_back(op);
+ }
+
+ bool intersects(const Rect& rect) const {
+ if (!rect.intersects(mBounds)) return false;
+
+ for (const BakedOpState* op : mOps) {
+ if (rect.intersects(op->computedState.clippedBounds)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ batchid_t getBatchId() const { return mBatchId; }
+ bool isMerging() const { return mMerging; }
+
+ const std::vector<BakedOpState*>& getOps() const { return mOps; }
+
+ void dump() const {
+ ALOGD(" Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING,
+ this, mBatchId, mMerging, (int) mOps.size(), RECT_ARGS(mBounds));
+ }
+protected:
+ batchid_t mBatchId;
+ Rect mBounds;
+ std::vector<BakedOpState*> mOps;
+ bool mMerging;
+};
+
+class OpBatch : public BatchBase {
+public:
+ OpBatch(batchid_t batchId, BakedOpState* op)
+ : BatchBase(batchId, op, false) {
+ }
+
+ void batchOp(BakedOpState* op) {
+ mBounds.unionWith(op->computedState.clippedBounds);
+ mOps.push_back(op);
+ }
+};
+
+class MergingOpBatch : public BatchBase {
+public:
+ MergingOpBatch(batchid_t batchId, BakedOpState* op)
+ : BatchBase(batchId, op, true)
+ , mClipSideFlags(op->computedState.clipSideFlags) {
+ }
+
+ /*
+ * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds
+ * and clip side flags. Positive bounds delta means new bounds fit in old.
+ */
+ static inline bool checkSide(const int currentFlags, const int newFlags, const int side,
+ float boundsDelta) {
+ bool currentClipExists = currentFlags & side;
+ bool newClipExists = newFlags & side;
+
+ // if current is clipped, we must be able to fit new bounds in current
+ if (boundsDelta > 0 && currentClipExists) return false;
+
+ // if new is clipped, we must be able to fit current bounds in new
+ if (boundsDelta < 0 && newClipExists) return false;
+
+ return true;
+ }
+
+ static bool paintIsDefault(const SkPaint& paint) {
+ return paint.getAlpha() == 255
+ && paint.getColorFilter() == nullptr
+ && paint.getShader() == nullptr;
+ }
+
+ static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) {
+ // Note: don't check color, since all currently mergeable ops can merge across colors
+ return a.getAlpha() == b.getAlpha()
+ && a.getColorFilter() == b.getColorFilter()
+ && a.getShader() == b.getShader();
+ }
+
+ /*
+ * Checks if a (mergeable) op can be merged into this batch
+ *
+ * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is
+ * important to consider all paint attributes used in the draw calls in deciding both a) if an
+ * op tries to merge at all, and b) if the op can merge with another set of ops
+ *
+ * False positives can lead to information from the paints of subsequent merged operations being
+ * dropped, so we make simplifying qualifications on the ops that can merge, per op type.
+ */
+ bool canMergeWith(BakedOpState* op) const {
+ bool isTextBatch = getBatchId() == OpBatchType::Text
+ || getBatchId() == OpBatchType::ColorText;
+
+ // Overlapping other operations is only allowed for text without shadow. For other ops,
+ // multiDraw isn't guaranteed to overdraw correctly
+ if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) {
+ if (intersects(op->computedState.clippedBounds)) return false;
+ }
+
+ const BakedOpState* lhs = op;
+ const BakedOpState* rhs = mOps[0];
+
+ if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false;
+
+ // Identical round rect clip state means both ops will clip in the same way, or not at all.
+ // As the state objects are const, we can compare their pointers to determine mergeability
+ if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
+
+ // Local masks prevent merge, since they're potentially in different coordinate spaces
+ if (lhs->computedState.localProjectionPathMask
+ || rhs->computedState.localProjectionPathMask) return false;
+
+ /* Clipping compatibility check
+ *
+ * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its
+ * clip for that side.
+ */
+ const int currentFlags = mClipSideFlags;
+ const int newFlags = op->computedState.clipSideFlags;
+ if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) {
+ const Rect& opBounds = op->computedState.clippedBounds;
+ float boundsDelta = mBounds.left - opBounds.left;
+ if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false;
+ boundsDelta = mBounds.top - opBounds.top;
+ if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false;
+
+ // right and bottom delta calculation reversed to account for direction
+ boundsDelta = opBounds.right - mBounds.right;
+ if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false;
+ boundsDelta = opBounds.bottom - mBounds.bottom;
+ if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false;
+ }
+
+ const SkPaint* newPaint = op->op->paint;
+ const SkPaint* oldPaint = mOps[0]->op->paint;
+
+ if (newPaint == oldPaint) {
+ // if paints are equal, then modifiers + paint attribs don't need to be compared
+ return true;
+ } else if (newPaint && !oldPaint) {
+ return paintIsDefault(*newPaint);
+ } else if (!newPaint && oldPaint) {
+ return paintIsDefault(*oldPaint);
+ }
+ return paintsAreEquivalent(*newPaint, *oldPaint);
+ }
+
+ void mergeOp(BakedOpState* op) {
+ mBounds.unionWith(op->computedState.clippedBounds);
+ mOps.push_back(op);
+
+ // Because a new op must have passed canMergeWith(), we know it's passed the clipping compat
+ // check, and doesn't extend past a side of the clip that's in use by the merged batch.
+ // Therefore it's safe to simply always merge flags, and use the bounds as the clip rect.
+ mClipSideFlags |= op->computedState.clipSideFlags;
+ }
+
+ int getClipSideFlags() const { return mClipSideFlags; }
+ const Rect& getClipRect() const { return mBounds; }
+
+private:
+ int mClipSideFlags;
+};
+
+LayerBuilder::LayerBuilder(uint32_t width, uint32_t height,
+ const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
+ : width(width)
+ , height(height)
+ , repaintRect(repaintRect)
+ , repaintClip(repaintRect)
+ , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
+ , beginLayerOp(beginLayerOp)
+ , renderNode(renderNode) {}
+
+// iterate back toward target to see if anything drawn since should overlap the new op
+// if no target, merging ops still iterate to find similar batch to insert after
+void LayerBuilder::locateInsertIndex(int batchId, const Rect& clippedBounds,
+ BatchBase** targetBatch, size_t* insertBatchIndex) const {
+ for (int i = mBatches.size() - 1; i >= 0; i--) {
+ BatchBase* overBatch = mBatches[i];
+
+ if (overBatch == *targetBatch) break;
+
+ // TODO: also consider shader shared between batch types
+ if (batchId == overBatch->getBatchId()) {
+ *insertBatchIndex = i + 1;
+ if (!*targetBatch) break; // found insert position, quit
+ }
+
+ if (overBatch->intersects(clippedBounds)) {
+ // NOTE: it may be possible to optimize for special cases where two operations
+ // of the same batch/paint could swap order, such as with a non-mergeable
+ // (clipped) and a mergeable text operation
+ *targetBatch = nullptr;
+ break;
+ }
+ }
+}
+
+void LayerBuilder::deferLayerClear(const Rect& rect) {
+ mClearRects.push_back(rect);
+}
+
+void LayerBuilder::onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState) {
+ if (bakedState->op->opId != RecordedOpId::CopyToLayerOp) {
+ // First non-CopyToLayer, so stop stashing up layer clears for unclipped save layers,
+ // and issue them together in one draw.
+ flushLayerClears(allocator);
+
+ if (CC_UNLIKELY(activeUnclippedSaveLayers.empty()
+ && bakedState->computedState.opaqueOverClippedBounds
+ && bakedState->computedState.clippedBounds.contains(repaintRect)
+ && !Properties::debugOverdraw)) {
+ // discard all deferred drawing ops, since new one will occlude them
+ clear();
+ }
+ }
+}
+
+void LayerBuilder::flushLayerClears(LinearAllocator& allocator) {
+ if (CC_UNLIKELY(!mClearRects.empty())) {
+ const int vertCount = mClearRects.size() * 4;
+ // put the verts in the frame allocator, since
+ // 1) SimpleRectsOps needs verts, not rects
+ // 2) even if mClearRects stored verts, std::vectors will move their contents
+ Vertex* const verts = (Vertex*) allocator.create_trivial_array<Vertex>(vertCount);
+
+ Vertex* currentVert = verts;
+ Rect bounds = mClearRects[0];
+ for (auto&& rect : mClearRects) {
+ bounds.unionWith(rect);
+ Vertex::set(currentVert++, rect.left, rect.top);
+ Vertex::set(currentVert++, rect.right, rect.top);
+ Vertex::set(currentVert++, rect.left, rect.bottom);
+ Vertex::set(currentVert++, rect.right, rect.bottom);
+ }
+ mClearRects.clear(); // discard rects before drawing so this method isn't reentrant
+
+ // One or more unclipped saveLayers have been enqueued, with deferred clears.
+ // Flush all of these clears with a single draw
+ SkPaint* paint = allocator.create<SkPaint>();
+ paint->setXfermodeMode(SkXfermode::kClear_Mode);
+ SimpleRectsOp* op = allocator.create_trivial<SimpleRectsOp>(bounds,
+ Matrix4::identity(), nullptr, paint,
+ verts, vertCount);
+ BakedOpState* bakedState = BakedOpState::directConstruct(allocator,
+ &repaintClip, bounds, *op);
+ deferUnmergeableOp(allocator, bakedState, OpBatchType::Vertices);
+ }
+}
+
+void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator,
+ BakedOpState* op, batchid_t batchId) {
+ onDeferOp(allocator, op);
+ OpBatch* targetBatch = mBatchLookup[batchId];
+
+ size_t insertBatchIndex = mBatches.size();
+ if (targetBatch) {
+ locateInsertIndex(batchId, op->computedState.clippedBounds,
+ (BatchBase**)(&targetBatch), &insertBatchIndex);
+ }
+
+ if (targetBatch) {
+ targetBatch->batchOp(op);
+ } else {
+ // new non-merging batch
+ targetBatch = allocator.create<OpBatch>(batchId, op);
+ mBatchLookup[batchId] = targetBatch;
+ mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
+ }
+}
+
+void LayerBuilder::deferMergeableOp(LinearAllocator& allocator,
+ BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
+ onDeferOp(allocator, op);
+ MergingOpBatch* targetBatch = nullptr;
+
+ // Try to merge with any existing batch with same mergeId
+ auto getResult = mMergingBatchLookup[batchId].find(mergeId);
+ if (getResult != mMergingBatchLookup[batchId].end()) {
+ targetBatch = getResult->second;
+ if (!targetBatch->canMergeWith(op)) {
+ targetBatch = nullptr;
+ }
+ }
+
+ size_t insertBatchIndex = mBatches.size();
+ locateInsertIndex(batchId, op->computedState.clippedBounds,
+ (BatchBase**)(&targetBatch), &insertBatchIndex);
+
+ if (targetBatch) {
+ targetBatch->mergeOp(op);
+ } else {
+ // new merging batch
+ targetBatch = allocator.create<MergingOpBatch>(batchId, op);
+ mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch));
+
+ mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
+ }
+}
+
+void LayerBuilder::replayBakedOpsImpl(void* arg,
+ BakedOpReceiver* unmergedReceivers, MergedOpReceiver* mergedReceivers) const {
+ ATRACE_NAME("flush drawing commands");
+ for (const BatchBase* batch : mBatches) {
+ size_t size = batch->getOps().size();
+ if (size > 1 && batch->isMerging()) {
+ int opId = batch->getOps()[0]->op->opId;
+ const MergingOpBatch* mergingBatch = static_cast<const MergingOpBatch*>(batch);
+ MergedBakedOpList data = {
+ batch->getOps().data(),
+ size,
+ mergingBatch->getClipSideFlags(),
+ mergingBatch->getClipRect()
+ };
+ mergedReceivers[opId](arg, data);
+ } else {
+ for (const BakedOpState* op : batch->getOps()) {
+ unmergedReceivers[op->op->opId](arg, *op);
+ }
+ }
+ }
+}
+
+void LayerBuilder::clear() {
+ mBatches.clear();
+ for (int i = 0; i < OpBatchType::Count; i++) {
+ mBatchLookup[i] = nullptr;
+ mMergingBatchLookup[i].clear();
+ }
+}
+
+void LayerBuilder::dump() const {
+ ALOGD("LayerBuilder %p, %ux%u buffer %p, blo %p, rn %p (%s)",
+ this, width, height, offscreenBuffer, beginLayerOp,
+ renderNode, renderNode ? renderNode->getName() : "-");
+ for (const BatchBase* batch : mBatches) {
+ batch->dump();
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/LayerBuilder.h b/libs/hwui/LayerBuilder.h
new file mode 100644
index 000000000000..4de432c5e7be
--- /dev/null
+++ b/libs/hwui/LayerBuilder.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 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 "ClipArea.h"
+#include "Rect.h"
+#include "utils/Macros.h"
+
+#include <vector>
+#include <unordered_map>
+
+struct SkRect;
+
+namespace android {
+namespace uirenderer {
+
+class BakedOpState;
+struct BeginLayerOp;
+class BatchBase;
+class LinearAllocator;
+struct MergedBakedOpList;
+class MergingOpBatch;
+class OffscreenBuffer;
+class OpBatch;
+class RenderNode;
+
+typedef int batchid_t;
+typedef const void* mergeid_t;
+
+namespace OpBatchType {
+ enum {
+ Bitmap,
+ MergedPatch,
+ AlphaVertices,
+ Vertices,
+ AlphaMaskTexture,
+ Text,
+ ColorText,
+ Shadow,
+ TextureLayer,
+ Functor,
+ CopyToLayer,
+ CopyFromLayer,
+
+ Count // must be last
+ };
+}
+
+typedef void (*BakedOpReceiver)(void*, const BakedOpState&);
+typedef void (*MergedOpReceiver)(void*, const MergedBakedOpList& opList);
+
+/**
+ * Stores the deferred render operations and state used to compute ordering
+ * for a single FBO/layer.
+ */
+class LayerBuilder {
+// Prevent copy/assign because users may stash pointer to offscreenBuffer and viewportClip
+PREVENT_COPY_AND_ASSIGN(LayerBuilder);
+public:
+ // Create LayerBuilder for Fbo0
+ LayerBuilder(uint32_t width, uint32_t height, const Rect& repaintRect)
+ : LayerBuilder(width, height, repaintRect, nullptr, nullptr) {};
+
+ // Create LayerBuilder for an offscreen layer, where beginLayerOp is present for a
+ // saveLayer, renderNode is present for a HW layer.
+ LayerBuilder(uint32_t width, uint32_t height,
+ const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+
+ // iterate back toward target to see if anything drawn since should overlap the new op
+ // if no target, merging ops still iterate to find similar batch to insert after
+ void locateInsertIndex(int batchId, const Rect& clippedBounds,
+ BatchBase** targetBatch, size_t* insertBatchIndex) const;
+
+ void deferUnmergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId);
+
+ // insertion point of a new batch, will hopefully be immediately after similar batch
+ // (generally, should be similar shader)
+ void deferMergeableOp(LinearAllocator& allocator,
+ BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
+
+ void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers, MergedOpReceiver*) const;
+
+ void deferLayerClear(const Rect& dstRect);
+
+ bool empty() const {
+ return mBatches.empty();
+ }
+
+ void clear();
+
+ void dump() const;
+
+ const uint32_t width;
+ const uint32_t height;
+ const Rect repaintRect;
+ const ClipRect repaintClip;
+ OffscreenBuffer* offscreenBuffer;
+ const BeginLayerOp* beginLayerOp;
+ const RenderNode* renderNode;
+
+ // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps
+ std::vector<BakedOpState*> activeUnclippedSaveLayers;
+private:
+ void onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState);
+ void flushLayerClears(LinearAllocator& allocator);
+
+ std::vector<BatchBase*> mBatches;
+
+ /**
+ * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
+ * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
+ * collide, which avoids the need to resolve mergeid collisions.
+ */
+ std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatchLookup[OpBatchType::Count];
+
+ // Maps batch ids to the most recent *non-merging* batch of that id
+ OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
+
+ std::vector<Rect> mClearRects;
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index bcbd4129b7e7..f5681ce712d5 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
+#include "LayerCache.h"
-#include <GLES2/gl2.h>
+#include "Caches.h"
+#include "Properties.h"
#include <utils/Log.h>
-#include "Caches.h"
-#include "LayerCache.h"
-#include "Properties.h"
+#include <GLES2/gl2.h>
namespace android {
namespace uirenderer {
@@ -31,15 +30,9 @@ namespace uirenderer {
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
-LayerCache::LayerCache(): mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, nullptr) > 0) {
- INIT_LOGD(" Setting layer cache size to %sMB", property);
- setMaxSize(MB(atof(property)));
- } else {
- INIT_LOGD(" Using default layer cache size of %.2fMB", DEFAULT_LAYER_CACHE_SIZE);
- }
-}
+LayerCache::LayerCache()
+ : mSize(0)
+ , mMaxSize(Properties::layerPoolSize) {}
LayerCache::~LayerCache() {
clear();
@@ -83,15 +76,14 @@ void LayerCache::deleteLayer(Layer* layer) {
LAYER_LOGD("Destroying layer %dx%d, fbo %d", layer->getWidth(), layer->getHeight(),
layer->getFbo());
mSize -= layer->getWidth() * layer->getHeight() * 4;
- layer->state = Layer::kState_DeletedFromCache;
+ layer->state = Layer::State::DeletedFromCache;
layer->decStrong(nullptr);
}
}
void LayerCache::clear() {
- size_t count = mCache.size();
- for (size_t i = 0; i < count; i++) {
- deleteLayer(mCache.itemAt(i).mLayer);
+ for (auto entry : mCache) {
+ deleteLayer(entry.mLayer);
}
mCache.clear();
}
@@ -100,27 +92,26 @@ Layer* LayerCache::get(RenderState& renderState, const uint32_t width, const uin
Layer* layer = nullptr;
LayerEntry entry(width, height);
- ssize_t index = mCache.indexOf(entry);
+ auto iter = mCache.find(entry);
- if (index >= 0) {
- entry = mCache.itemAt(index);
- mCache.removeAt(index);
+ if (iter != mCache.end()) {
+ entry = *iter;
+ mCache.erase(iter);
layer = entry.mLayer;
- layer->state = Layer::kState_RemovedFromCache;
+ layer->state = Layer::State::RemovedFromCache;
mSize -= layer->getWidth() * layer->getHeight() * 4;
LAYER_LOGD("Reusing layer %dx%d", layer->getWidth(), layer->getHeight());
} else {
LAYER_LOGD("Creating new layer %dx%d", entry.mWidth, entry.mHeight);
- layer = new Layer(Layer::kType_DisplayList, renderState, entry.mWidth, entry.mHeight);
+ layer = new Layer(Layer::Type::DisplayList, renderState, entry.mWidth, entry.mHeight);
layer->setBlend(true);
layer->generateTexture();
layer->bindTexture();
layer->setFilter(GL_NEAREST);
layer->setWrap(GL_CLAMP_TO_EDGE, false);
- glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
#if DEBUG_LAYERS
dump();
@@ -131,9 +122,7 @@ Layer* LayerCache::get(RenderState& renderState, const uint32_t width, const uin
}
void LayerCache::dump() {
- size_t size = mCache.size();
- for (size_t i = 0; i < size; i++) {
- const LayerEntry& entry = mCache.itemAt(i);
+ for (auto entry : mCache) {
ALOGD(" Layer size %dx%d", entry.mWidth, entry.mHeight);
}
}
@@ -146,13 +135,9 @@ bool LayerCache::put(Layer* layer) {
if (size < mMaxSize) {
// TODO: Use an LRU
while (mSize + size > mMaxSize) {
- size_t position = 0;
-#if LAYER_REMOVE_BIGGEST_FIRST
- position = mCache.size() - 1;
-#endif
- Layer* victim = mCache.itemAt(position).mLayer;
+ Layer* victim = mCache.begin()->mLayer;
deleteLayer(victim);
- mCache.removeAt(position);
+ mCache.erase(mCache.begin());
LAYER_LOGD(" Deleting layer %.2fx%.2f", victim->layer.getWidth(),
victim->layer.getHeight());
@@ -162,14 +147,14 @@ bool LayerCache::put(Layer* layer) {
LayerEntry entry(layer);
- mCache.add(entry);
+ mCache.insert(entry);
mSize += size;
- layer->state = Layer::kState_InCache;
+ layer->state = Layer::State::InCache;
return true;
}
- layer->state = Layer::kState_FailedToCache;
+ layer->state = Layer::State::FailedToCache;
return false;
}
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index 7d17b9ba41aa..6fe7b3aae859 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -19,7 +19,8 @@
#include "Debug.h"
#include "Layer.h"
-#include "utils/SortedList.h"
+
+#include <set>
namespace android {
namespace uirenderer {
@@ -118,12 +119,8 @@ private:
return compare(*this, other) != 0;
}
- friend inline int strictly_order_type(const LayerEntry& lhs, const LayerEntry& rhs) {
- return LayerEntry::compare(lhs, rhs) < 0;
- }
-
- friend inline int compare_type(const LayerEntry& lhs, const LayerEntry& rhs) {
- return LayerEntry::compare(lhs, rhs);
+ bool operator<(const LayerEntry& other) const {
+ return LayerEntry::compare(*this, other) < 0;
}
Layer* mLayer;
@@ -133,7 +130,7 @@ private:
void deleteLayer(Layer* layer);
- SortedList<LayerEntry> mCache;
+ std::multiset<LayerEntry> mCache;
uint32_t mSize;
uint32_t mMaxSize;
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 00add2903371..137316f57725 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -43,8 +43,8 @@ LayerRenderer::LayerRenderer(RenderState& renderState, Layer* layer)
LayerRenderer::~LayerRenderer() {
}
-void LayerRenderer::prepareDirty(float left, float top, float right, float bottom,
- bool opaque) {
+void LayerRenderer::prepareDirty(int viewportWidth, int viewportHeight,
+ float left, float top, float right, float bottom, bool opaque) {
LAYER_RENDERER_LOGD("Rendering into layer, fbo = %d", mLayer->getFbo());
mRenderState.bindFramebuffer(mLayer->getFbo());
@@ -58,13 +58,14 @@ void LayerRenderer::prepareDirty(float left, float top, float right, float botto
mLayer->region.clear();
dirty.set(0.0f, 0.0f, width, height);
} else {
- dirty.intersect(0.0f, 0.0f, width, height);
+ dirty.doIntersect(0.0f, 0.0f, width, height);
android::Rect r(dirty.left, dirty.top, dirty.right, dirty.bottom);
mLayer->region.subtractSelf(r);
}
mLayer->clipRect.set(dirty);
- OpenGLRenderer::prepareDirty(dirty.left, dirty.top, dirty.right, dirty.bottom, opaque);
+ OpenGLRenderer::prepareDirty(viewportWidth, viewportHeight,
+ dirty.left, dirty.top, dirty.right, dirty.bottom, opaque);
}
void LayerRenderer::clear(float left, float top, float right, float bottom, bool opaque) {
@@ -188,7 +189,7 @@ Layer* LayerRenderer::createRenderLayer(RenderState& renderState, uint32_t width
LAYER_RENDERER_LOGD("Requesting new render layer %dx%d", width, height);
Caches& caches = Caches::getInstance();
- GLuint fbo = caches.fboCache.get();
+ GLuint fbo = renderState.createFramebuffer();
if (!fbo) {
ALOGW("Could not obtain an FBO");
return nullptr;
@@ -203,7 +204,7 @@ Layer* LayerRenderer::createRenderLayer(RenderState& renderState, uint32_t width
// We first obtain a layer before comparing against the max texture size
// because layers are not allocated at the exact desired size. They are
- // always created slighly larger to improve recycling
+ // always created slightly larger to improve recycling
const uint32_t maxTextureSize = caches.maxTextureSize;
if (layer->getWidth() > maxTextureSize || layer->getHeight() > maxTextureSize) {
ALOGW("Layer exceeds max. dimensions supported by the GPU (%dx%d, max=%dx%d)",
@@ -272,7 +273,7 @@ bool LayerRenderer::resizeLayer(Layer* layer, uint32_t width, uint32_t height) {
Layer* LayerRenderer::createTextureLayer(RenderState& renderState) {
LAYER_RENDERER_LOGD("Creating new texture layer");
- Layer* layer = new Layer(Layer::kType_Texture, renderState, 0, 0);
+ Layer* layer = new Layer(Layer::Type::Texture, renderState, 0, 0);
layer->setCacheable(false);
layer->layer.set(0.0f, 0.0f, 0.0f, 0.0f);
layer->texCoords.set(0.0f, 1.0f, 1.0f, 0.0f);
@@ -286,7 +287,7 @@ Layer* LayerRenderer::createTextureLayer(RenderState& renderState) {
}
void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
- bool isOpaque, bool forceFilter, GLenum renderTarget, float* textureTransform) {
+ bool isOpaque, bool forceFilter, GLenum renderTarget, const float* textureTransform) {
if (layer) {
layer->setBlend(!isOpaque);
layer->setForceFilter(forceFilter);
@@ -352,11 +353,11 @@ void LayerRenderer::flushLayer(RenderState& renderState, Layer* layer) {
bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* bitmap) {
Caches& caches = Caches::getInstance();
- if (layer
+ if (layer && layer->isRenderable()
&& bitmap->width() <= caches.maxTextureSize
&& bitmap->height() <= caches.maxTextureSize) {
- GLuint fbo = caches.fboCache.get();
+ GLuint fbo = renderState.createFramebuffer();
if (!fbo) {
ALOGW("Could not obtain an FBO");
return false;
@@ -372,7 +373,6 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap*
GLenum format;
GLenum type;
- GLenum error = GL_NO_ERROR;
bool status = false;
switch (bitmap->colorType()) {
@@ -407,7 +407,6 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap*
renderState.bindFramebuffer(fbo);
glGenTextures(1, &texture);
- if ((error = glGetError()) != GL_NO_ERROR) goto error;
caches.textureState().activateTexture(0);
caches.textureState().bindTexture(texture);
@@ -422,24 +421,19 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap*
glTexImage2D(GL_TEXTURE_2D, 0, format, bitmap->width(), bitmap->height(),
0, format, type, nullptr);
- if ((error = glGetError()) != GL_NO_ERROR) goto error;
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, texture, 0);
- if ((error = glGetError()) != GL_NO_ERROR) goto error;
{
LayerRenderer renderer(renderState, layer);
- renderer.setViewport(bitmap->width(), bitmap->height());
- renderer.OpenGLRenderer::prepareDirty(0.0f, 0.0f,
- bitmap->width(), bitmap->height(), !layer->isBlend());
+ renderer.OpenGLRenderer::prepareDirty(bitmap->width(), bitmap->height(),
+ 0.0f, 0.0f, bitmap->width(), bitmap->height(), !layer->isBlend());
renderState.scissor().setEnabled(false);
renderer.translate(0.0f, bitmap->height());
renderer.scale(1.0f, -1.0f);
- if ((error = glGetError()) != GL_NO_ERROR) goto error;
-
{
Rect bounds;
bounds.set(0.0f, 0.0f, bitmap->width(), bitmap->height());
@@ -448,26 +442,20 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap*
glReadPixels(0, 0, bitmap->width(), bitmap->height(), format,
type, bitmap->getPixels());
- if ((error = glGetError()) != GL_NO_ERROR) goto error;
}
status = true;
}
-error:
-#if DEBUG_OPENGL
- if (error != GL_NO_ERROR) {
- ALOGD("GL error while copying layer into bitmap = 0x%x", error);
- }
-#endif
-
renderState.bindFramebuffer(previousFbo);
layer->setAlpha(alpha, mode);
layer->setFbo(previousLayerFbo);
caches.textureState().deleteTexture(texture);
- caches.fboCache.put(fbo);
+ renderState.deleteFramebuffer(fbo);
renderState.setViewport(previousViewportWidth, previousViewportHeight);
+ GL_CHECKPOINT(MODERATE);
+
return status;
}
return false;
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
index 47ded7e7e0bf..38c3705cfa25 100644
--- a/libs/hwui/LayerRenderer.h
+++ b/libs/hwui/LayerRenderer.h
@@ -50,8 +50,8 @@ public:
virtual ~LayerRenderer();
virtual void onViewportInitialized() override { /* do nothing */ }
- virtual void prepareDirty(float left, float top, float right, float bottom,
- bool opaque) override;
+ virtual void prepareDirty(int viewportWidth, int viewportHeight,
+ float left, float top, float right, float bottom, bool opaque) override;
virtual void clear(float left, float top, float right, float bottom, bool opaque) override;
virtual bool finish() override;
@@ -59,7 +59,7 @@ public:
static Layer* createRenderLayer(RenderState& renderState, uint32_t width, uint32_t height);
static bool resizeLayer(Layer* layer, uint32_t width, uint32_t height);
static void updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
- bool isOpaque, bool forceFilter, GLenum renderTarget, float* textureTransform);
+ bool isOpaque, bool forceFilter, GLenum renderTarget, const float* textureTransform);
static void destroyLayer(Layer* layer);
static bool copyLayer(RenderState& renderState, Layer* layer, SkBitmap* bitmap);
diff --git a/libs/hwui/LayerUpdateQueue.cpp b/libs/hwui/LayerUpdateQueue.cpp
new file mode 100644
index 000000000000..db5f676d09dc
--- /dev/null
+++ b/libs/hwui/LayerUpdateQueue.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 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 "LayerUpdateQueue.h"
+
+#include "RenderNode.h"
+
+namespace android {
+namespace uirenderer {
+
+void LayerUpdateQueue::clear() {
+ mEntries.clear();
+}
+
+void LayerUpdateQueue::enqueueLayerWithDamage(RenderNode* renderNode, Rect damage) {
+ damage.doIntersect(0, 0, renderNode->getWidth(), renderNode->getHeight());
+ if (!damage.isEmpty()) {
+ for (Entry& entry : mEntries) {
+ if (CC_UNLIKELY(entry.renderNode == renderNode)) {
+ entry.damage.unionWith(damage);
+ return;
+ }
+ }
+ mEntries.emplace_back(renderNode, damage);
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/LayerUpdateQueue.h b/libs/hwui/LayerUpdateQueue.h
new file mode 100644
index 000000000000..5b1a8543dd0d
--- /dev/null
+++ b/libs/hwui/LayerUpdateQueue.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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_LAYER_UPDATE_QUEUE_H
+#define ANDROID_HWUI_LAYER_UPDATE_QUEUE_H
+
+#include "Rect.h"
+#include "utils/Macros.h"
+
+#include <vector>
+#include <unordered_map>
+
+namespace android {
+namespace uirenderer {
+
+class RenderNode;
+
+class LayerUpdateQueue {
+ PREVENT_COPY_AND_ASSIGN(LayerUpdateQueue);
+public:
+ struct Entry {
+ Entry(RenderNode* renderNode, const Rect& damage)
+ : renderNode(renderNode)
+ , damage(damage) {}
+ RenderNode* renderNode;
+ Rect damage;
+ };
+
+ LayerUpdateQueue() {}
+ void enqueueLayerWithDamage(RenderNode* renderNode, Rect dirty);
+ void clear();
+ const std::vector<Entry>& entries() const { return mEntries; }
+private:
+ std::vector<Entry> mEntries;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_LAYER_UPDATE_QUEUE_H
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index 06e67c0e85ad..709156c4098b 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <math.h>
#include <stdlib.h>
#include <string.h>
@@ -154,10 +152,6 @@ void Matrix4::load(const float* v) {
mType = kTypeUnknown;
}
-void Matrix4::load(const Matrix4& v) {
- *this = v;
-}
-
void Matrix4::load(const SkMatrix& v) {
memset(data, 0, sizeof(data));
@@ -443,6 +437,14 @@ void Matrix4::mapPoint(float& x, float& y) const {
y = dy * dz;
}
+/**
+ * Set the contents of the rect to be the bounding rect around each of the corners, mapped by the
+ * matrix.
+ *
+ * NOTE: an empty rect to an arbitrary matrix isn't guaranteed to have an empty output, since that's
+ * important for conservative bounds estimation (e.g. rotate45Matrix.mapRect of Rect(0, 10) should
+ * result in non-empty.
+ */
void Matrix4::mapRect(Rect& r) const {
if (isIdentity()) return;
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index ed54a25f3edf..9cde5d6aa04e 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-#ifndef ANDROID_HWUI_MATRIX_H
-#define ANDROID_HWUI_MATRIX_H
+#pragma once
-#include <SkMatrix.h>
+#include "Rect.h"
#include <cutils/compiler.h>
-
-#include "Rect.h"
+#include <iomanip>
+#include <ostream>
+#include <SkMatrix.h>
namespace android {
namespace uirenderer {
@@ -114,7 +114,6 @@ public:
void loadIdentity();
void load(const float* v);
- void load(const Matrix4& v);
void load(const SkMatrix& v);
void loadInverse(const Matrix4& v);
@@ -127,6 +126,9 @@ public:
void loadMultiply(const Matrix4& u, const Matrix4& v);
void loadOrtho(float left, float right, float bottom, float top, float near, float far);
+ void loadOrtho(int width, int height) {
+ loadOrtho(0, width, height, 0, -1, 1);
+ }
uint8_t getType() const;
@@ -137,9 +139,11 @@ public:
}
void multiply(const Matrix4& v) {
- Matrix4 u;
- u.loadMultiply(*this, v);
- load(u);
+ if (!v.isIdentity()) {
+ Matrix4 u;
+ u.loadMultiply(*this, v);
+ *this = u;
+ }
}
void multiply(float v);
@@ -214,8 +218,26 @@ public:
void dump(const char* label = nullptr) const;
+ friend std::ostream& operator<<(std::ostream& os, const Matrix4& matrix) {
+ if (matrix.isSimple()) {
+ os << "offset " << matrix.getTranslateX() << "x" << matrix.getTranslateY();
+ if (!matrix.isPureTranslate()) {
+ os << ", scale " << matrix[kScaleX] << "x" << matrix[kScaleY];
+ }
+ } else {
+ os << "[" << matrix[0];
+ for (int i = 1; i < 16; i++) {
+ os << ", " << matrix[i];
+ }
+ os << "]";
+ }
+ return os;
+ }
+
static const Matrix4& identity();
+ void invalidateType() { mType = kTypeUnknown; }
+
private:
mutable uint8_t mType;
@@ -240,4 +262,3 @@ typedef Matrix4 mat4;
}; // namespace uirenderer
}; // namespace android
-#endif // ANDROID_HWUI_MATRIX_H
diff --git a/libs/hwui/OpDumper.cpp b/libs/hwui/OpDumper.cpp
new file mode 100644
index 000000000000..cab93e8faf6a
--- /dev/null
+++ b/libs/hwui/OpDumper.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 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 "OpDumper.h"
+
+#include "RecordedOp.h"
+
+namespace android {
+namespace uirenderer {
+
+#define STRINGIFY(n) #n,
+static const char* sOpNameLut[] = BUILD_FULL_OP_LUT(STRINGIFY);
+
+void OpDumper::dump(const RecordedOp& op, std::ostream& output, int level) {
+ for (int i = 0; i < level; i++) {
+ output << " ";
+ }
+
+ Rect localBounds(op.unmappedBounds);
+ op.localMatrix.mapRect(localBounds);
+ output << sOpNameLut[op.opId] << " " << localBounds;
+
+ if (op.localClip
+ && (!op.localClip->rect.contains(localBounds) || op.localClip->intersectWithRoot)) {
+ output << std::fixed << std::setprecision(0)
+ << " clip=" << op.localClip->rect
+ << " mode=" << (int)op.localClip->mode;
+
+ if (op.localClip->intersectWithRoot) {
+ output << " iwr";
+ }
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/OpDumper.h b/libs/hwui/OpDumper.h
new file mode 100644
index 000000000000..c99b51796b5c
--- /dev/null
+++ b/libs/hwui/OpDumper.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 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 <ostream>
+
+namespace android {
+namespace uirenderer {
+
+struct RecordedOp;
+
+class OpDumper {
+public:
+ static void dump(const RecordedOp& op, std::ostream& output, int level = 0);
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 2292ef415cfc..53ea7fa6f77d 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <GpuMemoryTracker.h>
#include "OpenGLRenderer.h"
#include "DeferredDisplayList.h"
@@ -30,6 +31,7 @@
#include "SkiaShader.h"
#include "Vector.h"
#include "VertexBuffer.h"
+#include "hwui/Canvas.h"
#include "utils/GLUtils.h"
#include "utils/PaintUtils.h"
#include "utils/TraceUtils.h"
@@ -38,8 +40,8 @@
#include <stdint.h>
#include <sys/types.h>
-#include <SkCanvas.h>
#include <SkColor.h>
+#include <SkPaintDefaults.h>
#include <SkPathOps.h>
#include <SkShader.h>
#include <SkTypeface.h>
@@ -70,8 +72,6 @@ OpenGLRenderer::OpenGLRenderer(RenderState& renderState)
, mRenderState(renderState)
, mFrameStarted(false)
, mScissorOptimizationDisabled(false)
- , mSuppressTiling(false)
- , mFirstFrameAfterResize(true)
, mDirty(false)
, mLightCenter((Vector3){FLT_MIN, FLT_MIN, FLT_MIN})
, mLightRadius(FLT_MIN)
@@ -113,13 +113,13 @@ void OpenGLRenderer::setLightCenter(const Vector3& lightCenter) {
void OpenGLRenderer::onViewportInitialized() {
glDisable(GL_DITHER);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- mFirstFrameAfterResize = true;
}
-void OpenGLRenderer::setupFrameState(float left, float top,
- float right, float bottom, bool opaque) {
+void OpenGLRenderer::setupFrameState(int viewportWidth, int viewportHeight,
+ float left, float top, float right, float bottom, bool opaque) {
mCaches.clearGarbage();
- mState.initializeSaveStack(left, top, right, bottom, mLightCenter);
+ mState.initializeSaveStack(viewportWidth, viewportHeight,
+ left, top, right, bottom, mLightCenter);
mOpaque = opaque;
mTilingClip.set(left, top, right, bottom);
}
@@ -134,25 +134,16 @@ void OpenGLRenderer::startFrame() {
mRenderState.setViewport(mState.getWidth(), mState.getHeight());
- // Functors break the tiling extension in pretty spectacular ways
- // This ensures we don't use tiling when a functor is going to be
- // invoked during the frame
- mSuppressTiling = mCaches.hasRegisteredFunctors()
- || mFirstFrameAfterResize;
- mFirstFrameAfterResize = false;
-
- startTilingCurrentClip(true);
-
debugOverdraw(true, true);
clear(mTilingClip.left, mTilingClip.top,
mTilingClip.right, mTilingClip.bottom, mOpaque);
}
-void OpenGLRenderer::prepareDirty(float left, float top,
- float right, float bottom, bool opaque) {
+void OpenGLRenderer::prepareDirty(int viewportWidth, int viewportHeight,
+ float left, float top, float right, float bottom, bool opaque) {
- setupFrameState(left, top, right, bottom, opaque);
+ setupFrameState(viewportWidth, viewportHeight, left, top, right, bottom, opaque);
// Layer renderers will start the frame immediately
// The framebuffer renderer will first defer the display list
@@ -192,46 +183,8 @@ void OpenGLRenderer::clear(float left, float top, float right, float bottom, boo
mRenderState.scissor().reset();
}
-void OpenGLRenderer::startTilingCurrentClip(bool opaque, bool expand) {
- if (!mSuppressTiling) {
- const Snapshot* snapshot = currentSnapshot();
-
- const Rect* clip = &mTilingClip;
- if (snapshot->flags & Snapshot::kFlagFboTarget) {
- clip = &(snapshot->layer->clipRect);
- }
-
- startTiling(*clip, getViewportHeight(), opaque, expand);
- }
-}
-
-void OpenGLRenderer::startTiling(const Rect& clip, int windowHeight, bool opaque, bool expand) {
- if (!mSuppressTiling) {
- if(expand) {
- // Expand the startTiling region by 1
- int leftNotZero = (clip.left > 0) ? 1 : 0;
- int topNotZero = (windowHeight - clip.bottom > 0) ? 1 : 0;
-
- mCaches.startTiling(
- clip.left - leftNotZero,
- windowHeight - clip.bottom - topNotZero,
- clip.right - clip.left + leftNotZero + 1,
- clip.bottom - clip.top + topNotZero + 1,
- opaque);
- } else {
- mCaches.startTiling(clip.left, windowHeight - clip.bottom,
- clip.right - clip.left, clip.bottom - clip.top, opaque);
- }
- }
-}
-
-void OpenGLRenderer::endTiling() {
- if (!mSuppressTiling) mCaches.endTiling();
-}
-
bool OpenGLRenderer::finish() {
renderOverdraw();
- endTiling();
mTempPaths.clear();
// When finish() is invoked on FBO 0 we've reached the end
@@ -242,12 +195,11 @@ bool OpenGLRenderer::finish() {
}
if (!suppressErrorChecks()) {
-#if DEBUG_OPENGL
- GLUtils::dumpGLErrors();
-#endif
+ GL_CHECKPOINT(MODERATE);
#if DEBUG_MEMORY_USAGE
mCaches.dumpMemoryUsage();
+ GPUMemoryTracker::dump();
#else
if (Properties::debugLevel & kDebugMemory) {
mCaches.dumpMemoryUsage();
@@ -272,7 +224,7 @@ void OpenGLRenderer::resumeAfterLayer() {
void OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
if (mState.currentlyIgnored()) return;
- Rect clip(mState.currentClipRect());
+ Rect clip(mState.currentRenderTargetClip());
clip.snapToPixelBoundaries();
// Since we don't know what the functor will draw, let's dirty
@@ -381,7 +333,6 @@ bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) {
&& layer->renderNode.get() && layer->renderNode->isRenderable()) {
if (inFrame) {
- endTiling();
debugOverdraw(false, false);
}
@@ -393,7 +344,6 @@ bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) {
if (inFrame) {
resumeAfterLayer();
- startTilingCurrentClip();
}
layer->debugDrawUpdate = Properties::debugLayersUpdates;
@@ -419,7 +369,7 @@ void OpenGLRenderer::updateLayers() {
// Note: it is very important to update the layers in order
for (int i = 0; i < count; i++) {
- Layer* layer = mLayerUpdates.itemAt(i).get();
+ Layer* layer = mLayerUpdates[i].get();
updateLayer(layer, false);
}
@@ -438,7 +388,7 @@ void OpenGLRenderer::flushLayers() {
// Note: it is very important to update the layers in order
for (int i = 0; i < count; i++) {
- mLayerUpdates.itemAt(i)->flush();
+ mLayerUpdates[i]->flush();
}
mLayerUpdates.clear();
@@ -455,7 +405,7 @@ void OpenGLRenderer::pushLayerUpdate(Layer* layer) {
// the insertion order. The linear search is not an issue since
// this list is usually very short (typically one item, at most a few)
for (int i = mLayerUpdates.size() - 1; i >= 0; i--) {
- if (mLayerUpdates.itemAt(i) == layer) {
+ if (mLayerUpdates[i] == layer) {
return;
}
}
@@ -466,8 +416,8 @@ void OpenGLRenderer::pushLayerUpdate(Layer* layer) {
void OpenGLRenderer::cancelLayerUpdate(Layer* layer) {
if (layer) {
for (int i = mLayerUpdates.size() - 1; i >= 0; i--) {
- if (mLayerUpdates.itemAt(i) == layer) {
- mLayerUpdates.removeAt(i);
+ if (mLayerUpdates[i] == layer) {
+ mLayerUpdates.erase(mLayerUpdates.begin() + i);
break;
}
}
@@ -522,7 +472,7 @@ void OpenGLRenderer::onSnapshotRestored(const Snapshot& removed, const Snapshot&
int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
const SkPaint* paint, int flags, const SkPath* convexMask) {
// force matrix/clip isolation for layer
- flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag;
+ flags |= SaveFlags::MatrixClip;
const int count = mState.saveSnapshot(flags);
@@ -539,7 +489,8 @@ void OpenGLRenderer::calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool
currentTransform()->mapRect(bounds);
// Layers only make sense if they are in the framebuffer's bounds
- if (bounds.intersect(mState.currentClipRect())) {
+ bounds.doIntersect(mState.currentRenderTargetClip());
+ if (!bounds.isEmpty()) {
// We cannot work with sub-pixels in this case
bounds.snapToPixelBoundaries();
@@ -548,23 +499,20 @@ void OpenGLRenderer::calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool
// of the framebuffer
const Snapshot& previous = *(currentSnapshot()->previous);
Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight());
- if (!bounds.intersect(previousViewport)) {
- bounds.setEmpty();
- } else if (fboLayer) {
+
+ bounds.doIntersect(previousViewport);
+ if (!bounds.isEmpty() && fboLayer) {
clip.set(bounds);
mat4 inverse;
inverse.loadInverse(*currentTransform());
inverse.mapRect(clip);
clip.snapToPixelBoundaries();
- if (clip.intersect(untransformedBounds)) {
+ clip.doIntersect(untransformedBounds);
+ if (!clip.isEmpty()) {
clip.translate(-untransformedBounds.left, -untransformedBounds.top);
bounds.set(untransformedBounds);
- } else {
- clip.setEmpty();
}
}
- } else {
- bounds.setEmpty();
}
}
@@ -583,7 +531,7 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float
const SkPaint* paint, int flags) {
const int count = mState.saveSnapshot(flags);
- if (!mState.currentlyIgnored() && (flags & SkCanvas::kClipToLayer_SaveFlag)) {
+ if (!mState.currentlyIgnored() && (flags & SaveFlags::ClipToLayer)) {
// initialize the snapshot as though it almost represents an FBO layer so deferred draw
// operations will be able to store and restore the current clip and transform info, and
// quick rejection will be correct (for display lists)
@@ -591,7 +539,7 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float
Rect bounds(left, top, right, bottom);
Rect clip;
calculateLayerBoundsAndClip(bounds, clip, true);
- updateSnapshotIgnoreForLayer(bounds, clip, true, getAlphaDirect(paint));
+ updateSnapshotIgnoreForLayer(bounds, clip, true, PaintUtils::getAlphaDirect(paint));
if (!mState.currentlyIgnored()) {
writableSnapshot()->resetTransform(-bounds.left, -bounds.top, 0.0f);
@@ -610,7 +558,7 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float
* and the frame buffer still receive every drawing command. For instance, if a
* layer is created and a shape intersecting the bounds of the layers and the
* framebuffer is draw, the shape will be drawn on both (unless the layer was
- * created with the SkCanvas::kClipToLayer_SaveFlag flag.)
+ * created with the SaveFlags::ClipToLayer flag.)
*
* A way to implement layers is to create an FBO for each layer, backed by an RGBA
* texture. Unfortunately, this is inefficient as it requires every primitive to
@@ -660,13 +608,13 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto
LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top);
LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize());
- const bool fboLayer = flags & SkCanvas::kClipToLayer_SaveFlag;
+ const bool fboLayer = flags & SaveFlags::ClipToLayer;
// Window coordinates of the layer
Rect clip;
Rect bounds(left, top, right, bottom);
calculateLayerBoundsAndClip(bounds, clip, fboLayer);
- updateSnapshotIgnoreForLayer(bounds, clip, fboLayer, getAlphaDirect(paint));
+ updateSnapshotIgnoreForLayer(bounds, clip, fboLayer, PaintUtils::getAlphaDirect(paint));
// Bail out if we won't draw in this snapshot
if (mState.currentlyIgnored()) {
@@ -726,7 +674,7 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto
bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) {
layer->clipRect.set(clip);
- layer->setFbo(mCaches.fboCache.get());
+ layer->setFbo(mRenderState.createFramebuffer());
writableSnapshot()->region = &writableSnapshot()->layer->region;
writableSnapshot()->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
@@ -736,7 +684,6 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) {
writableSnapshot()->initializeViewport(bounds.getWidth(), bounds.getHeight());
writableSnapshot()->roundRectClipState = nullptr;
- endTiling();
debugOverdraw(false, false);
// Bind texture to FBO
mRenderState.bindFramebuffer(layer->getFbo());
@@ -751,9 +698,6 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) {
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
layer->getTextureId(), 0);
- // Expand the startTiling region by 1
- startTilingCurrentClip(true, true);
-
// Clear the FBO, expand the clear region by 1 to get nice bilinear filtering
mRenderState.scissor().setEnabled(true);
mRenderState.scissor().set(clip.left - 1.0f, bounds.getHeight() - clip.bottom - 1.0f,
@@ -786,8 +730,6 @@ void OpenGLRenderer::composeLayer(const Snapshot& removed, const Snapshot& resto
mRenderState.scissor().setEnabled(mScissorOptimizationDisabled || clipRequired);
if (fboLayer) {
- endTiling();
-
// Detach the texture from the FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
@@ -796,8 +738,6 @@ void OpenGLRenderer::composeLayer(const Snapshot& removed, const Snapshot& resto
// Unbind current FBO and restore previous one
mRenderState.bindFramebuffer(restored.fbo);
debugOverdraw(true, false);
-
- startTilingCurrentClip();
}
if (!fboLayer && layer->getAlpha() < 255) {
@@ -950,7 +890,7 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) {
if (CC_UNLIKELY(layer->region.isEmpty())) return; // nothing to draw
if (layer->getConvexMask()) {
- save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::MatrixClip);
// clip to the area of the layer the mask can be larger
clipRect(rect.left, rect.top, rect.right, rect.bottom, SkRegion::kIntersect_Op);
@@ -1097,7 +1037,8 @@ void OpenGLRenderer::dirtyLayer(const float left, const float top,
}
void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) {
- if (CC_LIKELY(!bounds.isEmpty() && bounds.intersect(mState.currentClipRect()))) {
+ bounds.doIntersect(mState.currentRenderTargetClip());
+ if (!bounds.isEmpty()) {
bounds.snapToPixelBoundaries();
android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom);
if (!dirty.isEmpty()) {
@@ -1144,7 +1085,7 @@ void OpenGLRenderer::clearLayerRegions() {
.setMeshIndexedQuads(&mesh[0], quadCount)
.setFillClear()
.setTransform(*currentSnapshot(), transformFlags)
- .setModelViewOffsetRect(0, 0, Rect(currentSnapshot()->getClipRect()))
+ .setModelViewOffsetRect(0, 0, Rect(currentSnapshot()->getRenderTargetClip()))
.build();
renderGlop(glop, GlopRenderType::LayerClear);
@@ -1159,7 +1100,7 @@ void OpenGLRenderer::clearLayerRegions() {
///////////////////////////////////////////////////////////////////////////////
bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) {
- const Rect& currentClip = mState.currentClipRect();
+ const Rect& currentClip = mState.currentRenderTargetClip();
const mat4* currentMatrix = currentTransform();
if (stateDeferFlags & kStateDeferFlag_Draw) {
@@ -1171,7 +1112,8 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef
// is used, it should more closely duplicate the quickReject logic (in how it uses
// snapToPixelBoundaries)
- if (!clippedBounds.intersect(currentClip)) {
+ clippedBounds.doIntersect(currentClip);
+ if (clippedBounds.isEmpty()) {
// quick rejected
return true;
}
@@ -1201,20 +1143,24 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef
// Transform and alpha always deferred, since they are used by state operations
// (Note: saveLayer/restore use colorFilter and alpha, so we just save restore everything)
- state.mMatrix.load(*currentMatrix);
+ state.mMatrix = *currentMatrix;
state.mAlpha = currentSnapshot()->alpha;
// always store/restore, since these are just pointers
state.mRoundRectClipState = currentSnapshot()->roundRectClipState;
+#if !HWUI_NEW_OPS
state.mProjectionPathMask = currentSnapshot()->projectionPathMask;
+#endif
return false;
}
void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool skipClipRestore) {
- setMatrix(state.mMatrix);
+ setGlobalMatrix(state.mMatrix);
writableSnapshot()->alpha = state.mAlpha;
writableSnapshot()->roundRectClipState = state.mRoundRectClipState;
+#if !HWUI_NEW_OPS
writableSnapshot()->projectionPathMask = state.mProjectionPathMask;
+#endif
if (state.mClipValid && !skipClipRestore) {
writableSnapshot()->setClip(state.mClip.left, state.mClip.top,
@@ -1246,7 +1192,7 @@ void OpenGLRenderer::setupMergedMultiDraw(const Rect* clipRect) {
///////////////////////////////////////////////////////////////////////////////
void OpenGLRenderer::setScissorFromClip() {
- Rect clip(mState.currentClipRect());
+ Rect clip(mState.currentRenderTargetClip());
clip.snapToPixelBoundaries();
if (mRenderState.scissor().set(clip.left, getViewportHeight() - clip.bottom,
@@ -1267,17 +1213,10 @@ void OpenGLRenderer::ensureStencilBuffer() {
void OpenGLRenderer::attachStencilBufferToLayer(Layer* layer) {
// The layer's FBO is already bound when we reach this stage
if (!layer->getStencilRenderBuffer()) {
- // GL_QCOM_tiled_rendering doesn't like it if a renderbuffer
- // is attached after we initiated tiling. We must turn it off,
- // attach the new render buffer then turn tiling back on
- endTiling();
-
RenderBuffer* buffer = mCaches.renderBufferCache.get(
Stencil::getLayerStencilFormat(),
layer->getWidth(), layer->getHeight());
layer->setStencilRenderBuffer(buffer);
-
- startTiling(layer->clipRect, layer->layer.getHeight());
}
}
@@ -1308,9 +1247,8 @@ void OpenGLRenderer::drawRectangleList(const RectangleList& rectangleList) {
Rect bounds = tr.getBounds();
if (transform.rectToRect()) {
transform.mapRect(bounds);
- if (!bounds.intersect(scissorBox)) {
- bounds.setEmpty();
- } else {
+ bounds.doIntersect(scissorBox);
+ if (!bounds.isEmpty()) {
handlePointNoTransform(rectangleVertices, bounds.left, bounds.top);
handlePointNoTransform(rectangleVertices, bounds.right, bounds.top);
handlePointNoTransform(rectangleVertices, bounds.left, bounds.bottom);
@@ -1471,11 +1409,13 @@ void OpenGLRenderer::renderGlop(const Glop& glop, GlopRenderType type) {
setStencilFromClip();
}
- mRenderState.render(glop);
+ mRenderState.render(glop, currentSnapshot()->getOrthoMatrix());
if (type == GlopRenderType::Standard && !mRenderState.stencil().isWriteEnabled()) {
// TODO: specify more clearly when a draw should dirty the layer.
// is writing to the stencil the only time we should ignore this?
+#if !HWUI_NEW_OPS
dirtyLayer(glop.bounds.left, glop.bounds.top, glop.bounds.right, glop.bounds.bottom);
+#endif
mDirty = true;
}
}
@@ -1497,10 +1437,7 @@ void OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t
return;
}
- // Don't avoid overdraw when visualizing, since that makes it harder to
- // debug where it's coming from, and when the problem occurs.
- bool avoidOverdraw = !Properties::debugOverdraw;
- DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw);
+ DeferredDisplayList deferredList(mState.currentRenderTargetClip());
DeferStateStruct deferStruct(deferredList, *this, replayFlags);
renderNode->defer(deferStruct, 0);
@@ -1543,7 +1480,7 @@ void OpenGLRenderer::drawBitmaps(const SkBitmap* bitmap, AssetAtlas::Entry* entr
.setMeshTexturedMesh(vertices, bitmapCount * 6)
.setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha)
.setTransform(*currentSnapshot(), transformFlags)
- .setModelViewOffsetRectOptionalSnap(snap, x, y, Rect(0, 0, bounds.getWidth(), bounds.getHeight()))
+ .setModelViewOffsetRectOptionalSnap(snap, x, y, Rect(bounds.getWidth(), bounds.getHeight()))
.build();
renderGlop(glop, GlopRenderType::Multi);
}
@@ -1566,7 +1503,7 @@ void OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
.setMeshTexturedUnitQuad(texture->uvMapper)
.setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha)
.setTransform(*currentSnapshot(), TransformFlags::None)
- .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
+ .setModelViewMapUnitToRectSnap(Rect(texture->width(), texture->height()))
.build();
renderGlop(glop);
}
@@ -1595,7 +1532,7 @@ void OpenGLRenderer::drawBitmapMesh(const SkBitmap* bitmap, int meshWidth, int m
colors = tempColors.get();
}
- Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
+ Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef());
const UvMapper& mapper(getMapper(texture));
for (int32_t y = 0; y < meshHeight; y++) {
@@ -1670,10 +1607,10 @@ void OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, Rect src, Rect dst, cons
if (!texture) return;
const AutoTexture autoCleanup(texture);
- Rect uv(std::max(0.0f, src.left / texture->width),
- std::max(0.0f, src.top / texture->height),
- std::min(1.0f, src.right / texture->width),
- std::min(1.0f, src.bottom / texture->height));
+ Rect uv(std::max(0.0f, src.left / texture->width()),
+ std::max(0.0f, src.top / texture->height()),
+ std::min(1.0f, src.right / texture->width()),
+ std::min(1.0f, src.bottom / texture->height()));
const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType)
? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
@@ -1699,6 +1636,7 @@ void OpenGLRenderer::drawPatch(const SkBitmap* bitmap, const Patch* mesh,
Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap);
if (!texture) return;
+ const AutoTexture autoCleanup(texture);
// 9 patches are built for stretching - always filter
int textureFillFlags = TextureFillFlags::ForceFilter;
@@ -1711,7 +1649,7 @@ void OpenGLRenderer::drawPatch(const SkBitmap* bitmap, const Patch* mesh,
.setMeshPatchQuads(*mesh)
.setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha)
.setTransform(*currentSnapshot(), TransformFlags::None)
- .setModelViewOffsetRectSnap(left, top, Rect(0, 0, right - left, bottom - top)) // TODO: get minimal bounds from patch
+ .setModelViewOffsetRectSnap(left, top, Rect(right - left, bottom - top)) // TODO: get minimal bounds from patch
.build();
renderGlop(glop);
}
@@ -1741,7 +1679,7 @@ void OpenGLRenderer::drawPatches(const SkBitmap* bitmap, AssetAtlas::Entry* entr
.setMeshTexturedIndexedQuads(vertices, elementCount)
.setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha)
.setTransform(*currentSnapshot(), transformFlags)
- .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
+ .setModelViewOffsetRect(0, 0, Rect())
.build();
renderGlop(glop, GlopRenderType::Multi);
}
@@ -1835,7 +1773,7 @@ void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
// No need to check against the clip, we fill the clip region
if (mState.currentlyIgnored()) return;
- Rect clip(mState.currentClipRect());
+ Rect clip(mState.currentRenderTargetClip());
clip.snapToPixelBoundaries();
SkPaint paint;
@@ -1901,6 +1839,7 @@ void OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p
path.addCircle(x, y, radius);
}
+#if !HWUI_NEW_OPS
if (CC_UNLIKELY(currentSnapshot()->projectionPathMask != nullptr)) {
// mask ripples with projection mask
SkPath maskPath = *(currentSnapshot()->projectionPathMask->projectionMask);
@@ -1918,8 +1857,9 @@ void OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p
// Mask the ripple path by the projection mask, now that it's
// in local space. Note that this can create CCW paths.
- Op(path, maskPath, kIntersect_PathOp, &path);
+ Op(path, maskPath, kIntersect_SkPathOp, &path);
}
+#endif
drawConvexPath(path, p);
}
@@ -1978,9 +1918,6 @@ void OpenGLRenderer::drawArc(float left, float top, float right, float bottom,
drawConvexPath(path, p);
}
-// See SkPaintDefaults.h
-#define SkPaintDefaults_MiterLimit SkIntToScalar(4)
-
void OpenGLRenderer::drawRect(float left, float top, float right, float bottom,
const SkPaint* p) {
if (mState.currentlyIgnored()
@@ -1991,6 +1928,7 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom,
if (p->getStyle() != SkPaint::kFill_Style) {
// only fill style is supported by drawConvexPath, since others have to handle joins
+ static_assert(SkPaintDefaults_MiterLimit == 4.0f, "Miter limit has changed");
if (p->getPathEffect() != nullptr || p->getStrokeJoin() != SkPaint::kMiter_Join ||
p->getStrokeMiter() != SkPaintDefaults_MiterLimit) {
mCaches.textureState().activateTexture(0);
@@ -2019,13 +1957,13 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom,
}
}
-void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text,
- int bytesCount, int count, const float* positions,
+void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const glyph_t* glyphs,
+ int count, const float* positions,
FontRenderer& fontRenderer, int alpha, float x, float y) {
mCaches.textureState().activateTexture(0);
- TextShadow textShadow;
- if (!getTextShadow(paint, &textShadow)) {
+ PaintUtils::TextShadow textShadow;
+ if (!PaintUtils::getTextShadow(paint, &textShadow)) {
LOG_ALWAYS_FATAL("failed to query shadow attributes");
}
@@ -2033,7 +1971,7 @@ void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text,
// if shader-based correction is enabled
mCaches.dropShadowCache.setFontRenderer(fontRenderer);
ShadowTexture* texture = mCaches.dropShadowCache.get(
- paint, text, bytesCount, count, textShadow.radius, positions);
+ paint, glyphs, count, textShadow.radius, positions);
// If the drop shadow exceeds the max texture size or couldn't be
// allocated, skip drawing
if (!texture) return;
@@ -2048,69 +1986,19 @@ void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text,
.setMeshTexturedUnitQuad(nullptr)
.setFillShadowTexturePaint(*texture, textShadow.color, *paint, currentSnapshot()->alpha)
.setTransform(*currentSnapshot(), TransformFlags::None)
- .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
+ .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height()))
.build();
renderGlop(glop);
}
+// TODO: remove this, once mState.currentlyIgnored captures snapshot alpha
bool OpenGLRenderer::canSkipText(const SkPaint* paint) const {
- float alpha = (hasTextShadow(paint) ? 1.0f : paint->getAlpha()) * currentSnapshot()->alpha;
+ float alpha = (PaintUtils::hasTextShadow(paint)
+ ? 1.0f : paint->getAlpha()) * currentSnapshot()->alpha;
return MathUtils::isZero(alpha)
&& PaintUtils::getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode;
}
-void OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count,
- const float* positions, const SkPaint* paint) {
- if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) {
- return;
- }
-
- // NOTE: Skia does not support perspective transform on drawPosText yet
- if (!currentTransform()->isSimple()) {
- return;
- }
-
- mRenderState.scissor().setEnabled(true);
-
- float x = 0.0f;
- float y = 0.0f;
- const bool pureTranslate = currentTransform()->isPureTranslate();
- if (pureTranslate) {
- x = floorf(x + currentTransform()->getTranslateX() + 0.5f);
- y = floorf(y + currentTransform()->getTranslateY() + 0.5f);
- }
-
- FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.setFont(paint, SkMatrix::I());
-
- int alpha;
- SkXfermode::Mode mode;
- getAlphaAndMode(paint, &alpha, &mode);
-
- if (CC_UNLIKELY(hasTextShadow(paint))) {
- drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
- alpha, 0.0f, 0.0f);
- }
-
- // Pick the appropriate texture filtering
- bool linearFilter = currentTransform()->changesBounds();
- if (pureTranslate && !linearFilter) {
- linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
- }
- fontRenderer.setTextureFiltering(linearFilter);
-
- const Rect& clip(pureTranslate ? writableSnapshot()->getClipRect() : writableSnapshot()->getLocalClip());
- Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
-
- TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
- if (fontRenderer.renderPosText(paint, &clip, text, 0, bytesCount, count, x, y,
- positions, hasLayer() ? &bounds : nullptr, &functor)) {
- dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
- mDirty = true;
- }
-
-}
-
bool OpenGLRenderer::findBestFontTransform(const mat4& transform, SkMatrix* outMatrix) const {
if (CC_LIKELY(transform.isPureTranslate())) {
outMatrix->setIdentity();
@@ -2165,8 +2053,9 @@ void OpenGLRenderer::skew(float sx, float sy) {
mState.skew(sx, sy);
}
-void OpenGLRenderer::setMatrix(const Matrix4& matrix) {
- mState.setMatrix(matrix);
+void OpenGLRenderer::setLocalMatrix(const Matrix4& matrix) {
+ mState.setMatrix(mBaseTransform);
+ mState.concatMatrix(matrix);
}
void OpenGLRenderer::setLocalMatrix(const SkMatrix& matrix) {
@@ -2203,14 +2092,14 @@ void OpenGLRenderer::setProjectionPathMask(LinearAllocator& allocator, const SkP
mState.setProjectionPathMask(allocator, path);
}
-void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float x, float y,
+void OpenGLRenderer::drawText(const glyph_t* glyphs, int bytesCount, int count, float x, float y,
const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds,
DrawOpMode drawOpMode) {
if (drawOpMode == DrawOpMode::kImmediate) {
// The checks for corner-case ignorable text and quick rejection is only done for immediate
// drawing as ops from DeferredDisplayList are already filtered for these
- if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint) ||
+ if (glyphs == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint) ||
quickRejectSetupScissor(bounds)) {
return;
}
@@ -2227,15 +2116,14 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float
y = floorf(y + transform.getTranslateY() + 0.5f);
}
- int alpha;
- SkXfermode::Mode mode;
- getAlphaAndMode(paint, &alpha, &mode);
+ int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha;
+ SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint);
- FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
+ FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer();
- if (CC_UNLIKELY(hasTextShadow(paint))) {
+ if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) {
fontRenderer.setFont(paint, SkMatrix::I());
- drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
+ drawTextShadow(paint, glyphs, count, positions, fontRenderer,
alpha, oldX, oldY);
}
@@ -2260,21 +2148,26 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float
fontRenderer.setTextureFiltering(linearFilter);
// TODO: Implement better clipping for scaled/rotated text
- const Rect* clip = !pureTranslate ? nullptr : &mState.currentClipRect();
+ const Rect* clip = !pureTranslate ? nullptr : &mState.currentRenderTargetClip();
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
bool status;
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("unsupported");
+ TextDrawFunctor functor(nullptr, nullptr, nullptr, x, y, pureTranslate, alpha, mode, paint);
+#else
TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
+#endif
// don't call issuedrawcommand, do it at end of batch
bool forceFinish = (drawOpMode != DrawOpMode::kDefer);
if (CC_UNLIKELY(paint->getTextAlign() != SkPaint::kLeft_Align)) {
SkPaint paintCopy(*paint);
paintCopy.setTextAlign(SkPaint::kLeft_Align);
- status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y,
+ status = fontRenderer.renderPosText(&paintCopy, clip, glyphs, count, x, y,
positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish);
} else {
- status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
+ status = fontRenderer.renderPosText(paint, clip, glyphs, count, x, y,
positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish);
}
@@ -2285,33 +2178,35 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float
dirtyLayerUnchecked(layerBounds, getRegion());
}
- drawTextDecorations(totalAdvance, oldX, oldY, paint);
-
mDirty = true;
}
-void OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count,
+void OpenGLRenderer::drawTextOnPath(const glyph_t* glyphs, int bytesCount, int count,
const SkPath* path, float hOffset, float vOffset, const SkPaint* paint) {
- if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) {
+ if (glyphs == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) {
return;
}
// TODO: avoid scissor by calculating maximum bounds using path bounds + font metrics
mRenderState.scissor().setEnabled(true);
- FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
+ FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer();
fontRenderer.setFont(paint, SkMatrix::I());
fontRenderer.setTextureFiltering(true);
- int alpha;
- SkXfermode::Mode mode;
- getAlphaAndMode(paint, &alpha, &mode);
+ int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha;
+ SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint);
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("unsupported");
+ TextDrawFunctor functor(nullptr, nullptr, nullptr, 0.0f, 0.0f, false, alpha, mode, paint);
+#else
TextDrawFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint);
+#endif
const Rect* clip = &writableSnapshot()->getLocalClip();
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
- if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
+ if (fontRenderer.renderTextOnPath(paint, clip, glyphs, count, path,
hOffset, vOffset, hasLayer() ? &bounds : nullptr, &functor)) {
dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
mDirty = true;
@@ -2325,16 +2220,19 @@ void OpenGLRenderer::drawPath(const SkPath* path, const SkPaint* paint) {
PathTexture* texture = mCaches.pathCache.get(path, paint);
if (!texture) return;
- const AutoTexture autoCleanup(texture);
const float x = texture->left - texture->offset;
const float y = texture->top - texture->offset;
drawPathTexture(texture, x, y, paint);
+
+ if (texture->cleanup) {
+ mCaches.pathCache.remove(path, paint);
+ }
mDirty = true;
}
-void OpenGLRenderer::drawLayer(Layer* layer, float x, float y) {
+void OpenGLRenderer::drawLayer(Layer* layer) {
if (!layer) {
return;
}
@@ -2343,14 +2241,14 @@ void OpenGLRenderer::drawLayer(Layer* layer, float x, float y) {
if (layer->isTextureLayer()) {
transform = &layer->getTransform();
if (!transform->isIdentity()) {
- save(SkCanvas::kMatrix_SaveFlag);
+ save(SaveFlags::Matrix);
concatMatrix(*transform);
}
}
bool clipRequired = false;
const bool rejected = mState.calculateQuickRejectForScissor(
- x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight(),
+ 0, 0, layer->layer.getWidth(), layer->layer.getHeight(),
&clipRequired, nullptr, false);
if (rejected) {
@@ -2379,7 +2277,7 @@ void OpenGLRenderer::drawLayer(Layer* layer, float x, float y) {
.setMeshTexturedIndexedQuads(layer->mesh, layer->meshElementCount)
.setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap)
.setTransform(*currentSnapshot(), TransformFlags::None)
- .setModelViewOffsetRectSnap(x, y, Rect(0, 0, layer->layer.getWidth(), layer->layer.getHeight()))
+ .setModelViewOffsetRectSnap(0, 0, Rect(layer->layer.getWidth(), layer->layer.getHeight()))
.build();
DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, renderGlop(glop));
#if DEBUG_LAYERS_AS_REGIONS
@@ -2392,7 +2290,7 @@ void OpenGLRenderer::drawLayer(Layer* layer, float x, float y) {
SkPaint paint;
paint.setColor(0x7f00ff00);
- drawColorRect(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight(), &paint);
+ drawColorRect(0, 0, layer->layer.getWidth(), layer->layer.getHeight(), &paint);
}
}
layer->hasDrawnSinceUpdate = true;
@@ -2418,7 +2316,7 @@ void OpenGLRenderer::setDrawFilter(SkDrawFilter* filter) {
///////////////////////////////////////////////////////////////////////////////
Texture* OpenGLRenderer::getTexture(const SkBitmap* bitmap) {
- Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
+ Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef());
if (!texture) {
return mCaches.textureCache.get(bitmap);
}
@@ -2427,7 +2325,7 @@ Texture* OpenGLRenderer::getTexture(const SkBitmap* bitmap) {
void OpenGLRenderer::drawPathTexture(PathTexture* texture, float x, float y,
const SkPaint* paint) {
- if (quickRejectSetupScissor(x, y, x + texture->width, y + texture->height)) {
+ if (quickRejectSetupScissor(x, y, x + texture->width(), y + texture->height())) {
return;
}
@@ -2437,61 +2335,11 @@ void OpenGLRenderer::drawPathTexture(PathTexture* texture, float x, float y,
.setMeshTexturedUnitQuad(nullptr)
.setFillPathTexturePaint(*texture, *paint, currentSnapshot()->alpha)
.setTransform(*currentSnapshot(), TransformFlags::None)
- .setModelViewMapUnitToRect(Rect(x, y, x + texture->width, y + texture->height))
+ .setModelViewMapUnitToRect(Rect(x, y, x + texture->width(), y + texture->height()))
.build();
renderGlop(glop);
}
-// Same values used by Skia
-#define kStdStrikeThru_Offset (-6.0f / 21.0f)
-#define kStdUnderline_Offset (1.0f / 9.0f)
-#define kStdUnderline_Thickness (1.0f / 18.0f)
-
-void OpenGLRenderer::drawTextDecorations(float underlineWidth, float x, float y,
- const SkPaint* paint) {
- // Handle underline and strike-through
- uint32_t flags = paint->getFlags();
- if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
- SkPaint paintCopy(*paint);
-
- if (CC_LIKELY(underlineWidth > 0.0f)) {
- const float textSize = paintCopy.getTextSize();
- const float strokeWidth = std::max(textSize * kStdUnderline_Thickness, 1.0f);
-
- const float left = x;
- float top = 0.0f;
-
- int linesCount = 0;
- if (flags & SkPaint::kUnderlineText_Flag) linesCount++;
- if (flags & SkPaint::kStrikeThruText_Flag) linesCount++;
-
- const int pointsCount = 4 * linesCount;
- float points[pointsCount];
- int currentPoint = 0;
-
- if (flags & SkPaint::kUnderlineText_Flag) {
- top = y + textSize * kStdUnderline_Offset;
- points[currentPoint++] = left;
- points[currentPoint++] = top;
- points[currentPoint++] = left + underlineWidth;
- points[currentPoint++] = top;
- }
-
- if (flags & SkPaint::kStrikeThruText_Flag) {
- top = y + textSize * kStdStrikeThru_Offset;
- points[currentPoint++] = left;
- points[currentPoint++] = top;
- points[currentPoint++] = left + underlineWidth;
- points[currentPoint++] = top;
- }
-
- paintCopy.setStrokeWidth(strokeWidth);
-
- drawLines(&points[0], pointsCount, &paintCopy);
- }
- }
-}
-
void OpenGLRenderer::drawRects(const float* rects, int count, const SkPaint* paint) {
if (mState.currentlyIgnored()) {
return;
@@ -2595,12 +2443,6 @@ void OpenGLRenderer::drawColorRect(float left, float top, float right, float bot
renderGlop(glop);
}
-void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha,
- SkXfermode::Mode* mode) const {
- getAlphaAndModeDirect(paint, alpha, mode);
- *alpha *= currentSnapshot()->alpha;
-}
-
float OpenGLRenderer::getLayerAlpha(const Layer* layer) const {
return (layer->getAlpha() / 255.0f) * currentSnapshot()->alpha;
}
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 402f6edd475d..dacd8ccaa6ea 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -35,6 +35,7 @@
#include <SkBitmap.h>
#include <SkCanvas.h>
#include <SkColorFilter.h>
+#include <SkDrawLooper.h>
#include <SkMatrix.h>
#include <SkPaint.h>
#include <SkRegion.h>
@@ -44,12 +45,13 @@
#include <utils/Functor.h>
#include <utils/RefBase.h>
#include <utils/SortedVector.h>
-#include <utils/Vector.h>
#include <cutils/compiler.h>
#include <androidfw/ResourceTypes.h>
+#include <vector>
+
class SkShader;
namespace android {
@@ -117,15 +119,6 @@ public:
OpenGLRenderer(RenderState& renderState);
virtual ~OpenGLRenderer();
- /**
- * Sets the dimension of the underlying drawing surface. This method must
- * be called at least once every time the drawing surface changes size.
- *
- * @param width The width in pixels of the underlysing surface
- * @param height The height in pixels of the underlysing surface
- */
- void setViewport(int width, int height) { mState.setViewport(width, height); }
-
void initProperties();
void initLight(float lightRadius, uint8_t ambientShadowAlpha,
uint8_t spotShadowAlpha);
@@ -141,21 +134,8 @@ public:
* and will not be cleared. If false, the target surface
* will be cleared
*/
- virtual void prepareDirty(float left, float top, float right, float bottom,
- bool opaque);
-
- /**
- * Prepares the renderer to draw a frame. This method must be invoked
- * at the beginning of each frame. When this method is invoked, the
- * entire drawing surface is assumed to be redrawn.
- *
- * @param opaque If true, the target surface is considered opaque
- * and will not be cleared. If false, the target surface
- * will be cleared
- */
- void prepare(bool opaque) {
- prepareDirty(0.0f, 0.0f, mState.getWidth(), mState.getHeight(), opaque);
- }
+ virtual void prepareDirty(int viewportWidth, int viewportHeight,
+ float left, float top, float right, float bottom, bool opaque);
/**
* Indicates the end of a frame. This method must be invoked whenever
@@ -186,7 +166,7 @@ public:
const SkPaint* paint, int flags);
void drawRenderNode(RenderNode* displayList, Rect& dirty, int32_t replayFlags = 1);
- void drawLayer(Layer* layer, float x, float y);
+ void drawLayer(Layer* layer);
void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
void drawBitmaps(const SkBitmap* bitmap, AssetAtlas::Entry* entry, int bitmapCount,
TextureVertex* vertices, bool pureTranslate, const Rect& bounds, const SkPaint* paint);
@@ -211,11 +191,9 @@ public:
void drawPath(const SkPath* path, const SkPaint* paint);
void drawLines(const float* points, int count, const SkPaint* paint);
void drawPoints(const float* points, int count, const SkPaint* paint);
- void drawTextOnPath(const char* text, int bytesCount, int count, const SkPath* path,
+ void drawTextOnPath(const glyph_t* glyphs, int bytesCount, int count, const SkPath* path,
float hOffset, float vOffset, const SkPaint* paint);
- void drawPosText(const char* text, int bytesCount, int count,
- const float* positions, const SkPaint* paint);
- void drawText(const char* text, int bytesCount, int count, float x, float y,
+ void drawText(const glyph_t* glyphs, int bytesCount, int count, float x, float y,
const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds,
DrawOpMode drawOpMode = DrawOpMode::kImmediate);
void drawRects(const float* rects, int count, const SkPaint* paint);
@@ -280,57 +258,6 @@ public:
void endMark() const;
/**
- * Gets the alpha and xfermode out of a paint object. If the paint is null
- * alpha will be 255 and the xfermode will be SRC_OVER. This method does
- * not multiply the paint's alpha by the current snapshot's alpha, and does
- * not replace the alpha with the overrideLayerAlpha
- *
- * @param paint The paint to extract values from
- * @param alpha Where to store the resulting alpha
- * @param mode Where to store the resulting xfermode
- */
- static inline void getAlphaAndModeDirect(const SkPaint* paint, int* alpha,
- SkXfermode::Mode* mode) {
- *mode = getXfermodeDirect(paint);
- *alpha = getAlphaDirect(paint);
- }
-
- static inline SkXfermode::Mode getXfermodeDirect(const SkPaint* paint) {
- if (!paint) return SkXfermode::kSrcOver_Mode;
- return PaintUtils::getXfermode(paint->getXfermode());
- }
-
- static inline int getAlphaDirect(const SkPaint* paint) {
- if (!paint) return 255;
- return paint->getAlpha();
- }
-
- struct TextShadow {
- SkScalar radius;
- float dx;
- float dy;
- SkColor color;
- };
-
- static inline bool getTextShadow(const SkPaint* paint, TextShadow* textShadow) {
- SkDrawLooper::BlurShadowRec blur;
- if (paint && paint->getLooper() && paint->getLooper()->asABlurShadow(&blur)) {
- if (textShadow) {
- textShadow->radius = Blur::convertSigmaToRadius(blur.fSigma);
- textShadow->dx = blur.fOffset.fX;
- textShadow->dy = blur.fOffset.fY;
- textShadow->color = blur.fColor;
- }
- return true;
- }
- return false;
- }
-
- static inline bool hasTextShadow(const SkPaint* paint) {
- return getTextShadow(paint, nullptr);
- }
-
- /**
* Build the best transform to use to rasterize text given a full
* transform matrix, and whether filteration is needed.
*
@@ -366,8 +293,10 @@ public:
void restore();
void restoreToCount(int saveCount);
- void getMatrix(SkMatrix* outMatrix) const { mState.getMatrix(outMatrix); }
- void setMatrix(const SkMatrix& matrix) { mState.setMatrix(matrix); }
+ void setGlobalMatrix(const Matrix4& matrix) {
+ mState.setMatrix(matrix);
+ }
+ void setLocalMatrix(const Matrix4& matrix);
void setLocalMatrix(const SkMatrix& matrix);
void concatMatrix(const SkMatrix& matrix) { mState.concatMatrix(matrix); }
@@ -426,7 +355,8 @@ protected:
* Perform the setup specific to a frame. This method does not
* issue any OpenGL commands.
*/
- void setupFrameState(float left, float top, float right, float bottom, bool opaque);
+ void setupFrameState(int viewportWidth, int viewportHeight,
+ float left, float top, float right, float bottom, bool opaque);
/**
* Indicates the start of rendering. This method will setup the
@@ -510,16 +440,6 @@ protected:
void drawTextureLayer(Layer* layer, const Rect& rect);
/**
- * Gets the alpha and xfermode out of a paint object. If the paint is null
- * alpha will be 255 and the xfermode will be SRC_OVER. Accounts for snapshot alpha.
- *
- * @param paint The paint to extract values from
- * @param alpha Where to store the resulting alpha
- * @param mode Where to store the resulting xfermode
- */
- inline void getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) const;
-
- /**
* Gets the alpha from a layer, accounting for snapshot alpha
*
* @param layer The layer from which the alpha is extracted
@@ -554,27 +474,6 @@ private:
void discardFramebuffer(float left, float top, float right, float bottom);
/**
- * Tells the GPU what part of the screen is about to be redrawn.
- * This method will use the current layer space clip rect.
- * This method needs to be invoked every time getTargetFbo() is
- * bound again.
- */
- void startTilingCurrentClip(bool opaque = false, bool expand = false);
-
- /**
- * Tells the GPU what part of the screen is about to be redrawn.
- * This method needs to be invoked every time getTargetFbo() is
- * bound again.
- */
- void startTiling(const Rect& clip, int windowHeight, bool opaque = false, bool expand = false);
-
- /**
- * Tells the GPU that we are done drawing the frame or that we
- * are switching to another render target.
- */
- void endTiling();
-
- /**
* Sets the clipping rectangle using glScissor. The clip is defined by
* the current snapshot's clipRect member.
*/
@@ -736,24 +635,11 @@ private:
*/
void drawConvexPath(const SkPath& path, const SkPaint* paint);
- /**
- * Draws text underline and strike-through if needed.
- *
- * @param text The text to decor
- * @param bytesCount The number of bytes in the text
- * @param totalAdvance The total advance in pixels, defines underline/strikethrough length
- * @param x The x coordinate where the text will be drawn
- * @param y The y coordinate where the text will be drawn
- * @param paint The paint to draw the text with
- */
- void drawTextDecorations(float totalAdvance, float x, float y, const SkPaint* paint);
-
/**
* Draws shadow layer on text (with optional positions).
*
* @param paint The paint to draw the shadow with
* @param text The text to draw
- * @param bytesCount The number of bytes in the text
* @param count The number of glyphs in the text
* @param positions The x, y positions of individual glyphs (or NULL)
* @param fontRenderer The font renderer object
@@ -761,7 +647,7 @@ private:
* @param x The x coordinate where the shadow will be drawn
* @param y The y coordinate where the shadow will be drawn
*/
- void drawTextShadow(const SkPaint* paint, const char* text, int bytesCount, int count,
+ void drawTextShadow(const SkPaint* paint, const glyph_t* glyphs, int count,
const float* positions, FontRenderer& fontRenderer, int alpha,
float x, float y);
@@ -855,16 +741,12 @@ private:
// List of rectangles to clear after saveLayer() is invoked
std::vector<Rect> mLayers;
// List of layers to update at the beginning of a frame
- Vector< sp<Layer> > mLayerUpdates;
+ std::vector< sp<Layer> > mLayerUpdates;
// See PROPERTY_DISABLE_SCISSOR_OPTIMIZATION in
// Properties.h
bool mScissorOptimizationDisabled;
- // No-ops start/endTiling when set
- bool mSuppressTiling;
- bool mFirstFrameAfterResize;
-
bool mSkipOutlineClip;
// True if anything has been drawn since the last call to
diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h
index c98932cf095e..922ff7caecb8 100644
--- a/libs/hwui/Outline.h
+++ b/libs/hwui/Outline.h
@@ -26,20 +26,43 @@ namespace uirenderer {
class Outline {
public:
+ enum class Type {
+ None = 0,
+ Empty = 1,
+ ConvexPath = 2,
+ RoundRect = 3
+ };
+
Outline()
: mShouldClip(false)
- , mType(kOutlineType_None)
+ , mType(Type::None)
, mRadius(0)
, mAlpha(0.0f) {}
void setRoundRect(int left, int top, int right, int bottom, float radius, float alpha) {
- mType = kOutlineType_RoundRect;
+ mAlpha = alpha;
+ if (mType == Type::RoundRect
+ && left == mBounds.left
+ && right == mBounds.right
+ && top == mBounds.top
+ && bottom == mBounds.bottom
+ && radius == mRadius) {
+ // nothing to change, don't do any work
+ return;
+ }
+
+ mType = Type::RoundRect;
mBounds.set(left, top, right, bottom);
mRadius = radius;
+
+ // update mPath to reflect new outline
mPath.reset();
- mPath.addRoundRect(SkRect::MakeLTRB(left, top, right, bottom),
- radius, radius);
- mAlpha = alpha;
+ if (MathUtils::isPositive(radius)) {
+ mPath.addRoundRect(SkRect::MakeLTRB(left, top, right, bottom),
+ radius, radius);
+ } else {
+ mPath.addRect(left, top, right, bottom);
+ }
}
void setConvexPath(const SkPath* outline, float alpha) {
@@ -47,26 +70,26 @@ public:
setEmpty();
return;
}
- mType = kOutlineType_ConvexPath;
+ mType = Type::ConvexPath;
mPath = *outline;
mBounds.set(outline->getBounds());
mAlpha = alpha;
}
void setEmpty() {
- mType = kOutlineType_Empty;
+ mType = Type::Empty;
mPath.reset();
mAlpha = 0.0f;
}
void setNone() {
- mType = kOutlineType_None;
+ mType = Type::None;
mPath.reset();
mAlpha = 0.0f;
}
bool isEmpty() const {
- return mType == kOutlineType_Empty;
+ return mType == Type::Empty;
}
float getAlpha() const {
@@ -83,7 +106,7 @@ public:
bool willClip() const {
// only round rect outlines can be used for clipping
- return mShouldClip && (mType == kOutlineType_RoundRect);
+ return mShouldClip && (mType == Type::RoundRect);
}
bool willRoundRectClip() const {
@@ -92,7 +115,7 @@ public:
}
bool getAsRoundRect(Rect* outRect, float* outRadius) const {
- if (mType == kOutlineType_RoundRect) {
+ if (mType == Type::RoundRect) {
outRect->set(mBounds);
*outRadius = mRadius;
return true;
@@ -101,21 +124,26 @@ public:
}
const SkPath* getPath() const {
- if (mType == kOutlineType_None || mType == kOutlineType_Empty) return nullptr;
+ if (mType == Type::None || mType == Type::Empty) return nullptr;
return &mPath;
}
-private:
- enum OutlineType {
- kOutlineType_None = 0,
- kOutlineType_Empty = 1,
- kOutlineType_ConvexPath = 2,
- kOutlineType_RoundRect = 3
- };
+ Type getType() const {
+ return mType;
+ }
+
+ const Rect& getBounds() const {
+ return mBounds;
+ }
+
+ float getRadius() const {
+ return mRadius;
+ }
+private:
bool mShouldClip;
- OutlineType mType;
+ Type mType;
Rect mBounds;
float mRadius;
float mAlpha;
diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp
index 6a7dfb3890d3..b471e7850a99 100644
--- a/libs/hwui/Patch.cpp
+++ b/libs/hwui/Patch.cpp
@@ -14,18 +14,16 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
-#include <cmath>
-
-#include <utils/Log.h>
+#include "Patch.h"
#include "Caches.h"
-#include "Patch.h"
#include "Properties.h"
#include "UvMapper.h"
#include "utils/MathUtils.h"
+#include <algorithm>
+#include <utils/Log.h>
+
namespace android {
namespace uirenderer {
@@ -191,10 +189,10 @@ void Patch::generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, f
const uint32_t oldQuadCount = quadCount;
quadCount++;
- x1 = MathUtils::max(x1, 0.0f);
- x2 = MathUtils::max(x2, 0.0f);
- y1 = MathUtils::max(y1, 0.0f);
- y2 = MathUtils::max(y2, 0.0f);
+ x1 = std::max(x1, 0.0f);
+ x2 = std::max(x2, 0.0f);
+ y1 = std::max(y1, 0.0f);
+ y2 = std::max(y2, 0.0f);
// Skip degenerate and transparent (empty) quads
if ((mColors[oldQuadCount] == 0) || x1 >= x2 || y1 >= y2) {
@@ -208,8 +206,7 @@ void Patch::generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, f
// Record all non empty quads
if (hasEmptyQuads) {
- Rect bounds(x1, y1, x2, y2);
- quads.add(bounds);
+ quads.emplace_back(x1, y1, x2, y2);
}
mUvMapper.map(u1, v1, u2, v2);
diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h
index b63bd24456d3..f04416ccabf9 100644
--- a/libs/hwui/Patch.h
+++ b/libs/hwui/Patch.h
@@ -21,13 +21,13 @@
#include <GLES2/gl2.h>
-#include <utils/Vector.h>
-
#include <androidfw/ResourceTypes.h>
#include "Rect.h"
#include "UvMapper.h"
+#include <vector>
+
namespace android {
namespace uirenderer {
@@ -52,7 +52,7 @@ public:
uint32_t verticesCount = 0;
uint32_t indexCount = 0;
bool hasEmptyQuads = false;
- Vector<Rect> quads;
+ std::vector<Rect> quads;
GLintptr positionOffset = 0;
GLintptr textureOffset = 0;
diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp
index 27652624b498..bd6feb9fc762 100644
--- a/libs/hwui/PatchCache.cpp
+++ b/libs/hwui/PatchCache.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <utils/JenkinsHash.h>
#include <utils/Log.h>
@@ -34,20 +32,12 @@ namespace uirenderer {
PatchCache::PatchCache(RenderState& renderState)
: mRenderState(renderState)
+ , mMaxSize(Properties::patchCacheSize)
, mSize(0)
, mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity)
, mMeshBuffer(0)
, mFreeBlocks(nullptr)
- , mGenerationId(0) {
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_PATCH_CACHE_SIZE, property, nullptr) > 0) {
- INIT_LOGD(" Setting patch cache size to %skB", property);
- mMaxSize = KB(atoi(property));
- } else {
- INIT_LOGD(" Using default patch cache size of %.2fkB", DEFAULT_PATCH_CACHE_SIZE);
- mMaxSize = KB(DEFAULT_PATCH_CACHE_SIZE);
- }
-}
+ , mGenerationId(0) {}
PatchCache::~PatchCache() {
clear();
diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h
index 387f79acf0ec..66ef6a0279ba 100644
--- a/libs/hwui/PatchCache.h
+++ b/libs/hwui/PatchCache.h
@@ -169,7 +169,7 @@ private:
#endif
RenderState& mRenderState;
- uint32_t mMaxSize;
+ const uint32_t mMaxSize;
uint32_t mSize;
LruCache<PatchDescription, Patch*> mCache;
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 3af640f76365..a8ace8c10edd 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-#define ATRACE_TAG ATRACE_TAG_VIEW
-
#include <SkBitmap.h>
#include <SkCanvas.h>
#include <SkColor.h>
#include <SkPaint.h>
#include <SkPath.h>
+#include <SkPathEffect.h>
#include <SkRect.h>
#include <utils/JenkinsHash.h>
@@ -33,21 +31,39 @@
#include "thread/Signal.h"
#include "thread/TaskProcessor.h"
+#include <cutils/properties.h>
+
namespace android {
namespace uirenderer {
+template <class T>
+static bool compareWidthHeight(const T& lhs, const T& rhs) {
+ return (lhs.mWidth == rhs.mWidth) && (lhs.mHeight == rhs.mHeight);
+}
+
+static bool compareRoundRects(const PathDescription::Shape::RoundRect& lhs,
+ const PathDescription::Shape::RoundRect& rhs) {
+ return compareWidthHeight(lhs, rhs) && lhs.mRx == rhs.mRx && lhs.mRy == rhs.mRy;
+}
+
+static bool compareArcs(const PathDescription::Shape::Arc& lhs, const PathDescription::Shape::Arc& rhs) {
+ return compareWidthHeight(lhs, rhs) && lhs.mStartAngle == rhs.mStartAngle &&
+ lhs.mSweepAngle == rhs.mSweepAngle && lhs.mUseCenter == rhs.mUseCenter;
+}
+
///////////////////////////////////////////////////////////////////////////////
// Cache entries
///////////////////////////////////////////////////////////////////////////////
PathDescription::PathDescription()
- : type(kShapeNone)
+ : type(ShapeType::None)
, join(SkPaint::kDefault_Join)
, cap(SkPaint::kDefault_Cap)
, style(SkPaint::kFill_Style)
, miter(4.0f)
, strokeWidth(1.0f)
, pathEffect(nullptr) {
+ // Shape bits should be set to zeroes, because they are used for hash calculation.
memset(&shape, 0, sizeof(Shape));
}
@@ -59,11 +75,12 @@ PathDescription::PathDescription(ShapeType type, const SkPaint* paint)
, miter(paint->getStrokeMiter())
, strokeWidth(paint->getStrokeWidth())
, pathEffect(paint->getPathEffect()) {
+ // Shape bits should be set to zeroes, because they are used for hash calculation.
memset(&shape, 0, sizeof(Shape));
}
hash_t PathDescription::hash() const {
- uint32_t hash = JenkinsHashMix(0, type);
+ uint32_t hash = JenkinsHashMix(0, static_cast<int>(type));
hash = JenkinsHashMix(hash, join);
hash = JenkinsHashMix(hash, cap);
hash = JenkinsHashMix(hash, style);
@@ -74,6 +91,32 @@ hash_t PathDescription::hash() const {
return JenkinsHashWhiten(hash);
}
+bool PathDescription::operator==(const PathDescription& rhs) const {
+ if (type != rhs.type) return false;
+ if (join != rhs.join) return false;
+ if (cap != rhs.cap) return false;
+ if (style != rhs.style) return false;
+ if (miter != rhs.miter) return false;
+ if (strokeWidth != rhs.strokeWidth) return false;
+ if (pathEffect != rhs.pathEffect) return false;
+ switch (type) {
+ case ShapeType::None:
+ return 0;
+ case ShapeType::Rect:
+ return compareWidthHeight(shape.rect, rhs.shape.rect);
+ case ShapeType::RoundRect:
+ return compareRoundRects(shape.roundRect, rhs.shape.roundRect);
+ case ShapeType::Circle:
+ return shape.circle.mRadius == rhs.shape.circle.mRadius;
+ case ShapeType::Oval:
+ return compareWidthHeight(shape.oval, rhs.shape.oval);
+ case ShapeType::Arc:
+ return compareArcs(shape.arc, rhs.shape.arc);
+ case ShapeType::Path:
+ return shape.path.mGenerationID == rhs.shape.path.mGenerationID;
+ }
+}
+
///////////////////////////////////////////////////////////////////////////////
// Utilities
///////////////////////////////////////////////////////////////////////////////
@@ -136,17 +179,10 @@ static void drawPath(const SkPath *path, const SkPaint* paint, SkBitmap& bitmap,
// Cache constructor/destructor
///////////////////////////////////////////////////////////////////////////////
-PathCache::PathCache():
- mCache(LruCache<PathDescription, PathTexture*>::kUnlimitedCapacity),
- mSize(0), mMaxSize(MB(DEFAULT_PATH_CACHE_SIZE)) {
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_PATH_CACHE_SIZE, property, nullptr) > 0) {
- INIT_LOGD(" Setting %s cache size to %sMB", name, property);
- mMaxSize = MB(atof(property));
- } else {
- INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_PATH_CACHE_SIZE);
- }
-
+PathCache::PathCache()
+ : mCache(LruCache<PathDescription, PathTexture*>::kUnlimitedCapacity)
+ , mSize(0)
+ , mMaxSize(Properties::pathCacheSize) {
mCache.setOnEntryRemovedListener(this);
GLint maxTextureSize;
@@ -186,7 +222,7 @@ void PathCache::operator()(PathDescription& entry, PathTexture*& texture) {
void PathCache::removeTexture(PathTexture* texture) {
if (texture) {
- const uint32_t size = texture->width * texture->height;
+ const uint32_t size = texture->width() * texture->height();
// If there is a pending task we must wait for it to return
// before attempting our cleanup
@@ -210,9 +246,7 @@ void PathCache::removeTexture(PathTexture* texture) {
ALOGD("Shape deleted, size = %d", size);
}
- if (texture->id) {
- Caches::getInstance().textureState().deleteTexture(texture->id);
- }
+ texture->deleteTexture();
delete texture;
}
}
@@ -249,8 +283,7 @@ PathTexture* PathCache::addTexture(const PathDescription& entry, const SkPath *p
drawPath(path, paint, bitmap, left, top, offset, width, height);
PathTexture* texture = new PathTexture(Caches::getInstance(),
- left, top, offset, width, height,
- path->getGenerationID());
+ left, top, offset, path->getGenerationID());
generateTexture(entry, &bitmap, texture);
return texture;
@@ -263,7 +296,7 @@ void PathCache::generateTexture(const PathDescription& entry, SkBitmap* bitmap,
// Note here that we upload to a texture even if it's bigger than mMaxSize.
// Such an entry in mCache will only be temporary, since it will be evicted
// immediately on trim, or on any other Path entering the cache.
- uint32_t size = texture->width * texture->height;
+ uint32_t size = texture->width() * texture->height();
mSize += size;
PATH_LOGD("PathCache::get/create: name, size, mSize = %d, %d, %d",
texture->id, size, mSize);
@@ -281,24 +314,8 @@ void PathCache::clear() {
void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) {
ATRACE_NAME("Upload Path Texture");
- SkAutoLockPixels alp(bitmap);
- if (!bitmap.readyToDraw()) {
- ALOGE("Cannot generate texture from bitmap");
- return;
- }
-
- glGenTextures(1, &texture->id);
-
- Caches::getInstance().textureState().bindTexture(texture->id);
- // Textures are Alpha8
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-
- texture->blend = true;
- glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
- GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels());
-
+ texture->upload(bitmap);
texture->setFilter(GL_LINEAR);
- texture->setWrap(GL_CLAMP_TO_EDGE);
}
///////////////////////////////////////////////////////////////////////////////
@@ -321,16 +338,12 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
texture->left = left;
texture->top = top;
texture->offset = offset;
- texture->width = width;
- texture->height = height;
if (width <= mMaxTextureSize && height <= mMaxTextureSize) {
SkBitmap* bitmap = new SkBitmap();
drawPath(&t->path, &t->paint, *bitmap, left, top, offset, width, height);
t->setResult(bitmap);
} else {
- texture->width = 0;
- texture->height = 0;
t->setResult(nullptr);
}
}
@@ -341,7 +354,7 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
void PathCache::removeDeferred(const SkPath* path) {
Mutex::Autolock l(mLock);
- mGarbage.push(path->getGenerationID());
+ mGarbage.push_back(path->getGenerationID());
}
void PathCache::clearGarbage() {
@@ -349,14 +362,11 @@ void PathCache::clearGarbage() {
{ // scope for the mutex
Mutex::Autolock l(mLock);
- size_t count = mGarbage.size();
- for (size_t i = 0; i < count; i++) {
- const uint32_t generationID = mGarbage.itemAt(i);
-
+ for (const uint32_t generationID : mGarbage) {
LruCache<PathDescription, PathTexture*>::Iterator iter(mCache);
while (iter.next()) {
const PathDescription& key = iter.key();
- if (key.type == kShapePath && key.shape.path.mGenerationID == generationID) {
+ if (key.type == ShapeType::Path && key.shape.path.mGenerationID == generationID) {
pathsToRemove.push(key);
}
}
@@ -370,7 +380,7 @@ void PathCache::clearGarbage() {
}
PathTexture* PathCache::get(const SkPath* path, const SkPaint* paint) {
- PathDescription entry(kShapePath, paint);
+ PathDescription entry(ShapeType::Path, paint);
entry.shape.path.mGenerationID = path->getGenerationID();
PathTexture* texture = mCache.get(entry);
@@ -400,12 +410,18 @@ PathTexture* PathCache::get(const SkPath* path, const SkPaint* paint) {
return texture;
}
+void PathCache::remove(const SkPath* path, const SkPaint* paint) {
+ PathDescription entry(ShapeType::Path, paint);
+ entry.shape.path.mGenerationID = path->getGenerationID();
+ mCache.remove(entry);
+}
+
void PathCache::precache(const SkPath* path, const SkPaint* paint) {
if (!Caches::getInstance().tasks.canRunTasks()) {
return;
}
- PathDescription entry(kShapePath, paint);
+ PathDescription entry(ShapeType::Path, paint);
entry.shape.path.mGenerationID = path->getGenerationID();
PathTexture* texture = mCache.get(entry);
@@ -444,7 +460,7 @@ void PathCache::precache(const SkPath* path, const SkPaint* paint) {
PathTexture* PathCache::getRoundRect(float width, float height,
float rx, float ry, const SkPaint* paint) {
- PathDescription entry(kShapeRoundRect, paint);
+ PathDescription entry(ShapeType::RoundRect, paint);
entry.shape.roundRect.mWidth = width;
entry.shape.roundRect.mHeight = height;
entry.shape.roundRect.mRx = rx;
@@ -469,7 +485,7 @@ PathTexture* PathCache::getRoundRect(float width, float height,
///////////////////////////////////////////////////////////////////////////////
PathTexture* PathCache::getCircle(float radius, const SkPaint* paint) {
- PathDescription entry(kShapeCircle, paint);
+ PathDescription entry(ShapeType::Circle, paint);
entry.shape.circle.mRadius = radius;
PathTexture* texture = get(entry);
@@ -489,7 +505,7 @@ PathTexture* PathCache::getCircle(float radius, const SkPaint* paint) {
///////////////////////////////////////////////////////////////////////////////
PathTexture* PathCache::getOval(float width, float height, const SkPaint* paint) {
- PathDescription entry(kShapeOval, paint);
+ PathDescription entry(ShapeType::Oval, paint);
entry.shape.oval.mWidth = width;
entry.shape.oval.mHeight = height;
@@ -512,7 +528,7 @@ PathTexture* PathCache::getOval(float width, float height, const SkPaint* paint)
///////////////////////////////////////////////////////////////////////////////
PathTexture* PathCache::getRect(float width, float height, const SkPaint* paint) {
- PathDescription entry(kShapeRect, paint);
+ PathDescription entry(ShapeType::Rect, paint);
entry.shape.rect.mWidth = width;
entry.shape.rect.mHeight = height;
@@ -536,7 +552,7 @@ PathTexture* PathCache::getRect(float width, float height, const SkPaint* paint)
PathTexture* PathCache::getArc(float width, float height,
float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint) {
- PathDescription entry(kShapeArc, paint);
+ PathDescription entry(ShapeType::Arc, paint);
entry.shape.arc.mWidth = width;
entry.shape.arc.mHeight = height;
entry.shape.arc.mStartAngle = startAngle;
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 70148631db34..6368ddd49966 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -25,10 +25,12 @@
#include "utils/Pair.h"
#include <GLES2/gl2.h>
+#include <SkPaint.h>
#include <SkPath.h>
#include <utils/LruCache.h>
#include <utils/Mutex.h>
-#include <utils/Vector.h>
+
+#include <vector>
class SkBitmap;
class SkCanvas;
@@ -60,13 +62,11 @@ class Caches;
*/
struct PathTexture: public Texture {
PathTexture(Caches& caches, float left, float top,
- float offset, int width, int height, int generation)
+ float offset, int generation)
: Texture(caches)
, left(left)
, top(top)
, offset(offset) {
- this->width = width;
- this->height = height;
this->generation = generation;
}
PathTexture(Caches& caches, int generation)
@@ -109,18 +109,18 @@ private:
sp<Task<SkBitmap*> > mTask;
}; // struct PathTexture
-enum ShapeType {
- kShapeNone,
- kShapeRect,
- kShapeRoundRect,
- kShapeCircle,
- kShapeOval,
- kShapeArc,
- kShapePath
+enum class ShapeType {
+ None,
+ Rect,
+ RoundRect,
+ Circle,
+ Oval,
+ Arc,
+ Path
};
struct PathDescription {
- DESCRIPTION_TYPE(PathDescription);
+ HASHABLE_TYPE(PathDescription);
ShapeType type;
SkPaint::Join join;
SkPaint::Cap cap;
@@ -160,8 +160,6 @@ struct PathDescription {
PathDescription();
PathDescription(ShapeType shapeType, const SkPaint* paint);
-
- hash_t hash() const;
};
/**
@@ -201,6 +199,7 @@ public:
PathTexture* getArc(float width, float height, float startAngle, float sweepAngle,
bool useCenter, const SkPaint* paint);
PathTexture* get(const SkPath* path, const SkPaint* paint);
+ void remove(const SkPath* path, const SkPaint* paint);
/**
* Removes the specified path. This is meant to be called from threads
@@ -300,14 +299,14 @@ private:
LruCache<PathDescription, PathTexture*> mCache;
uint32_t mSize;
- uint32_t mMaxSize;
+ const uint32_t mMaxSize;
GLuint mMaxTextureSize;
bool mDebugEnabled;
sp<PathProcessor> mProcessor;
- Vector<uint32_t> mGarbage;
+ std::vector<uint32_t> mGarbage;
mutable Mutex mLock;
}; // class PathCache
diff --git a/libs/hwui/PathParser.cpp b/libs/hwui/PathParser.cpp
new file mode 100644
index 000000000000..2179f146115a
--- /dev/null
+++ b/libs/hwui/PathParser.cpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2015 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 "PathParser.h"
+
+#include "jni.h"
+
+#include <errno.h>
+#include <utils/Log.h>
+#include <sstream>
+#include <stdlib.h>
+#include <string>
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+static size_t nextStart(const char* s, size_t length, size_t startIndex) {
+ size_t index = startIndex;
+ while (index < length) {
+ char c = s[index];
+ // Note that 'e' or 'E' are not valid path commands, but could be
+ // used for floating point numbers' scientific notation.
+ // Therefore, when searching for next command, we should ignore 'e'
+ // and 'E'.
+ if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
+ && c != 'e' && c != 'E') {
+ return index;
+ }
+ index++;
+ }
+ return index;
+}
+
+/**
+ * Calculate the position of the next comma or space or negative sign
+ * @param s the string to search
+ * @param start the position to start searching
+ * @param result the result of the extraction, including the position of the
+ * the starting position of next number, whether it is ending with a '-'.
+ */
+static void extract(int* outEndPosition, bool* outEndWithNegOrDot, const char* s, int start, int end) {
+ // Now looking for ' ', ',', '.' or '-' from the start.
+ int currentIndex = start;
+ bool foundSeparator = false;
+ *outEndWithNegOrDot = false;
+ bool secondDot = false;
+ bool isExponential = false;
+ for (; currentIndex < end; currentIndex++) {
+ bool isPrevExponential = isExponential;
+ isExponential = false;
+ char currentChar = s[currentIndex];
+ switch (currentChar) {
+ case ' ':
+ case ',':
+ foundSeparator = true;
+ break;
+ case '-':
+ // The negative sign following a 'e' or 'E' is not a separator.
+ if (currentIndex != start && !isPrevExponential) {
+ foundSeparator = true;
+ *outEndWithNegOrDot = true;
+ }
+ break;
+ case '.':
+ if (!secondDot) {
+ secondDot = true;
+ } else {
+ // This is the second dot, and it is considered as a separator.
+ foundSeparator = true;
+ *outEndWithNegOrDot = true;
+ }
+ break;
+ case 'e':
+ case 'E':
+ isExponential = true;
+ break;
+ }
+ if (foundSeparator) {
+ break;
+ }
+ }
+ // In the case where nothing is found, we put the end position to the end of
+ // our extract range. Otherwise, end position will be where separator is found.
+ *outEndPosition = currentIndex;
+}
+
+static float parseFloat(PathParser::ParseResult* result, const char* startPtr, size_t expectedLength) {
+ char* endPtr = NULL;
+ float currentValue = strtof(startPtr, &endPtr);
+ if ((currentValue == HUGE_VALF || currentValue == -HUGE_VALF) && errno == ERANGE) {
+ result->failureOccurred = true;
+ result->failureMessage = "Float out of range: ";
+ result->failureMessage.append(startPtr, expectedLength);
+ }
+ if (currentValue == 0 && endPtr == startPtr) {
+ // No conversion is done.
+ result->failureOccurred = true;
+ result->failureMessage = "Float format error when parsing: ";
+ result->failureMessage.append(startPtr, expectedLength);
+ }
+ return currentValue;
+}
+
+/**
+ * Parse the floats in the string.
+ *
+ * @param s the string containing a command and list of floats
+ * @return true on success
+ */
+static void getFloats(std::vector<float>* outPoints, PathParser::ParseResult* result,
+ const char* pathStr, int start, int end) {
+
+ if (pathStr[start] == 'z' || pathStr[start] == 'Z') {
+ return;
+ }
+ int startPosition = start + 1;
+ int endPosition = start;
+
+ // The startPosition should always be the first character of the
+ // current number, and endPosition is the character after the current
+ // number.
+ while (startPosition < end) {
+ bool endWithNegOrDot;
+ extract(&endPosition, &endWithNegOrDot, pathStr, startPosition, end);
+
+ if (startPosition < endPosition) {
+ float currentValue = parseFloat(result, &pathStr[startPosition],
+ end - startPosition);
+ if (result->failureOccurred) {
+ return;
+ }
+ outPoints->push_back(currentValue);
+ }
+
+ if (endWithNegOrDot) {
+ // Keep the '-' or '.' sign with next number.
+ startPosition = endPosition;
+ } else {
+ startPosition = endPosition + 1;
+ }
+ }
+ return;
+}
+
+bool PathParser::isVerbValid(char verb) {
+ verb = tolower(verb);
+ return verb == 'a' || verb == 'c' || verb == 'h' || verb == 'l' || verb == 'm' || verb == 'q'
+ || verb == 's' || verb == 't' || verb == 'v' || verb == 'z';
+}
+
+void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result,
+ const char* pathStr, size_t strLen) {
+ if (pathStr == NULL) {
+ result->failureOccurred = true;
+ result->failureMessage = "Path string cannot be NULL.";
+ return;
+ }
+
+ size_t start = 0;
+ // Skip leading spaces.
+ while (isspace(pathStr[start]) && start < strLen) {
+ start++;
+ }
+ if (start == strLen) {
+ result->failureOccurred = true;
+ result->failureMessage = "Path string cannot be empty.";
+ return;
+ }
+ size_t end = start + 1;
+
+ while (end < strLen) {
+ end = nextStart(pathStr, strLen, end);
+ std::vector<float> points;
+ getFloats(&points, result, pathStr, start, end);
+ if (!isVerbValid(pathStr[start])) {
+ result->failureOccurred = true;
+ result->failureMessage = "Invalid pathData. Failure occurred at position "
+ + std::to_string(start) + " of path: " + pathStr;
+ }
+ // If either verb or points is not valid, return immediately.
+ if (result->failureOccurred) {
+ return;
+ }
+ data->verbs.push_back(pathStr[start]);
+ data->verbSizes.push_back(points.size());
+ data->points.insert(data->points.end(), points.begin(), points.end());
+ start = end;
+ end++;
+ }
+
+ if ((end - start) == 1 && start < strLen) {
+ if (!isVerbValid(pathStr[start])) {
+ result->failureOccurred = true;
+ result->failureMessage = "Invalid pathData. Failure occurred at position "
+ + std::to_string(start) + " of path: " + pathStr;
+ return;
+ }
+ data->verbs.push_back(pathStr[start]);
+ data->verbSizes.push_back(0);
+ }
+}
+
+void PathParser::dump(const PathData& data) {
+ // Print out the path data.
+ size_t start = 0;
+ for (size_t i = 0; i < data.verbs.size(); i++) {
+ std::ostringstream os;
+ os << data.verbs[i];
+ os << ", verb size: " << data.verbSizes[i];
+ for (size_t j = 0; j < data.verbSizes[i]; j++) {
+ os << " " << data.points[start + j];
+ }
+ start += data.verbSizes[i];
+ ALOGD("%s", os.str().c_str());
+ }
+
+ std::ostringstream os;
+ for (size_t i = 0; i < data.points.size(); i++) {
+ os << data.points[i] << ", ";
+ }
+ ALOGD("points are : %s", os.str().c_str());
+}
+
+void PathParser::parseAsciiStringForSkPath(SkPath* skPath, ParseResult* result, const char* pathStr, size_t strLen) {
+ PathData pathData;
+ getPathDataFromAsciiString(&pathData, result, pathStr, strLen);
+ if (result->failureOccurred) {
+ return;
+ }
+ // Check if there is valid data coming out of parsing the string.
+ if (pathData.verbs.size() == 0) {
+ result->failureOccurred = true;
+ result->failureMessage = "No verbs found in the string for pathData: ";
+ result->failureMessage += pathStr;
+ return;
+ }
+ VectorDrawableUtils::verbsToPath(skPath, pathData);
+ return;
+}
+
+}; // namespace uirenderer
+}; //namespace android
diff --git a/libs/hwui/PathParser.h b/libs/hwui/PathParser.h
new file mode 100644
index 000000000000..5578e8d42e2f
--- /dev/null
+++ b/libs/hwui/PathParser.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 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_PATHPARSER_H
+#define ANDROID_HWUI_PATHPARSER_H
+
+#include "VectorDrawable.h"
+#include "utils/VectorDrawableUtils.h"
+
+#include <jni.h>
+#include <android/log.h>
+#include <cutils/compiler.h>
+
+#include <string>
+
+namespace android {
+namespace uirenderer {
+
+
+class PathParser {
+public:
+ struct ANDROID_API ParseResult {
+ bool failureOccurred = false;
+ std::string failureMessage;
+ };
+ /**
+ * Parse the string literal and create a Skia Path. Return true on success.
+ */
+ ANDROID_API static void parseAsciiStringForSkPath(SkPath* outPath, ParseResult* result,
+ const char* pathStr, size_t strLength);
+ ANDROID_API static void getPathDataFromAsciiString(PathData* outData, ParseResult* result,
+ const char* pathStr, size_t strLength);
+ static void dump(const PathData& data);
+ static bool isVerbValid(char verb);
+};
+
+}; // namespace uirenderer
+}; // namespace android
+#endif //ANDROID_HWUI_PATHPARSER_H
diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp
index 38f214ab3e5d..9246237aeffb 100644
--- a/libs/hwui/PathTessellator.cpp
+++ b/libs/hwui/PathTessellator.cpp
@@ -13,10 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-#define LOG_TAG "OpenGLRenderer"
#define LOG_NDEBUG 1
-#define ATRACE_TAG ATRACE_TAG_VIEW
#define VERTEX_DEBUG 0
@@ -35,6 +32,15 @@
#define DEBUG_DUMP_BUFFER()
#endif
+#include "PathTessellator.h"
+
+#include "Matrix.h"
+#include "Vector.h"
+#include "Vertex.h"
+#include "utils/MathUtils.h"
+
+#include <algorithm>
+
#include <SkPath.h>
#include <SkPaint.h>
#include <SkPoint.h>
@@ -47,12 +53,6 @@
#include <utils/Log.h>
#include <utils/Trace.h>
-#include "PathTessellator.h"
-#include "Matrix.h"
-#include "Vector.h"
-#include "Vertex.h"
-#include "utils/MathUtils.h"
-
namespace android {
namespace uirenderer {
@@ -155,7 +155,7 @@ public:
// always use 2 points for hairline
if (halfStrokeWidth == 0.0f) return 2;
- float threshold = MathUtils::min(inverseScaleX, inverseScaleY) * ROUND_CAP_THRESH;
+ float threshold = std::min(inverseScaleX, inverseScaleY) * ROUND_CAP_THRESH;
return MathUtils::divisionsNeededToApproximateArc(halfStrokeWidth, PI, threshold);
}
return 0;
@@ -180,7 +180,8 @@ public:
}
};
-void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) {
+void getFillVerticesFromPerimeter(const std::vector<Vertex>& perimeter,
+ VertexBuffer& vertexBuffer) {
Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size());
int currentIndex = 0;
@@ -204,8 +205,8 @@ void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer&
* Uses an additional 2 vertices at the end to wrap around, closing the tri-strip
* (for a total of perimeter.size() * 2 + 2 vertices)
*/
-void getStrokeVerticesFromPerimeter(const PaintInfo& paintInfo, const Vector<Vertex>& perimeter,
- VertexBuffer& vertexBuffer) {
+void getStrokeVerticesFromPerimeter(const PaintInfo& paintInfo,
+ const std::vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) {
Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size() * 2 + 2);
int currentIndex = 0;
@@ -263,7 +264,7 @@ static inline void storeBeginEnd(const PaintInfo& paintInfo, const Vertex& cente
* 2 - can zig-zag across 'extra' vertices at either end, to create round caps
*/
void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo,
- const Vector<Vertex>& vertices, VertexBuffer& vertexBuffer) {
+ const std::vector<Vertex>& vertices, VertexBuffer& vertexBuffer) {
const int extra = paintInfo.capExtraDivisions();
const int allocSize = (vertices.size() + extra) * 2;
Vertex* buffer = vertexBuffer.alloc<Vertex>(allocSize);
@@ -342,8 +343,9 @@ void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo,
*
* 3 - zig zag back and forth inside the shape to fill it (using perimeter.size() vertices)
*/
-void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Vertex>& perimeter,
- VertexBuffer& vertexBuffer, float maxAlpha = 1.0f) {
+void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo,
+ const std::vector<Vertex>& perimeter, VertexBuffer& vertexBuffer,
+ float maxAlpha = 1.0f) {
AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2);
// generate alpha points - fill Alpha vertex gaps in between each point with
@@ -401,7 +403,7 @@ void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Ver
* For explanation of constants and general methodoloyg, see comments for
* getStrokeVerticesFromUnclosedVerticesAA() below.
*/
-inline static void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>& vertices,
+inline static void storeCapAA(const PaintInfo& paintInfo, const std::vector<Vertex>& vertices,
AlphaVertex* buffer, bool isFirst, Vector2 normal, int offset) {
const int extra = paintInfo.capExtraDivisions();
const int extraOffset = (extra + 1) / 2;
@@ -426,8 +428,8 @@ inline static void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>&
}
// determine referencePoint, the center point for the 4 primary cap vertices
- const Vertex* point = isFirst ? vertices.begin() : (vertices.end() - 1);
- Vector2 referencePoint = {point->x, point->y};
+ const Vertex& point = isFirst ? vertices.front() : vertices.back();
+ Vector2 referencePoint = {point.x, point.y};
if (paintInfo.cap == SkPaint::kSquare_Cap) {
// To account for square cap, move the primary cap vertices (that create the AA edge) by the
// stroke offset vector (rotated to be parallel to the stroke)
@@ -572,7 +574,7 @@ or, for rounded caps:
= 2 + 6 * pts + 6 * roundDivs
*/
void getStrokeVerticesFromUnclosedVerticesAA(const PaintInfo& paintInfo,
- const Vector<Vertex>& vertices, VertexBuffer& vertexBuffer) {
+ const std::vector<Vertex>& vertices, VertexBuffer& vertexBuffer) {
const int extra = paintInfo.capExtraDivisions();
const int allocSize = 6 * vertices.size() + 2 + 6 * extra;
@@ -645,8 +647,8 @@ void getStrokeVerticesFromUnclosedVerticesAA(const PaintInfo& paintInfo,
}
-void getStrokeVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Vertex>& perimeter,
- VertexBuffer& vertexBuffer) {
+void getStrokeVerticesFromPerimeterAA(const PaintInfo& paintInfo,
+ const std::vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) {
AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * perimeter.size() + 8);
int offset = 2 * perimeter.size() + 3;
@@ -724,7 +726,7 @@ void PathTessellator::tessellatePath(const SkPath &path, const SkPaint* paint,
const PaintInfo paintInfo(paint, transform);
- Vector<Vertex> tempVertices;
+ std::vector<Vertex> tempVertices;
float threshInvScaleX = paintInfo.inverseScaleX;
float threshInvScaleY = paintInfo.inverseScaleY;
if (paintInfo.style == SkPaint::kStroke_Style) {
@@ -797,7 +799,7 @@ static void instanceVertices(VertexBuffer& srcBuffer, VertexBuffer& dstBuffer,
dstBuffer.alloc<TYPE>(numPoints * verticesPerPoint + (numPoints - 1) * 2);
for (int i = 0; i < count; i += 2) {
- bounds.expandToCoverVertex(points[i + 0], points[i + 1]);
+ bounds.expandToCover(points[i + 0], points[i + 1]);
dstBuffer.copyInto<TYPE>(srcBuffer, points[i + 0], points[i + 1]);
}
dstBuffer.createDegenerateSeparators<TYPE>(verticesPerPoint);
@@ -819,7 +821,7 @@ void PathTessellator::tessellatePoints(const float* points, int count, const SkP
}
// calculate outline
- Vector<Vertex> outlineVertices;
+ std::vector<Vertex> outlineVertices;
PathApproximationInfo approximationInfo(paintInfo.inverseScaleX, paintInfo.inverseScaleY,
OUTLINE_REFINE_THRESHOLD);
approximatePathOutlineVertices(path, true, approximationInfo, outlineVertices);
@@ -861,10 +863,8 @@ void PathTessellator::tessellateLines(const float* points, int count, const SkPa
vertexBuffer.alloc<Vertex>(numLines * lineAllocSize + (numLines - 1) * 2);
}
- Vector<Vertex> tempVertices;
- tempVertices.push();
- tempVertices.push();
- Vertex* tempVerticesData = tempVertices.editArray();
+ std::vector<Vertex> tempVertices(2);
+ Vertex* tempVerticesData = &tempVertices.front();
Rect bounds;
bounds.set(points[0], points[1], points[0], points[1]);
for (int i = 0; i < count; i += 4) {
@@ -878,8 +878,8 @@ void PathTessellator::tessellateLines(const float* points, int count, const SkPa
}
// calculate bounds
- bounds.expandToCoverVertex(tempVerticesData[0].x, tempVerticesData[0].y);
- bounds.expandToCoverVertex(tempVerticesData[1].x, tempVerticesData[1].y);
+ bounds.expandToCover(tempVerticesData[0].x, tempVerticesData[0].y);
+ bounds.expandToCover(tempVerticesData[1].x, tempVerticesData[1].y);
}
// since multiple objects tessellated into buffer, separate them with degen tris
@@ -900,18 +900,11 @@ void PathTessellator::tessellateLines(const float* points, int count, const SkPa
///////////////////////////////////////////////////////////////////////////////
bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, float threshold,
- Vector<Vertex>& outputVertices) {
+ std::vector<Vertex>& outputVertices) {
PathApproximationInfo approximationInfo(1.0f, 1.0f, threshold);
return approximatePathOutlineVertices(path, true, approximationInfo, outputVertices);
}
-void pushToVector(Vector<Vertex>& vertices, float x, float y) {
- // TODO: make this not yuck
- vertices.push();
- Vertex* newVertex = &(vertices.editArray()[vertices.size() - 1]);
- Vertex::set(newVertex, x, y);
-}
-
class ClockwiseEnforcer {
public:
void addPoint(const SkPoint& point) {
@@ -927,15 +920,15 @@ public:
lastX = x;
lastY = y;
}
- void reverseVectorIfNotClockwise(Vector<Vertex>& vertices) {
+ void reverseVectorIfNotClockwise(std::vector<Vertex>& vertices) {
if (sum < 0) {
// negative sum implies CounterClockwise
const int size = vertices.size();
for (int i = 0; i < size / 2; i++) {
Vertex tmp = vertices[i];
int k = size - 1 - i;
- vertices.replaceAt(vertices[k], i);
- vertices.replaceAt(tmp, k);
+ vertices[i] = vertices[k];
+ vertices[k] = tmp;
}
}
}
@@ -947,7 +940,7 @@ private:
};
bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool forceClose,
- const PathApproximationInfo& approximationInfo, Vector<Vertex>& outputVertices) {
+ const PathApproximationInfo& approximationInfo, std::vector<Vertex>& outputVertices) {
ATRACE_CALL();
// TODO: to support joins other than sharp miter, join vertices should be labelled in the
@@ -959,7 +952,7 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo
while (SkPath::kDone_Verb != (v = iter.next(pts))) {
switch (v) {
case SkPath::kMove_Verb:
- pushToVector(outputVertices, pts[0].x(), pts[0].y());
+ outputVertices.push_back(Vertex{pts[0].x(), pts[0].y()});
ALOGV("Move to pos %f %f", pts[0].x(), pts[0].y());
clockwiseEnforcer.addPoint(pts[0]);
break;
@@ -969,7 +962,7 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo
break;
case SkPath::kLine_Verb:
ALOGV("kLine_Verb %f %f -> %f %f", pts[0].x(), pts[0].y(), pts[1].x(), pts[1].y());
- pushToVector(outputVertices, pts[1].x(), pts[1].y());
+ outputVertices.push_back(Vertex{pts[1].x(), pts[1].y()});
clockwiseEnforcer.addPoint(pts[1]);
break;
case SkPath::kQuad_Verb:
@@ -1020,7 +1013,7 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo
int size = outputVertices.size();
if (size >= 2 && outputVertices[0].x == outputVertices[size - 1].x &&
outputVertices[0].y == outputVertices[size - 1].y) {
- outputVertices.pop();
+ outputVertices.pop_back();
wasClosed = true;
}
@@ -1048,7 +1041,7 @@ void PathTessellator::recursiveCubicBezierVertices(
float p1x, float p1y, float c1x, float c1y,
float p2x, float p2y, float c2x, float c2y,
const PathApproximationInfo& approximationInfo,
- Vector<Vertex>& outputVertices, int depth) {
+ std::vector<Vertex>& outputVertices, int depth) {
float dx = p2x - p1x;
float dy = p2y - p1y;
float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx);
@@ -1058,7 +1051,7 @@ void PathTessellator::recursiveCubicBezierVertices(
if (depth >= MAX_DEPTH
|| d * d <= getThreshold(approximationInfo, dx, dy)) {
// below thresh, draw line by adding endpoint
- pushToVector(outputVertices, p2x, p2y);
+ outputVertices.push_back(Vertex{p2x, p2y});
} else {
float p1c1x = (p1x + c1x) * 0.5f;
float p1c1y = (p1y + c1y) * 0.5f;
@@ -1093,7 +1086,7 @@ void PathTessellator::recursiveQuadraticBezierVertices(
float bx, float by,
float cx, float cy,
const PathApproximationInfo& approximationInfo,
- Vector<Vertex>& outputVertices, int depth) {
+ std::vector<Vertex>& outputVertices, int depth) {
float dx = bx - ax;
float dy = by - ay;
// d is the cross product of vector (B-A) and (C-B).
@@ -1102,7 +1095,7 @@ void PathTessellator::recursiveQuadraticBezierVertices(
if (depth >= MAX_DEPTH
|| d * d <= getThreshold(approximationInfo, dx, dy)) {
// below thresh, draw line by adding endpoint
- pushToVector(outputVertices, bx, by);
+ outputVertices.push_back(Vertex{bx, by});
} else {
float acx = (ax + cx) * 0.5f;
float bcx = (bx + cx) * 0.5f;
diff --git a/libs/hwui/PathTessellator.h b/libs/hwui/PathTessellator.h
index 16c8b36a6a9d..cddfb049212c 100644
--- a/libs/hwui/PathTessellator.h
+++ b/libs/hwui/PathTessellator.h
@@ -17,13 +17,17 @@
#ifndef ANDROID_HWUI_PATH_TESSELLATOR_H
#define ANDROID_HWUI_PATH_TESSELLATOR_H
-#include <utils/Vector.h>
-
#include "Matrix.h"
#include "Rect.h"
#include "Vertex.h"
#include "VertexBuffer.h"
+#include <algorithm>
+#include <vector>
+
+class SkPath;
+class SkPaint;
+
namespace android {
namespace uirenderer {
@@ -38,7 +42,7 @@ struct PathApproximationInfo {
: thresholdSquared(pixelThreshold * pixelThreshold)
, sqrInvScaleX(invScaleX * invScaleX)
, sqrInvScaleY(invScaleY * invScaleY)
- , thresholdForConicQuads(pixelThreshold * MathUtils::min(invScaleX, invScaleY) / 2.0f) {
+ , thresholdForConicQuads(pixelThreshold * std::min(invScaleX, invScaleY) / 2.0f) {
};
const float thresholdSquared;
@@ -109,11 +113,11 @@ public:
* @param outputVertices An empty Vector which will be populated with the output
*/
static bool approximatePathOutlineVertices(const SkPath &path, float threshold,
- Vector<Vertex> &outputVertices);
+ std::vector<Vertex> &outputVertices);
private:
static bool approximatePathOutlineVertices(const SkPath &path, bool forceClose,
- const PathApproximationInfo& approximationInfo, Vector<Vertex> &outputVertices);
+ const PathApproximationInfo& approximationInfo, std::vector<Vertex> &outputVertices);
/*
endpoints a & b,
@@ -124,7 +128,7 @@ private:
float bx, float by,
float cx, float cy,
const PathApproximationInfo& approximationInfo,
- Vector<Vertex> &outputVertices, int depth = 0);
+ std::vector<Vertex> &outputVertices, int depth = 0);
/*
endpoints p1, p2
@@ -136,7 +140,7 @@ private:
float p2x, float p2y,
float c2x, float c2y,
const PathApproximationInfo& approximationInfo,
- Vector<Vertex> &outputVertices, int depth = 0);
+ std::vector<Vertex> &outputVertices, int depth = 0);
};
}; // namespace uirenderer
diff --git a/libs/hwui/PixelBuffer.cpp b/libs/hwui/PixelBuffer.cpp
index 9665a68b0e77..165c7db4c85f 100644
--- a/libs/hwui/PixelBuffer.cpp
+++ b/libs/hwui/PixelBuffer.cpp
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include "PixelBuffer.h"
#include "Debug.h"
#include "Extensions.h"
#include "Properties.h"
#include "renderstate/RenderState.h"
+#include "utils/GLUtils.h"
#include <utils/Log.h>
@@ -37,12 +36,14 @@ public:
CpuPixelBuffer(GLenum format, uint32_t width, uint32_t height);
uint8_t* map(AccessMode mode = kAccessMode_ReadWrite) override;
- void unmap() override;
uint8_t* getMappedPointer() const override;
void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) override;
+protected:
+ void unmap() override;
+
private:
std::unique_ptr<uint8_t[]> mBuffer;
};
@@ -82,12 +83,14 @@ public:
~GpuPixelBuffer();
uint8_t* map(AccessMode mode = kAccessMode_ReadWrite) override;
- void unmap() override;
uint8_t* getMappedPointer() const override;
void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) override;
+protected:
+ void unmap() override;
+
private:
GLuint mBuffer;
uint8_t* mMappedPointer;
@@ -114,15 +117,12 @@ uint8_t* GpuPixelBuffer::map(AccessMode mode) {
if (mAccessMode == kAccessMode_None) {
mCaches.pixelBufferState().bind(mBuffer);
mMappedPointer = (uint8_t*) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, getSize(), mode);
-#if DEBUG_OPENGL
- if (!mMappedPointer) {
- GLenum status = GL_NO_ERROR;
- while ((status = glGetError()) != GL_NO_ERROR) {
- ALOGE("Could not map GPU pixel buffer: 0x%x", status);
- }
+ if (CC_UNLIKELY(!mMappedPointer)) {
+ GLUtils::dumpGLErrors();
+ LOG_ALWAYS_FATAL("Failed to map PBO");
}
-#endif
mAccessMode = mode;
+ mCaches.pixelBufferState().unbind();
}
return mMappedPointer;
@@ -152,6 +152,7 @@ void GpuPixelBuffer::upload(uint32_t x, uint32_t y, uint32_t width, uint32_t hei
unmap();
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, mFormat,
GL_UNSIGNED_BYTE, reinterpret_cast<void*>(offset));
+ mCaches.pixelBufferState().unbind();
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/PixelBuffer.h b/libs/hwui/PixelBuffer.h
index aac5ec4777ee..bbef36b72e4f 100644
--- a/libs/hwui/PixelBuffer.h
+++ b/libs/hwui/PixelBuffer.h
@@ -91,14 +91,6 @@ public:
virtual uint8_t* map(AccessMode mode = kAccessMode_ReadWrite) = 0;
/**
- * Unmaps this buffer, if needed. After the buffer is unmapped,
- * the pointer previously returned by map() becomes invalid and
- * should not be used. After calling this method, getMappedPointer()
- * will always return NULL.
- */
- virtual void unmap() = 0;
-
- /**
* Returns the current access mode for this buffer. If the buffer
* is not mapped, this method returns kAccessMode_None.
*/
@@ -204,6 +196,14 @@ protected:
mFormat(format), mWidth(width), mHeight(height), mAccessMode(kAccessMode_None) {
}
+ /**
+ * Unmaps this buffer, if needed. After the buffer is unmapped,
+ * the pointer previously returned by map() becomes invalid and
+ * should not be used. After calling this method, getMappedPointer()
+ * will always return NULL.
+ */
+ virtual void unmap() = 0;
+
GLenum mFormat;
uint32_t mWidth;
diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp
index 32713e9b36f3..e43b80d440e7 100644
--- a/libs/hwui/Program.cpp
+++ b/libs/hwui/Program.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-#define ATRACE_TAG ATRACE_TAG_VIEW
-
#include <utils/Trace.h>
#include "Program.h"
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index af1e4a74d46e..e5200a516777 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -78,14 +78,12 @@ namespace uirenderer {
#define PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT 38
#define PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT 39
-#define PROGRAM_HAS_GAMMA_CORRECTION 40
+#define PROGRAM_IS_SIMPLE_GRADIENT 40
-#define PROGRAM_IS_SIMPLE_GRADIENT 41
+#define PROGRAM_HAS_COLORS 41
-#define PROGRAM_HAS_COLORS 42
-
-#define PROGRAM_HAS_DEBUG_HIGHLIGHT 43
-#define PROGRAM_HAS_ROUND_RECT_CLIP 44
+#define PROGRAM_HAS_DEBUG_HIGHLIGHT 42
+#define PROGRAM_HAS_ROUND_RECT_CLIP 43
///////////////////////////////////////////////////////////////////////////////
// Types
@@ -103,10 +101,10 @@ typedef uint64_t programid;
* A ProgramDescription must be used in conjunction with a ProgramCache.
*/
struct ProgramDescription {
- enum ColorFilterMode {
- kColorNone = 0,
- kColorMatrix,
- kColorBlend
+ enum class ColorFilterMode {
+ None = 0,
+ Matrix,
+ Blend
};
enum Gradient {
@@ -157,9 +155,6 @@ struct ProgramDescription {
SkXfermode::Mode framebufferMode;
bool swapSrcDst;
- bool hasGammaCorrection;
- float gamma;
-
bool hasDebugHighlight;
bool hasRoundRectClip;
@@ -193,15 +188,12 @@ struct ProgramDescription {
bitmapWrapS = GL_CLAMP_TO_EDGE;
bitmapWrapT = GL_CLAMP_TO_EDGE;
- colorOp = kColorNone;
+ colorOp = ColorFilterMode::None;
colorMode = SkXfermode::kClear_Mode;
framebufferMode = SkXfermode::kClear_Mode;
swapSrcDst = false;
- hasGammaCorrection = false;
- gamma = 2.2f;
-
hasDebugHighlight = false;
hasRoundRectClip = false;
}
@@ -249,14 +241,14 @@ struct ProgramDescription {
key |= (shadersMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_SHADER_SHIFT;
}
switch (colorOp) {
- case kColorMatrix:
+ case ColorFilterMode::Matrix:
key |= PROGRAM_KEY_COLOR_MATRIX;
break;
- case kColorBlend:
+ case ColorFilterMode::Blend:
key |= PROGRAM_KEY_COLOR_BLEND;
key |= (colorMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_COLOR_OP_SHIFT;
break;
- case kColorNone:
+ case ColorFilterMode::None:
break;
}
key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT;
@@ -266,7 +258,6 @@ struct ProgramDescription {
if (useShadowAlphaInterp) key |= programid(0x1) << PROGRAM_USE_SHADOW_ALPHA_INTERP_SHIFT;
if (hasExternalTexture) key |= programid(0x1) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT;
if (hasTextureTransform) key |= programid(0x1) << PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT;
- if (hasGammaCorrection) key |= programid(0x1) << PROGRAM_HAS_GAMMA_CORRECTION;
if (isSimpleGradient) key |= programid(0x1) << PROGRAM_IS_SIMPLE_GRADIENT;
if (hasColors) key |= programid(0x1) << PROGRAM_HAS_COLORS;
if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT;
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 41adda15f367..05be48822fb2 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <utils/String8.h>
#include "Caches.h"
@@ -40,7 +38,8 @@ namespace uirenderer {
// Vertex shaders snippets
///////////////////////////////////////////////////////////////////////////////
-const char* gVS_Header_Attributes =
+const char* gVS_Header_Start =
+ "#version 100\n"
"attribute vec4 position;\n";
const char* gVS_Header_Attributes_TexCoords =
"attribute vec2 texCoords;\n";
@@ -134,6 +133,8 @@ const char* gVS_Footer =
// Fragment shaders snippets
///////////////////////////////////////////////////////////////////////////////
+const char* gFS_Header_Start =
+ "#version 100\n";
const char* gFS_Header_Extension_FramebufferFetch =
"#extension GL_NV_shader_framebuffer_fetch : enable\n\n";
const char* gFS_Header_Extension_ExternalTexture =
@@ -166,8 +167,6 @@ const char* gFS_Uniforms_ColorOp[3] = {
// PorterDuff
"uniform vec4 colorBlend;\n"
};
-const char* gFS_Uniforms_Gamma =
- "uniform float gamma;\n";
const char* gFS_Uniforms_HasRoundRectClip =
"uniform vec4 roundRectInnerRectLTRB;\n"
@@ -203,18 +202,10 @@ const char* gFS_Fast_SingleA8Texture =
"\nvoid main(void) {\n"
" gl_FragColor = texture2D(baseSampler, outTexCoords);\n"
"}\n\n";
-const char* gFS_Fast_SingleA8Texture_ApplyGamma =
- "\nvoid main(void) {\n"
- " gl_FragColor = vec4(0.0, 0.0, 0.0, pow(texture2D(baseSampler, outTexCoords).a, gamma));\n"
- "}\n\n";
const char* gFS_Fast_SingleModulateA8Texture =
"\nvoid main(void) {\n"
" gl_FragColor = color * texture2D(baseSampler, outTexCoords).a;\n"
"}\n\n";
-const char* gFS_Fast_SingleModulateA8Texture_ApplyGamma =
- "\nvoid main(void) {\n"
- " gl_FragColor = color * pow(texture2D(baseSampler, outTexCoords).a, gamma);\n"
- "}\n\n";
const char* gFS_Fast_SingleGradient[2] = {
"\nvoid main(void) {\n"
" gl_FragColor = %s + texture2D(gradientSampler, linear);\n"
@@ -249,13 +240,11 @@ const char* gFS_Main_FetchTexture[2] = {
// Modulate
" fragColor = color * texture2D(baseSampler, outTexCoords);\n"
};
-const char* gFS_Main_FetchA8Texture[4] = {
+const char* gFS_Main_FetchA8Texture[2] = {
// Don't modulate
" fragColor = texture2D(baseSampler, outTexCoords);\n",
- " fragColor = texture2D(baseSampler, outTexCoords);\n",
// Modulate
" fragColor = color * texture2D(baseSampler, outTexCoords).a;\n",
- " fragColor = color * pow(texture2D(baseSampler, outTexCoords).a, gamma);\n"
};
const char* gFS_Main_FetchGradient[6] = {
// Linear
@@ -283,38 +272,29 @@ const char* gFS_Main_BlendShadersBG =
" fragColor = blendShaders(gradientColor, bitmapColor)";
const char* gFS_Main_BlendShadersGB =
" fragColor = blendShaders(bitmapColor, gradientColor)";
-const char* gFS_Main_BlendShaders_Modulate[6] = {
+const char* gFS_Main_BlendShaders_Modulate[3] = {
// Don't modulate
";\n",
- ";\n",
// Modulate
" * color.a;\n",
- " * color.a;\n",
// Modulate with alpha 8 texture
" * texture2D(baseSampler, outTexCoords).a;\n",
- " * pow(texture2D(baseSampler, outTexCoords).a, gamma);\n"
};
-const char* gFS_Main_GradientShader_Modulate[6] = {
+const char* gFS_Main_GradientShader_Modulate[3] = {
// Don't modulate
" fragColor = gradientColor;\n",
- " fragColor = gradientColor;\n",
// Modulate
" fragColor = gradientColor * color.a;\n",
- " fragColor = gradientColor * color.a;\n",
// Modulate with alpha 8 texture
" fragColor = gradientColor * texture2D(baseSampler, outTexCoords).a;\n",
- " fragColor = gradientColor * pow(texture2D(baseSampler, outTexCoords).a, gamma);\n"
};
-const char* gFS_Main_BitmapShader_Modulate[6] = {
+const char* gFS_Main_BitmapShader_Modulate[3] = {
// Don't modulate
" fragColor = bitmapColor;\n",
- " fragColor = bitmapColor;\n",
// Modulate
" fragColor = bitmapColor * color.a;\n",
- " fragColor = bitmapColor * color.a;\n",
// Modulate with alpha 8 texture
" fragColor = bitmapColor * texture2D(baseSampler, outTexCoords).a;\n",
- " fragColor = bitmapColor * pow(texture2D(baseSampler, outTexCoords).a, gamma);\n"
};
const char* gFS_Main_FragColor =
" gl_FragColor = fragColor;\n";
@@ -459,7 +439,7 @@ static inline size_t gradientIndex(const ProgramDescription& description) {
String8 ProgramCache::generateVertexShader(const ProgramDescription& description) {
// Add attributes
- String8 shader(gVS_Header_Attributes);
+ String8 shader(gVS_Header_Start);
if (description.hasTexture || description.hasExternalTexture) {
shader.append(gVS_Header_Attributes_TexCoords);
}
@@ -539,13 +519,12 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
static bool shaderOp(const ProgramDescription& description, String8& shader,
const int modulateOp, const char** snippets) {
int op = description.hasAlpha8Texture ? MODULATE_OP_MODULATE_A8 : modulateOp;
- op = op * 2 + description.hasGammaCorrection;
shader.append(snippets[op]);
return description.hasAlpha8Texture;
}
String8 ProgramCache::generateFragmentShader(const ProgramDescription& description) {
- String8 shader;
+ String8 shader(gFS_Header_Start);
const bool blendFramebuffer = description.framebufferMode >= SkXfermode::kPlus_Mode;
if (blendFramebuffer) {
@@ -595,9 +574,6 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
shader.appendFormat(gFS_Uniforms_GradientSampler[description.isSimpleGradient],
gFS_Uniforms_Dither);
}
- if (description.hasGammaCorrection) {
- shader.append(gFS_Uniforms_Gamma);
- }
if (description.hasRoundRectClip) {
shader.append(gFS_Uniforms_HasRoundRectClip);
}
@@ -606,7 +582,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
if (!description.hasVertexAlpha
&& !blendFramebuffer
&& !description.hasColors
- && description.colorOp == ProgramDescription::kColorNone
+ && description.colorOp == ProgramDescription::ColorFilterMode::None
&& !description.hasDebugHighlight
&& !description.hasRoundRectClip) {
bool fast = false;
@@ -632,17 +608,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
fast = true;
} else if (singleA8Texture) {
if (!description.modulate) {
- if (description.hasGammaCorrection) {
- shader.append(gFS_Fast_SingleA8Texture_ApplyGamma);
- } else {
- shader.append(gFS_Fast_SingleA8Texture);
- }
+ shader.append(gFS_Fast_SingleA8Texture);
} else {
- if (description.hasGammaCorrection) {
- shader.append(gFS_Fast_SingleModulateA8Texture_ApplyGamma);
- } else {
- shader.append(gFS_Fast_SingleModulateA8Texture);
- }
+ shader.append(gFS_Fast_SingleModulateA8Texture);
}
fast = true;
} else if (singleGradient) {
@@ -670,13 +638,13 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
if (description.hasBitmap) {
shader.append(gFS_Uniforms_BitmapSampler);
}
- shader.append(gFS_Uniforms_ColorOp[description.colorOp]);
+ shader.append(gFS_Uniforms_ColorOp[static_cast<int>(description.colorOp)]);
// Generate required functions
if (description.hasGradient && description.hasBitmap) {
generateBlend(shader, "blendShaders", description.shadersMode);
}
- if (description.colorOp == ProgramDescription::kColorBlend) {
+ if (description.colorOp == ProgramDescription::ColorFilterMode::Blend) {
generateBlend(shader, "blendColors", description.colorMode);
}
if (blendFramebuffer) {
@@ -692,8 +660,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
if (description.hasTexture || description.hasExternalTexture) {
if (description.hasAlpha8Texture) {
if (!description.hasGradient && !description.hasBitmap) {
- shader.append(gFS_Main_FetchA8Texture[modulateOp * 2 +
- description.hasGammaCorrection]);
+ shader.append(gFS_Main_FetchA8Texture[modulateOp]);
}
} else {
shader.append(gFS_Main_FetchTexture[modulateOp]);
@@ -739,7 +706,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
}
// Apply the color op if needed
- shader.append(gFS_Main_ApplyColorOp[description.colorOp]);
+ shader.append(gFS_Main_ApplyColorOp[static_cast<int>(description.colorOp)]);
if (description.hasVertexAlpha) {
if (description.useShadowAlphaInterp) {
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 2e63793f6ffe..6f68c2bdff80 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -17,8 +17,12 @@
#include "Debug.h"
-#include <algorithm>
+#include <cutils/compiler.h>
#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include <algorithm>
+#include <cstdlib>
namespace android {
namespace uirenderer {
@@ -29,7 +33,22 @@ bool Properties::debugLayersUpdates = false;
bool Properties::debugOverdraw = false;
bool Properties::showDirtyRegions = false;
bool Properties::skipEmptyFrames = true;
-bool Properties::swapBuffersWithDamage = true;
+bool Properties::useBufferAge = true;
+bool Properties::enablePartialUpdates = true;
+
+float Properties::textGamma = DEFAULT_TEXT_GAMMA;
+
+int Properties::fboCacheSize = DEFAULT_FBO_CACHE_SIZE;
+int Properties::gradientCacheSize = MB(DEFAULT_GRADIENT_CACHE_SIZE);
+int Properties::layerPoolSize = MB(DEFAULT_LAYER_CACHE_SIZE);
+int Properties::patchCacheSize = KB(DEFAULT_PATCH_CACHE_SIZE);
+int Properties::pathCacheSize = MB(DEFAULT_PATH_CACHE_SIZE);
+int Properties::renderBufferCacheSize = MB(DEFAULT_RENDER_BUFFER_CACHE_SIZE);
+int Properties::tessellationCacheSize = MB(DEFAULT_VERTEX_CACHE_SIZE);
+int Properties::textDropShadowCacheSize = MB(DEFAULT_DROP_SHADOW_CACHE_SIZE);
+int Properties::textureCacheSize = MB(DEFAULT_TEXTURE_CACHE_SIZE);
+
+float Properties::textureCacheFlushRate = DEFAULT_TEXTURE_CACHE_FLUSH_RATE;
DebugLevel Properties::debugLevel = kDebugDisabled;
OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default;
@@ -45,13 +64,34 @@ int Properties::overrideSpotShadowStrength = -1;
ProfileType Properties::sProfileType = ProfileType::None;
bool Properties::sDisableProfileBars = false;
+bool Properties::waitForGpuCompletion = false;
+
+bool Properties::filterOutTestOverhead = false;
+
+static int property_get_int(const char* key, int defaultValue) {
+ char buf[PROPERTY_VALUE_MAX] = {'\0',};
+
+ if (property_get(key, buf, "") > 0) {
+ return atoi(buf);
+ }
+ return defaultValue;
+}
+
+static float property_get_float(const char* key, float defaultValue) {
+ char buf[PROPERTY_VALUE_MAX] = {'\0',};
+
+ if (property_get(key, buf, "") > 0) {
+ return atof(buf);
+ }
+ return defaultValue;
+}
+
bool Properties::load() {
char property[PROPERTY_VALUE_MAX];
bool prevDebugLayersUpdates = debugLayersUpdates;
bool prevDebugOverdraw = debugOverdraw;
StencilClipDebug prevDebugStencilClip = debugStencilClip;
-
debugOverdraw = false;
if (property_get(PROPERTY_DEBUG_OVERDRAW, property, nullptr) > 0) {
INIT_LOGD(" Overdraw debug enabled: %s", property);
@@ -98,13 +138,27 @@ bool Properties::load() {
showDirtyRegions = property_get_bool(PROPERTY_DEBUG_SHOW_DIRTY_REGIONS, false);
- debugLevel = kDebugDisabled;
- if (property_get(PROPERTY_DEBUG, property, nullptr) > 0) {
- debugLevel = (DebugLevel) atoi(property);
- }
+ debugLevel = (DebugLevel) property_get_int(PROPERTY_DEBUG, kDebugDisabled);
skipEmptyFrames = property_get_bool(PROPERTY_SKIP_EMPTY_DAMAGE, true);
- swapBuffersWithDamage = property_get_bool(PROPERTY_SWAP_WITH_DAMAGE, true);
+ useBufferAge = property_get_bool(PROPERTY_USE_BUFFER_AGE, true);
+ enablePartialUpdates = property_get_bool(PROPERTY_ENABLE_PARTIAL_UPDATES, true);
+
+ textGamma = property_get_float(PROPERTY_TEXT_GAMMA, DEFAULT_TEXT_GAMMA);
+
+ fboCacheSize = property_get_int(PROPERTY_FBO_CACHE_SIZE, DEFAULT_FBO_CACHE_SIZE);
+ gradientCacheSize = MB(property_get_float(PROPERTY_GRADIENT_CACHE_SIZE, DEFAULT_GRADIENT_CACHE_SIZE));
+ layerPoolSize = MB(property_get_float(PROPERTY_LAYER_CACHE_SIZE, DEFAULT_LAYER_CACHE_SIZE));
+ patchCacheSize = KB(property_get_float(PROPERTY_PATCH_CACHE_SIZE, DEFAULT_PATCH_CACHE_SIZE));
+ pathCacheSize = MB(property_get_float(PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE));
+ renderBufferCacheSize = MB(property_get_float(PROPERTY_RENDER_BUFFER_CACHE_SIZE, DEFAULT_RENDER_BUFFER_CACHE_SIZE));
+ tessellationCacheSize = MB(property_get_float(PROPERTY_VERTEX_CACHE_SIZE, DEFAULT_VERTEX_CACHE_SIZE));
+ textDropShadowCacheSize = MB(property_get_float(PROPERTY_DROP_SHADOW_CACHE_SIZE, DEFAULT_DROP_SHADOW_CACHE_SIZE));
+ textureCacheSize = MB(property_get_float(PROPERTY_TEXTURE_CACHE_SIZE, DEFAULT_TEXTURE_CACHE_SIZE));
+ textureCacheFlushRate = std::max(0.0f, std::min(1.0f,
+ property_get_float(PROPERTY_TEXTURE_CACHE_FLUSH_RATE, DEFAULT_TEXTURE_CACHE_FLUSH_RATE)));
+
+ filterOutTestOverhead = property_get_bool(PROPERTY_FILTER_TEST_OVERHEAD, false);
return (prevDebugLayersUpdates != debugLayersUpdates)
|| (prevDebugOverdraw != debugOverdraw)
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 26d8bf754ddb..171873de1f9a 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -18,8 +18,6 @@
#define ANDROID_HWUI_PROPERTIES_H
#include <cutils/properties.h>
-#include <stdlib.h>
-#include <utils/Singleton.h>
/**
* This file contains the list of system properties used to configure
@@ -33,12 +31,6 @@ namespace uirenderer {
// Compile-time properties
///////////////////////////////////////////////////////////////////////////////
-// If turned on, text is interpreted as glyphs instead of UTF-16
-#define RENDER_TEXT_AS_GLYPHS 1
-
-// Indicates whether to remove the biggest layers first, or the smaller ones
-#define LAYER_REMOVE_BIGGEST_FIRST 0
-
// Textures used by layers must have dimensions multiples of this number
#define LAYER_SIZE 64
@@ -87,12 +79,6 @@ enum DebugLevel {
#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.overdraw"
/**
- * Used to enable/disable PerfHUD ES profiling. The accepted values
- * are "true" and "false". The default value is "false".
- */
-#define PROPERTY_DEBUG_NV_PROFILING "debug.hwui.nv_profiling"
-
-/**
* System property used to enable or disable hardware rendering profiling.
* The default value of this property is assumed to be false.
*
@@ -151,13 +137,21 @@ enum DebugLevel {
#define PROPERTY_SKIP_EMPTY_DAMAGE "debug.hwui.skip_empty_damage"
/**
- * Setting this property will enable usage of EGL_KHR_swap_buffers_with_damage
- * See: https://www.khronos.org/registry/egl/extensions/KHR/EGL_KHR_swap_buffers_with_damage.txt
- * Default is "false" temporarily
- * TODO: Change to "true", make sure to remove the log in EglManager::swapBuffers
- * before changing this to default to true!
+ * Controls whether or not HWUI will use the EGL_EXT_buffer_age extension
+ * to do partial invalidates. Setting this to "false" will fall back to
+ * using BUFFER_PRESERVED instead
+ * Default is "true"
*/
-#define PROPERTY_SWAP_WITH_DAMAGE "debug.hwui.swap_with_damage"
+#define PROPERTY_USE_BUFFER_AGE "debug.hwui.use_buffer_age"
+
+/**
+ * Setting this to "false" will force HWUI to always do full-redraws of the surface.
+ * This will disable the use of EGL_EXT_buffer_age and BUFFER_PRESERVED.
+ * Default is "true"
+ */
+#define PROPERTY_ENABLE_PARTIAL_UPDATES "debug.hwui.enable_partial_updates"
+
+#define PROPERTY_FILTER_TEST_OVERHEAD "debug.hwui.filter_test_overhead"
///////////////////////////////////////////////////////////////////////////////
// Runtime configuration properties
@@ -204,30 +198,8 @@ enum DebugLevel {
#define PROPERTY_TEXT_LARGE_CACHE_WIDTH "ro.hwui.text_large_cache_width"
#define PROPERTY_TEXT_LARGE_CACHE_HEIGHT "ro.hwui.text_large_cache_height"
-// Indicates whether gamma correction should be applied in the shaders
-// or in lookup tables. Accepted values:
-//
-// - "lookup3", correction based on lookup tables. Gamma correction
-// is different for black and white text (see thresholds below)
-//
-// - "lookup", correction based on a single lookup table
-//
-// - "shader3", correction applied by a GLSL shader. Gamma correction
-// is different for black and white text (see thresholds below)
-//
-// - "shader", correction applied by a GLSL shader
-//
-// See PROPERTY_TEXT_GAMMA, PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD and
-// PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD for more control.
-#define PROPERTY_TEXT_GAMMA_METHOD "hwui.text_gamma_correction"
-#define DEFAULT_TEXT_GAMMA_METHOD "lookup"
-
// Gamma (>= 1.0, <= 10.0)
#define PROPERTY_TEXT_GAMMA "hwui.text_gamma"
-// Luminance threshold below which black gamma correction is applied. Range: [0..255]
-#define PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD "hwui.text_gamma.black_threshold"
-// Lumincance threshold above which white gamma correction is applied. Range: [0..255]
-#define PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD "hwui.text_gamma.white_threshold"
///////////////////////////////////////////////////////////////////////////////
// Default property values
@@ -238,7 +210,7 @@ enum DebugLevel {
#define DEFAULT_RENDER_BUFFER_CACHE_SIZE 2.0f
#define DEFAULT_PATH_CACHE_SIZE 4.0f
#define DEFAULT_VERTEX_CACHE_SIZE 1.0f
-#define DEFAULT_PATCH_CACHE_SIZE 128 // in kB
+#define DEFAULT_PATCH_CACHE_SIZE 128.0f // in kB
#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
#define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f
#define DEFAULT_FBO_CACHE_SIZE 0
@@ -246,8 +218,6 @@ enum DebugLevel {
#define DEFAULT_TEXTURE_CACHE_FLUSH_RATE 0.6f
#define DEFAULT_TEXT_GAMMA 1.4f
-#define DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD 64
-#define DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD 192
///////////////////////////////////////////////////////////////////////////////
// Misc
@@ -291,8 +261,21 @@ public:
static bool showDirtyRegions;
// TODO: Remove after stabilization period
static bool skipEmptyFrames;
- // TODO: Remove after stabilization period
- static bool swapBuffersWithDamage;
+ static bool useBufferAge;
+ static bool enablePartialUpdates;
+
+ static float textGamma;
+
+ static int fboCacheSize;
+ static int gradientCacheSize;
+ static int layerPoolSize;
+ static int patchCacheSize;
+ static int pathCacheSize;
+ static int renderBufferCacheSize;
+ static int tessellationCacheSize;
+ static int textDropShadowCacheSize;
+ static int textureCacheSize;
+ static float textureCacheFlushRate;
static DebugLevel debugLevel;
static OverdrawColorSet overdrawColorSet;
@@ -310,6 +293,13 @@ public:
static ProfileType getProfileType();
+ // Should be used only by test apps
+ static bool waitForGpuCompletion;
+
+ // Should only be set by automated tests to try and filter out
+ // any overhead they add
+ static bool filterOutTestOverhead;
+
private:
static ProfileType sProfileType;
static bool sDisableProfileBars;
diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp
new file mode 100644
index 000000000000..b29f91ff34aa
--- /dev/null
+++ b/libs/hwui/PropertyValuesAnimatorSet.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2016 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 "PropertyValuesAnimatorSet.h"
+#include "RenderNode.h"
+
+#include <algorithm>
+
+namespace android {
+namespace uirenderer {
+
+void PropertyValuesAnimatorSet::addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder,
+ Interpolator* interpolator, nsecs_t startDelay,
+ nsecs_t duration, int repeatCount) {
+
+ PropertyAnimator* animator = new PropertyAnimator(propertyValuesHolder,
+ interpolator, startDelay, duration, repeatCount);
+ mAnimators.emplace_back(animator);
+ setListener(new PropertyAnimatorSetListener(this));
+}
+
+PropertyValuesAnimatorSet::PropertyValuesAnimatorSet()
+ : BaseRenderNodeAnimator(1.0f) {
+ setStartValue(0);
+ mLastFraction = 0.0f;
+ setInterpolator(new LinearInterpolator());
+}
+
+void PropertyValuesAnimatorSet::onFinished(BaseRenderNodeAnimator* animator) {
+ if (mOneShotListener.get()) {
+ mOneShotListener->onAnimationFinished(animator);
+ mOneShotListener = nullptr;
+ }
+}
+
+float PropertyValuesAnimatorSet::getValue(RenderNode* target) const {
+ return mLastFraction;
+}
+
+void PropertyValuesAnimatorSet::setValue(RenderNode* target, float value) {
+ mLastFraction = value;
+}
+
+void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) {
+ if (playTime == 0 && mDuration > 0) {
+ // Reset all the animators
+ for (auto it = mAnimators.rbegin(); it != mAnimators.rend(); it++) {
+ // Note that this set may containing animators modifying the same property, so when we
+ // reset the animators, we need to make sure the animators that end the first will
+ // have the final say on what the property value should be.
+ (*it)->setFraction(0);
+ }
+ } else if (playTime >= mDuration) {
+ // Skip all the animators to end
+ for (auto& anim : mAnimators) {
+ anim->setFraction(1);
+ }
+ } else {
+ for (auto& anim : mAnimators) {
+ anim->setCurrentPlayTime(playTime);
+ }
+ }
+}
+
+void PropertyValuesAnimatorSet::start(AnimationListener* listener) {
+ init();
+ mOneShotListener = listener;
+ BaseRenderNodeAnimator::start();
+}
+
+void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) {
+ init();
+ mOneShotListener = listener;
+ BaseRenderNodeAnimator::reverse();
+}
+
+void PropertyValuesAnimatorSet::init() {
+ if (mInitialized) {
+ return;
+ }
+
+ // Sort the animators by their total duration. Note that all the animators in the set start at
+ // the same time, so the ones with longer total duration (which includes start delay) will
+ // be the ones that end later.
+ std::sort(mAnimators.begin(), mAnimators.end(), [](auto& a, auto&b) {
+ return a->getTotalDuration() < b->getTotalDuration();
+ });
+ mDuration = mAnimators[mAnimators.size() - 1]->getTotalDuration();
+ mInitialized = true;
+}
+
+uint32_t PropertyValuesAnimatorSet::dirtyMask() {
+ return RenderNode::DISPLAY_LIST;
+}
+
+PropertyAnimator::PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator,
+ nsecs_t startDelay, nsecs_t duration, int repeatCount)
+ : mPropertyValuesHolder(holder), mInterpolator(interpolator), mStartDelay(startDelay),
+ mDuration(duration) {
+ if (repeatCount < 0) {
+ mRepeatCount = UINT32_MAX;
+ } else {
+ mRepeatCount = repeatCount;
+ }
+ mTotalDuration = ((nsecs_t) mRepeatCount + 1) * mDuration + mStartDelay;
+}
+
+void PropertyAnimator::setCurrentPlayTime(nsecs_t playTime) {
+ if (playTime >= mStartDelay && playTime < mTotalDuration) {
+ nsecs_t currentIterationPlayTime = (playTime - mStartDelay) % mDuration;
+ float fraction = currentIterationPlayTime / (float) mDuration;
+ setFraction(fraction);
+ } else if (mLatestFraction < 1.0f && playTime >= mTotalDuration) {
+ // This makes sure we only set the fraction = 1 once. It is needed because there might
+ // be another animator modifying the same property after this animator finishes, we need
+ // to make sure we don't set conflicting values on the same property within one frame.
+ setFraction(1.0f);
+ }
+}
+
+void PropertyAnimator::setFraction(float fraction) {
+ mLatestFraction = fraction;
+ float interpolatedFraction = mInterpolator->interpolate(fraction);
+ mPropertyValuesHolder->setFraction(interpolatedFraction);
+}
+
+void PropertyAnimatorSetListener::onAnimationFinished(BaseRenderNodeAnimator* animator) {
+ mSet->onFinished(animator);
+}
+
+}
+}
diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h
new file mode 100644
index 000000000000..c7ae7c0e8ce1
--- /dev/null
+++ b/libs/hwui/PropertyValuesAnimatorSet.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 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 "Animator.h"
+#include "PropertyValuesHolder.h"
+#include "Interpolator.h"
+
+namespace android {
+namespace uirenderer {
+
+class PropertyAnimator {
+public:
+ PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator, nsecs_t startDelay,
+ nsecs_t duration, int repeatCount);
+ void setCurrentPlayTime(nsecs_t playTime);
+ nsecs_t getTotalDuration() {
+ return mTotalDuration;
+ }
+ void setFraction(float fraction);
+
+private:
+ std::unique_ptr<PropertyValuesHolder> mPropertyValuesHolder;
+ std::unique_ptr<Interpolator> mInterpolator;
+ nsecs_t mStartDelay;
+ nsecs_t mDuration;
+ uint32_t mRepeatCount;
+ nsecs_t mTotalDuration;
+ float mLatestFraction = 0.0f;
+};
+
+class ANDROID_API PropertyValuesAnimatorSet : public BaseRenderNodeAnimator {
+public:
+ friend class PropertyAnimatorSetListener;
+ PropertyValuesAnimatorSet();
+
+ void start(AnimationListener* listener);
+ void reverse(AnimationListener* listener);
+
+ void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder,
+ Interpolator* interpolators, int64_t startDelays,
+ nsecs_t durations, int repeatCount);
+ virtual uint32_t dirtyMask();
+
+protected:
+ virtual float getValue(RenderNode* target) const override;
+ virtual void setValue(RenderNode* target, float value) override;
+ virtual void onPlayTimeChanged(nsecs_t playTime) override;
+
+private:
+ void init();
+ void onFinished(BaseRenderNodeAnimator* animator);
+ // Listener set from outside
+ sp<AnimationListener> mOneShotListener;
+ std::vector< std::unique_ptr<PropertyAnimator> > mAnimators;
+ float mLastFraction = 0.0f;
+ bool mInitialized = false;
+};
+
+class PropertyAnimatorSetListener : public AnimationListener {
+public:
+ PropertyAnimatorSetListener(PropertyValuesAnimatorSet* set) : mSet(set) {}
+ virtual void onAnimationFinished(BaseRenderNodeAnimator* animator) override;
+
+private:
+ PropertyValuesAnimatorSet* mSet;
+};
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/PropertyValuesHolder.cpp b/libs/hwui/PropertyValuesHolder.cpp
new file mode 100644
index 000000000000..0932d653fd5e
--- /dev/null
+++ b/libs/hwui/PropertyValuesHolder.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 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 "PropertyValuesHolder.h"
+
+#include "utils/VectorDrawableUtils.h"
+
+#include <utils/Log.h>
+
+namespace android {
+namespace uirenderer {
+
+using namespace VectorDrawable;
+
+float PropertyValuesHolder::getValueFromData(float fraction) {
+ if (mDataSource.size() == 0) {
+ LOG_ALWAYS_FATAL("No data source is defined");
+ return 0;
+ }
+ if (fraction <= 0.0f) {
+ return mDataSource.front();
+ }
+ if (fraction >= 1.0f) {
+ return mDataSource.back();
+ }
+
+ fraction *= mDataSource.size() - 1;
+ int lowIndex = floor(fraction);
+ fraction -= lowIndex;
+
+ float value = mDataSource[lowIndex] * (1.0f - fraction)
+ + mDataSource[lowIndex + 1] * fraction;
+ return value;
+}
+
+void GroupPropertyValuesHolder::setFraction(float fraction) {
+ float animatedValue;
+ if (mDataSource.size() > 0) {
+ animatedValue = getValueFromData(fraction);
+ } else {
+ animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction;
+ }
+ mGroup->mutateProperties()->setPropertyValue(mPropertyId, animatedValue);
+}
+
+inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) {
+ return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction);
+}
+
+// TODO: Add a test for this
+SkColor FullPathColorPropertyValuesHolder::interpolateColors(SkColor fromColor, SkColor toColor,
+ float fraction) {
+ U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction);
+ U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction);
+ U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction);
+ U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction);
+ return SkColorSetARGB(alpha, red, green, blue);
+}
+
+void FullPathColorPropertyValuesHolder::setFraction(float fraction) {
+ SkColor animatedValue = interpolateColors(mStartValue, mEndValue, fraction);
+ mFullPath->mutateProperties()->setColorPropertyValue(mPropertyId, animatedValue);
+}
+
+void FullPathPropertyValuesHolder::setFraction(float fraction) {
+ float animatedValue;
+ if (mDataSource.size() > 0) {
+ animatedValue = getValueFromData(fraction);
+ } else {
+ animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction;
+ }
+ mFullPath->mutateProperties()->setPropertyValue(mPropertyId, animatedValue);
+}
+
+void PathDataPropertyValuesHolder::setFraction(float fraction) {
+ VectorDrawableUtils::interpolatePaths(&mPathData, mStartValue, mEndValue, fraction);
+ mPath->mutateProperties()->setData(mPathData);
+}
+
+void RootAlphaPropertyValuesHolder::setFraction(float fraction) {
+ float animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction;
+ mTree->mutateProperties()->setRootAlpha(animatedValue);
+}
+
+} // namepace uirenderer
+} // namespace android
diff --git a/libs/hwui/PropertyValuesHolder.h b/libs/hwui/PropertyValuesHolder.h
new file mode 100644
index 000000000000..b905faef104c
--- /dev/null
+++ b/libs/hwui/PropertyValuesHolder.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016 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 "VectorDrawable.h"
+
+#include <SkColor.h>
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * PropertyValues holder contains data needed to change a property of a Vector Drawable object.
+ * When a fraction in [0f, 1f] is provided, the holder will calculate an interpolated value based
+ * on its start and end value, and set the new value on the VectorDrawble's corresponding property.
+ */
+class ANDROID_API PropertyValuesHolder {
+public:
+ virtual void setFraction(float fraction) = 0;
+ void setPropertyDataSource(float* dataSource, int length) {
+ mDataSource.insert(mDataSource.begin(), dataSource, dataSource + length);
+ }
+ float getValueFromData(float fraction);
+ virtual ~PropertyValuesHolder() {}
+protected:
+ std::vector<float> mDataSource;
+};
+
+class ANDROID_API GroupPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ GroupPropertyValuesHolder(VectorDrawable::Group* ptr, int propertyId, float startValue,
+ float endValue)
+ : mGroup(ptr)
+ , mPropertyId(propertyId)
+ , mStartValue(startValue)
+ , mEndValue(endValue){
+ }
+ void setFraction(float fraction) override;
+private:
+ VectorDrawable::Group* mGroup;
+ int mPropertyId;
+ float mStartValue;
+ float mEndValue;
+};
+
+class ANDROID_API FullPathColorPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ FullPathColorPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, int32_t startValue,
+ int32_t endValue)
+ : mFullPath(ptr)
+ , mPropertyId(propertyId)
+ , mStartValue(startValue)
+ , mEndValue(endValue) {};
+ void setFraction(float fraction) override;
+ static SkColor interpolateColors(SkColor fromColor, SkColor toColor, float fraction);
+private:
+ VectorDrawable::FullPath* mFullPath;
+ int mPropertyId;
+ int32_t mStartValue;
+ int32_t mEndValue;
+};
+
+class ANDROID_API FullPathPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ FullPathPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, float startValue,
+ float endValue)
+ : mFullPath(ptr)
+ , mPropertyId(propertyId)
+ , mStartValue(startValue)
+ , mEndValue(endValue) {};
+ void setFraction(float fraction) override;
+private:
+ VectorDrawable::FullPath* mFullPath;
+ int mPropertyId;
+ float mStartValue;
+ float mEndValue;
+};
+
+class ANDROID_API PathDataPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ PathDataPropertyValuesHolder(VectorDrawable::Path* ptr, PathData* startValue,
+ PathData* endValue)
+ : mPath(ptr)
+ , mStartValue(*startValue)
+ , mEndValue(*endValue) {};
+ void setFraction(float fraction) override;
+private:
+ VectorDrawable::Path* mPath;
+ PathData mPathData;
+ PathData mStartValue;
+ PathData mEndValue;
+};
+
+class ANDROID_API RootAlphaPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ RootAlphaPropertyValuesHolder(VectorDrawable::Tree* tree, float startValue, float endValue)
+ : mTree(tree)
+ , mStartValue(startValue)
+ , mEndValue(endValue) {}
+ void setFraction(float fraction) override;
+private:
+ VectorDrawable::Tree* mTree;
+ float mStartValue;
+ float mEndValue;
+};
+}
+}
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
new file mode 100644
index 000000000000..55f823dfe226
--- /dev/null
+++ b/libs/hwui/Readback.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2016 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 "Readback.h"
+
+#include "Caches.h"
+#include "Image.h"
+#include "GlopBuilder.h"
+#include "renderstate/RenderState.h"
+#include "renderthread/EglManager.h"
+#include "utils/GLUtils.h"
+
+#include <GLES2/gl2.h>
+#include <ui/Fence.h>
+#include <ui/GraphicBuffer.h>
+
+namespace android {
+namespace uirenderer {
+
+CopyResult Readback::copySurfaceInto(renderthread::RenderThread& renderThread,
+ Surface& surface, SkBitmap* bitmap) {
+ // TODO: Clean this up and unify it with LayerRenderer::copyLayer,
+ // of which most of this is copied from.
+ renderThread.eglManager().initialize();
+
+ Caches& caches = Caches::getInstance();
+ RenderState& renderState = renderThread.renderState();
+ int destWidth = bitmap->width();
+ int destHeight = bitmap->height();
+ if (destWidth > caches.maxTextureSize
+ || destHeight > caches.maxTextureSize) {
+ ALOGW("Can't copy surface into bitmap, %dx%d exceeds max texture size %d",
+ destWidth, destHeight, caches.maxTextureSize);
+ return CopyResult::DestinationInvalid;
+ }
+ GLuint fbo = renderState.createFramebuffer();
+ if (!fbo) {
+ ALOGW("Could not obtain an FBO");
+ return CopyResult::UnknownError;
+ }
+
+ SkAutoLockPixels alp(*bitmap);
+
+ GLuint texture;
+
+ GLenum format;
+ GLenum type;
+
+ switch (bitmap->colorType()) {
+ case kAlpha_8_SkColorType:
+ format = GL_ALPHA;
+ type = GL_UNSIGNED_BYTE;
+ break;
+ case kRGB_565_SkColorType:
+ format = GL_RGB;
+ type = GL_UNSIGNED_SHORT_5_6_5;
+ break;
+ case kARGB_4444_SkColorType:
+ format = GL_RGBA;
+ type = GL_UNSIGNED_SHORT_4_4_4_4;
+ break;
+ case kN32_SkColorType:
+ default:
+ format = GL_RGBA;
+ type = GL_UNSIGNED_BYTE;
+ break;
+ }
+
+ renderState.bindFramebuffer(fbo);
+
+ // TODO: Use layerPool or something to get this maybe? But since we
+ // need explicit format control we can't currently.
+
+ // Setup the rendertarget
+ glGenTextures(1, &texture);
+ caches.textureState().activateTexture(0);
+ caches.textureState().bindTexture(texture);
+ glPixelStorei(GL_PACK_ALIGNMENT, bitmap->bytesPerPixel());
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, format, destWidth, destHeight,
+ 0, format, type, nullptr);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, texture, 0);
+
+ // Setup the source
+ sp<GraphicBuffer> sourceBuffer;
+ sp<Fence> sourceFence;
+ Matrix4 texTransform;
+ status_t err = surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence,
+ texTransform.data);
+ texTransform.invalidateType();
+ if (err != NO_ERROR) {
+ ALOGW("Failed to get last queued buffer, error = %d", err);
+ return CopyResult::UnknownError;
+ }
+ if (!sourceBuffer.get()) {
+ ALOGW("Surface doesn't have any previously queued frames, nothing to readback from");
+ return CopyResult::SourceEmpty;
+ }
+ if (sourceBuffer->getUsage() & GRALLOC_USAGE_PROTECTED) {
+ ALOGW("Surface is protected, unable to copy from it");
+ return CopyResult::SourceInvalid;
+ }
+ err = sourceFence->wait(500 /* ms */);
+ if (err != NO_ERROR) {
+ ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
+ return CopyResult::Timeout;
+ }
+
+ // TODO: Can't use Image helper since it forces GL_TEXTURE_2D usage via
+ // GL_OES_EGL_image, which doesn't work since we need samplerExternalOES
+ // to be able to properly sample from the buffer.
+
+ // Create the EGLImage object that maps the GraphicBuffer
+ EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ EGLClientBuffer clientBuffer = (EGLClientBuffer) sourceBuffer->getNativeBuffer();
+ EGLint attrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
+
+ EGLImageKHR sourceImage = eglCreateImageKHR(display, EGL_NO_CONTEXT,
+ EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs);
+
+ if (sourceImage == EGL_NO_IMAGE_KHR) {
+ ALOGW("Error creating image (%#x)", eglGetError());
+ return CopyResult::UnknownError;
+ }
+ GLuint sourceTexId;
+ // Create a 2D texture to sample from the EGLImage
+ glGenTextures(1, &sourceTexId);
+ Caches::getInstance().textureState().bindTexture(GL_TEXTURE_EXTERNAL_OES, sourceTexId);
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, sourceImage);
+
+ GLenum status = GL_NO_ERROR;
+ while ((status = glGetError()) != GL_NO_ERROR) {
+ ALOGW("Error creating image (%#x)", status);
+ return CopyResult::UnknownError;
+ }
+
+ Texture sourceTexture(caches);
+ sourceTexture.wrap(sourceTexId,
+ sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0 /* total lie */);
+
+ {
+ // Draw & readback
+ renderState.setViewport(destWidth, destHeight);
+ renderState.scissor().setEnabled(false);
+ renderState.blend().syncEnabled();
+ renderState.stencil().disable();
+
+ Rect destRect(destWidth, destHeight);
+ Glop glop;
+ GlopBuilder(renderState, caches, &glop)
+ .setRoundRectClipState(nullptr)
+ .setMeshTexturedUnitQuad(nullptr)
+ .setFillExternalTexture(sourceTexture, texTransform)
+ .setTransform(Matrix4::identity(), TransformFlags::None)
+ .setModelViewMapUnitToRect(destRect)
+ .build();
+ Matrix4 ortho;
+ ortho.loadOrtho(destWidth, destHeight);
+ renderState.render(glop, ortho);
+
+ glReadPixels(0, 0, bitmap->width(), bitmap->height(), format,
+ type, bitmap->getPixels());
+ }
+
+ // Cleanup
+ caches.textureState().deleteTexture(texture);
+ renderState.deleteFramebuffer(fbo);
+
+ GL_CHECKPOINT(MODERATE);
+
+ return CopyResult::Success;
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h
new file mode 100644
index 000000000000..a112c42988c0
--- /dev/null
+++ b/libs/hwui/Readback.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 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 "renderthread/RenderThread.h"
+
+#include <SkBitmap.h>
+#include <gui/Surface.h>
+
+namespace android {
+namespace uirenderer {
+
+// Keep in sync with PixelCopy.java codes
+enum class CopyResult {
+ Success = 0,
+ UnknownError = 1,
+ Timeout = 2,
+ SourceEmpty = 3,
+ SourceInvalid = 4,
+ DestinationInvalid = 5,
+};
+
+class Readback {
+public:
+ static CopyResult copySurfaceInto(renderthread::RenderThread& renderThread,
+ Surface& surface, SkBitmap* bitmap);
+};
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
new file mode 100644
index 000000000000..aee9d6370083
--- /dev/null
+++ b/libs/hwui/RecordedOp.h
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2015 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_RECORDED_OP_H
+#define ANDROID_HWUI_RECORDED_OP_H
+
+#include "RecordedOp.h"
+#include "font/FontUtil.h"
+#include "Matrix.h"
+#include "Rect.h"
+#include "RenderNode.h"
+#include "TessellationCache.h"
+#include "utils/LinearAllocator.h"
+#include "Vector.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <SkXfermode.h>
+
+class SkBitmap;
+class SkPaint;
+
+namespace android {
+namespace uirenderer {
+
+struct ClipBase;
+class OffscreenBuffer;
+class RenderNode;
+struct Vertex;
+
+namespace VectorDrawable {
+class Tree;
+}
+
+/**
+ * Authoritative op list, used for generating the op ID enum, ID based LUTS, and
+ * the functions to which they dispatch. Parameter macros are executed for each op,
+ * in order, based on the op's type.
+ *
+ * There are 4 types of op, which defines dispatch/LUT capability:
+ *
+ * | DisplayList | Render | Merge |
+ * -------------|-------------|-------------|-------------|
+ * PRE RENDER | Yes | | |
+ * RENDER ONLY | | Yes | |
+ * UNMERGEABLE | Yes | Yes | |
+ * MERGEABLE | Yes | Yes | Yes |
+ *
+ * PRE RENDER - These ops are recorded into DisplayLists, but can't be directly rendered. This
+ * may be because they need to be transformed into other op types (e.g. CirclePropsOp),
+ * be traversed to access multiple renderable ops within (e.g. RenderNodeOp), or because they
+ * modify renderbuffer lifecycle, instead of directly rendering content (the various LayerOps).
+ *
+ * RENDER ONLY - These ops cannot be recorded into DisplayLists, and are instead implicitly
+ * constructed from other commands/RenderNode properties. They cannot be merged.
+ *
+ * UNMERGEABLE - These ops can be recorded into DisplayLists and rendered directly, but do not
+ * support merged rendering.
+ *
+ * MERGEABLE - These ops can be recorded into DisplayLists and rendered individually, or merged
+ * under certain circumstances.
+ */
+#define MAP_OPS_BASED_ON_TYPE(PRE_RENDER_OP_FN, RENDER_ONLY_OP_FN, UNMERGEABLE_OP_FN, MERGEABLE_OP_FN) \
+ PRE_RENDER_OP_FN(RenderNodeOp) \
+ PRE_RENDER_OP_FN(CirclePropsOp) \
+ PRE_RENDER_OP_FN(RoundRectPropsOp) \
+ PRE_RENDER_OP_FN(BeginLayerOp) \
+ PRE_RENDER_OP_FN(EndLayerOp) \
+ PRE_RENDER_OP_FN(BeginUnclippedLayerOp) \
+ PRE_RENDER_OP_FN(EndUnclippedLayerOp) \
+ PRE_RENDER_OP_FN(VectorDrawableOp) \
+ \
+ RENDER_ONLY_OP_FN(ShadowOp) \
+ RENDER_ONLY_OP_FN(LayerOp) \
+ RENDER_ONLY_OP_FN(CopyToLayerOp) \
+ RENDER_ONLY_OP_FN(CopyFromLayerOp) \
+ \
+ UNMERGEABLE_OP_FN(ArcOp) \
+ UNMERGEABLE_OP_FN(BitmapMeshOp) \
+ UNMERGEABLE_OP_FN(BitmapRectOp) \
+ UNMERGEABLE_OP_FN(ColorOp) \
+ UNMERGEABLE_OP_FN(FunctorOp) \
+ UNMERGEABLE_OP_FN(LinesOp) \
+ UNMERGEABLE_OP_FN(OvalOp) \
+ UNMERGEABLE_OP_FN(PathOp) \
+ UNMERGEABLE_OP_FN(PointsOp) \
+ UNMERGEABLE_OP_FN(RectOp) \
+ UNMERGEABLE_OP_FN(RoundRectOp) \
+ UNMERGEABLE_OP_FN(SimpleRectsOp) \
+ UNMERGEABLE_OP_FN(TextOnPathOp) \
+ UNMERGEABLE_OP_FN(TextureLayerOp) \
+ \
+ MERGEABLE_OP_FN(BitmapOp) \
+ MERGEABLE_OP_FN(PatchOp) \
+ MERGEABLE_OP_FN(TextOp)
+
+/**
+ * LUT generators, which will insert nullptr for unsupported ops
+ */
+#define NULLPTR_OP_FN(Type) nullptr,
+
+#define BUILD_DEFERRABLE_OP_LUT(OP_FN) \
+ { MAP_OPS_BASED_ON_TYPE(OP_FN, NULLPTR_OP_FN, OP_FN, OP_FN) }
+
+#define BUILD_MERGEABLE_OP_LUT(OP_FN) \
+ { MAP_OPS_BASED_ON_TYPE(NULLPTR_OP_FN, NULLPTR_OP_FN, NULLPTR_OP_FN, OP_FN) }
+
+#define BUILD_RENDERABLE_OP_LUT(OP_FN) \
+ { MAP_OPS_BASED_ON_TYPE(NULLPTR_OP_FN, OP_FN, OP_FN, OP_FN) }
+
+#define BUILD_FULL_OP_LUT(OP_FN) \
+ { MAP_OPS_BASED_ON_TYPE(OP_FN, OP_FN, OP_FN, OP_FN) }
+
+/**
+ * Op mapping functions, which skip unsupported ops.
+ *
+ * Note: Do not use for LUTS, since these do not preserve ID order.
+ */
+#define NULL_OP_FN(Type)
+
+#define MAP_DEFERRABLE_OPS(OP_FN) \
+ MAP_OPS_BASED_ON_TYPE(OP_FN, NULL_OP_FN, OP_FN, OP_FN)
+
+#define MAP_MERGEABLE_OPS(OP_FN) \
+ MAP_OPS_BASED_ON_TYPE(NULL_OP_FN, NULL_OP_FN, NULL_OP_FN, OP_FN)
+
+#define MAP_RENDERABLE_OPS(OP_FN) \
+ MAP_OPS_BASED_ON_TYPE(NULL_OP_FN, OP_FN, OP_FN, OP_FN)
+
+// Generate OpId enum
+#define IDENTITY_FN(Type) Type,
+namespace RecordedOpId {
+ enum {
+ MAP_OPS_BASED_ON_TYPE(IDENTITY_FN, IDENTITY_FN, IDENTITY_FN, IDENTITY_FN)
+ Count,
+ };
+}
+static_assert(RecordedOpId::RenderNodeOp == 0,
+ "First index must be zero for LUTs to work");
+
+#define BASE_PARAMS const Rect& unmappedBounds, const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint
+#define BASE_PARAMS_PAINTLESS const Rect& unmappedBounds, const Matrix4& localMatrix, const ClipBase* localClip
+#define SUPER(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClip, paint)
+#define SUPER_PAINTLESS(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClip, nullptr)
+
+struct RecordedOp {
+ /* ID from RecordedOpId - generally used for jumping into function tables */
+ const int opId;
+
+ /* bounds in *local* space, without accounting for DisplayList transformation, or stroke */
+ const Rect unmappedBounds;
+
+ /* transform in recording space (vs DisplayList origin) */
+ const Matrix4 localMatrix;
+
+ /* clip in recording space - nullptr if not clipped */
+ const ClipBase* localClip;
+
+ /* optional paint, stored in base object to simplify merging logic */
+ const SkPaint* paint;
+protected:
+ RecordedOp(unsigned int opId, BASE_PARAMS)
+ : opId(opId)
+ , unmappedBounds(unmappedBounds)
+ , localMatrix(localMatrix)
+ , localClip(localClip)
+ , paint(paint) {}
+};
+
+struct RenderNodeOp : RecordedOp {
+ RenderNodeOp(BASE_PARAMS_PAINTLESS, RenderNode* renderNode)
+ : SUPER_PAINTLESS(RenderNodeOp)
+ , renderNode(renderNode) {}
+ RenderNode * renderNode; // not const, since drawing modifies it
+
+ /**
+ * Holds the transformation between the projection surface ViewGroup and this RenderNode
+ * drawing instance. Represents any translations / transformations done within the drawing of
+ * the compositing ancestor ViewGroup's draw, before the draw of the View represented by this
+ * DisplayList draw instance.
+ *
+ * Note: doesn't include transformation within the RenderNode, or its properties.
+ */
+ Matrix4 transformFromCompositingAncestor;
+ bool skipInOrderDraw = false;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Standard Ops
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+struct ArcOp : RecordedOp {
+ ArcOp(BASE_PARAMS, float startAngle, float sweepAngle, bool useCenter)
+ : SUPER(ArcOp)
+ , startAngle(startAngle)
+ , sweepAngle(sweepAngle)
+ , useCenter(useCenter) {}
+ const float startAngle;
+ const float sweepAngle;
+ const bool useCenter;
+};
+
+struct BitmapOp : RecordedOp {
+ BitmapOp(BASE_PARAMS, const SkBitmap* bitmap)
+ : SUPER(BitmapOp)
+ , bitmap(bitmap) {}
+ const SkBitmap* bitmap;
+ // TODO: asset atlas/texture id lookup?
+};
+
+struct BitmapMeshOp : RecordedOp {
+ BitmapMeshOp(BASE_PARAMS, const SkBitmap* bitmap, int meshWidth, int meshHeight,
+ const float* vertices, const int* colors)
+ : SUPER(BitmapMeshOp)
+ , bitmap(bitmap)
+ , meshWidth(meshWidth)
+ , meshHeight(meshHeight)
+ , vertices(vertices)
+ , colors(colors) {}
+ const SkBitmap* bitmap;
+ const int meshWidth;
+ const int meshHeight;
+ const float* vertices;
+ const int* colors;
+};
+
+struct BitmapRectOp : RecordedOp {
+ BitmapRectOp(BASE_PARAMS, const SkBitmap* bitmap, const Rect& src)
+ : SUPER(BitmapRectOp)
+ , bitmap(bitmap)
+ , src(src) {}
+ const SkBitmap* bitmap;
+ const Rect src;
+};
+
+struct CirclePropsOp : RecordedOp {
+ CirclePropsOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint,
+ float* x, float* y, float* radius)
+ : RecordedOp(RecordedOpId::CirclePropsOp, Rect(), localMatrix, localClip, paint)
+ , x(x)
+ , y(y)
+ , radius(radius) {}
+ const float* x;
+ const float* y;
+ const float* radius;
+};
+
+struct ColorOp : RecordedOp {
+ // Note: unbounded op that will fillclip, so no bounds/matrix needed
+ ColorOp(const ClipBase* localClip, int color, SkXfermode::Mode mode)
+ : RecordedOp(RecordedOpId::ColorOp, Rect(), Matrix4::identity(), localClip, nullptr)
+ , color(color)
+ , mode(mode) {}
+ const int color;
+ const SkXfermode::Mode mode;
+};
+
+struct FunctorOp : RecordedOp {
+ // Note: undefined record-time bounds, since this op fills the clip
+ // TODO: explicitly define bounds
+ FunctorOp(const Matrix4& localMatrix, const ClipBase* localClip, Functor* functor)
+ : RecordedOp(RecordedOpId::FunctorOp, Rect(), localMatrix, localClip, nullptr)
+ , functor(functor) {}
+ Functor* functor;
+};
+
+struct LinesOp : RecordedOp {
+ LinesOp(BASE_PARAMS, const float* points, const int floatCount)
+ : SUPER(LinesOp)
+ , points(points)
+ , floatCount(floatCount) {}
+ const float* points;
+ const int floatCount;
+};
+
+struct OvalOp : RecordedOp {
+ OvalOp(BASE_PARAMS)
+ : SUPER(OvalOp) {}
+};
+
+struct PatchOp : RecordedOp {
+ PatchOp(BASE_PARAMS, const SkBitmap* bitmap, const Res_png_9patch* patch)
+ : SUPER(PatchOp)
+ , bitmap(bitmap)
+ , patch(patch) {}
+ const SkBitmap* bitmap;
+ const Res_png_9patch* patch;
+};
+
+struct PathOp : RecordedOp {
+ PathOp(BASE_PARAMS, const SkPath* path)
+ : SUPER(PathOp)
+ , path(path) {}
+ const SkPath* path;
+};
+
+struct PointsOp : RecordedOp {
+ PointsOp(BASE_PARAMS, const float* points, const int floatCount)
+ : SUPER(PointsOp)
+ , points(points)
+ , floatCount(floatCount) {}
+ const float* points;
+ const int floatCount;
+};
+
+struct RectOp : RecordedOp {
+ RectOp(BASE_PARAMS)
+ : SUPER(RectOp) {}
+};
+
+struct RoundRectOp : RecordedOp {
+ RoundRectOp(BASE_PARAMS, float rx, float ry)
+ : SUPER(RoundRectOp)
+ , rx(rx)
+ , ry(ry) {}
+ const float rx;
+ const float ry;
+};
+
+struct RoundRectPropsOp : RecordedOp {
+ RoundRectPropsOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint,
+ float* left, float* top, float* right, float* bottom, float *rx, float *ry)
+ : RecordedOp(RecordedOpId::RoundRectPropsOp, Rect(), localMatrix, localClip, paint)
+ , left(left)
+ , top(top)
+ , right(right)
+ , bottom(bottom)
+ , rx(rx)
+ , ry(ry) {}
+ const float* left;
+ const float* top;
+ const float* right;
+ const float* bottom;
+ const float* rx;
+ const float* ry;
+};
+
+struct VectorDrawableOp : RecordedOp {
+ VectorDrawableOp(VectorDrawable::Tree* tree, BASE_PARAMS_PAINTLESS)
+ : SUPER_PAINTLESS(VectorDrawableOp)
+ , vectorDrawable(tree) {}
+ VectorDrawable::Tree* vectorDrawable;
+};
+
+/**
+ * Real-time, dynamic-lit shadow.
+ *
+ * Uses invalid/empty bounds and matrix since ShadowOp bounds aren't known at defer time,
+ * and are resolved dynamically, and transform isn't needed.
+ *
+ * State construction handles these properties specially, ignoring matrix/bounds.
+ */
+struct ShadowOp : RecordedOp {
+ ShadowOp(sp<TessellationCache::ShadowTask>& shadowTask, float casterAlpha)
+ : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), nullptr, nullptr)
+ , shadowTask(shadowTask)
+ , casterAlpha(casterAlpha) {
+ };
+ sp<TessellationCache::ShadowTask> shadowTask;
+ const float casterAlpha;
+};
+
+struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?)
+ SimpleRectsOp(BASE_PARAMS, Vertex* vertices, size_t vertexCount)
+ : SUPER(SimpleRectsOp)
+ , vertices(vertices)
+ , vertexCount(vertexCount) {}
+ Vertex* vertices;
+ const size_t vertexCount;
+};
+
+struct TextOp : RecordedOp {
+ TextOp(BASE_PARAMS, const glyph_t* glyphs, const float* positions, int glyphCount,
+ float x, float y)
+ : SUPER(TextOp)
+ , glyphs(glyphs)
+ , positions(positions)
+ , glyphCount(glyphCount)
+ , x(x)
+ , y(y) {}
+ const glyph_t* glyphs;
+ const float* positions;
+ const int glyphCount;
+ const float x;
+ const float y;
+};
+
+struct TextOnPathOp : RecordedOp {
+ // TODO: explicitly define bounds
+ TextOnPathOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint,
+ const glyph_t* glyphs, int glyphCount, const SkPath* path, float hOffset, float vOffset)
+ : RecordedOp(RecordedOpId::TextOnPathOp, Rect(), localMatrix, localClip, paint)
+ , glyphs(glyphs)
+ , glyphCount(glyphCount)
+ , path(path)
+ , hOffset(hOffset)
+ , vOffset(vOffset) {}
+ const glyph_t* glyphs;
+ const int glyphCount;
+
+ const SkPath* path;
+ const float hOffset;
+ const float vOffset;
+};
+
+struct TextureLayerOp : RecordedOp {
+ TextureLayerOp(BASE_PARAMS_PAINTLESS, Layer* layer)
+ : SUPER_PAINTLESS(TextureLayerOp)
+ , layer(layer) {}
+
+ // Copy an existing TextureLayerOp, replacing the underlying matrix
+ TextureLayerOp(const TextureLayerOp& op, const Matrix4& replacementMatrix)
+ : RecordedOp(RecordedOpId::TextureLayerOp, op.unmappedBounds, replacementMatrix,
+ op.localClip, op.paint)
+ , layer(op.layer) {
+
+ }
+ Layer* layer;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Layers
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Stateful operation! denotes the creation of an off-screen layer,
+ * and that commands following will render into it.
+ */
+struct BeginLayerOp : RecordedOp {
+ BeginLayerOp(BASE_PARAMS)
+ : SUPER(BeginLayerOp) {}
+};
+
+/**
+ * Stateful operation! Denotes end of off-screen layer, and that
+ * commands since last BeginLayerOp should be drawn into parent FBO.
+ *
+ * State in this op is empty, it just serves to signal that a layer has been finished.
+ */
+struct EndLayerOp : RecordedOp {
+ EndLayerOp()
+ : RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {}
+};
+
+struct BeginUnclippedLayerOp : RecordedOp {
+ BeginUnclippedLayerOp(BASE_PARAMS)
+ : SUPER(BeginUnclippedLayerOp) {}
+};
+
+struct EndUnclippedLayerOp : RecordedOp {
+ EndUnclippedLayerOp()
+ : RecordedOp(RecordedOpId::EndUnclippedLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {}
+};
+
+struct CopyToLayerOp : RecordedOp {
+ CopyToLayerOp(const RecordedOp& op, OffscreenBuffer** layerHandle)
+ : RecordedOp(RecordedOpId::CopyToLayerOp,
+ op.unmappedBounds,
+ op.localMatrix,
+ nullptr, // clip intentionally ignored
+ op.paint)
+ , layerHandle(layerHandle) {}
+
+ // Records a handle to the Layer object, since the Layer itself won't be
+ // constructed until after this operation is constructed.
+ OffscreenBuffer** layerHandle;
+};
+
+
+// draw the parameter layer underneath
+struct CopyFromLayerOp : RecordedOp {
+ CopyFromLayerOp(const RecordedOp& op, OffscreenBuffer** layerHandle)
+ : RecordedOp(RecordedOpId::CopyFromLayerOp,
+ op.unmappedBounds,
+ op.localMatrix,
+ nullptr, // clip intentionally ignored
+ op.paint)
+ , layerHandle(layerHandle) {}
+
+ // Records a handle to the Layer object, since the Layer itself won't be
+ // constructed until after this operation is constructed.
+ OffscreenBuffer** layerHandle;
+};
+
+/**
+ * Draws an OffscreenBuffer.
+ *
+ * Alpha, mode, and colorfilter are embedded, since LayerOps are always dynamically generated,
+ * when creating/tracking a SkPaint* during defer isn't worth the bother.
+ */
+struct LayerOp : RecordedOp {
+ // Records a one-use (saveLayer) layer for drawing.
+ LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle)
+ : SUPER_PAINTLESS(LayerOp)
+ , layerHandle(layerHandle)
+ , alpha(paint ? paint->getAlpha() / 255.0f : 1.0f)
+ , mode(PaintUtils::getXfermodeDirect(paint))
+ , colorFilter(paint ? paint->getColorFilter() : nullptr) {}
+
+ LayerOp(RenderNode& node)
+ : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), nullptr, nullptr)
+ , layerHandle(node.getLayerHandle())
+ , alpha(node.properties().layerProperties().alpha() / 255.0f)
+ , mode(node.properties().layerProperties().xferMode())
+ , colorFilter(node.properties().layerProperties().colorFilter()) {}
+
+ // Records a handle to the Layer object, since the Layer itself won't be
+ // constructed until after this operation is constructed.
+ OffscreenBuffer** layerHandle;
+ const float alpha;
+ const SkXfermode::Mode mode;
+
+ // pointer to object owned by either LayerProperties, or a recorded Paint object in a
+ // BeginLayerOp. Lives longer than LayerOp in either case, so no skia ref counting is used.
+ SkColorFilter* colorFilter;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_RECORDED_OP_H
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
new file mode 100644
index 000000000000..b49f9b529989
--- /dev/null
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -0,0 +1,657 @@
+/*
+ * Copyright (C) 2015 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 "RecordingCanvas.h"
+
+#include "DeferredLayerUpdater.h"
+#include "RecordedOp.h"
+#include "RenderNode.h"
+#include "VectorDrawable.h"
+
+namespace android {
+namespace uirenderer {
+
+RecordingCanvas::RecordingCanvas(size_t width, size_t height)
+ : mState(*this)
+ , mResourceCache(ResourceCache::getInstance()) {
+ resetRecording(width, height);
+}
+
+RecordingCanvas::~RecordingCanvas() {
+ LOG_ALWAYS_FATAL_IF(mDisplayList,
+ "Destroyed a RecordingCanvas during a record!");
+}
+
+void RecordingCanvas::resetRecording(int width, int height) {
+ LOG_ALWAYS_FATAL_IF(mDisplayList,
+ "prepareDirty called a second time during a recording!");
+ mDisplayList = new DisplayList();
+
+ mState.initializeRecordingSaveStack(width, height);
+
+ mDeferredBarrierType = DeferredBarrierType::InOrder;
+ mState.setDirtyClip(false);
+}
+
+DisplayList* RecordingCanvas::finishRecording() {
+ restoreToCount(1);
+ mPaintMap.clear();
+ mRegionMap.clear();
+ mPathMap.clear();
+ DisplayList* displayList = mDisplayList;
+ mDisplayList = nullptr;
+ mSkiaCanvasProxy.reset(nullptr);
+ return displayList;
+}
+
+void RecordingCanvas::insertReorderBarrier(bool enableReorder) {
+ if (enableReorder) {
+ mDeferredBarrierType = DeferredBarrierType::OutOfOrder;
+ mDeferredBarrierClip = getRecordedClip();
+ } else {
+ mDeferredBarrierType = DeferredBarrierType::InOrder;
+ mDeferredBarrierClip = nullptr;
+ }
+}
+
+SkCanvas* RecordingCanvas::asSkCanvas() {
+ LOG_ALWAYS_FATAL_IF(!mDisplayList,
+ "attempting to get an SkCanvas when we are not recording!");
+ if (!mSkiaCanvasProxy) {
+ mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this));
+ }
+
+ // SkCanvas instances default to identity transform, but should inherit
+ // the state of this Canvas; if this code was in the SkiaCanvasProxy
+ // constructor, we couldn't cache mSkiaCanvasProxy.
+ SkMatrix parentTransform;
+ getMatrix(&parentTransform);
+ mSkiaCanvasProxy.get()->setMatrix(parentTransform);
+
+ return mSkiaCanvasProxy.get();
+}
+
+// ----------------------------------------------------------------------------
+// CanvasStateClient implementation
+// ----------------------------------------------------------------------------
+
+void RecordingCanvas::onViewportInitialized() {
+}
+
+void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
+ if (removed.flags & Snapshot::kFlagIsFboLayer) {
+ addOp(alloc().create_trivial<EndLayerOp>());
+ } else if (removed.flags & Snapshot::kFlagIsLayer) {
+ addOp(alloc().create_trivial<EndUnclippedLayerOp>());
+ }
+}
+
+// ----------------------------------------------------------------------------
+// android/graphics/Canvas state operations
+// ----------------------------------------------------------------------------
+// Save (layer)
+int RecordingCanvas::save(SaveFlags::Flags flags) {
+ return mState.save((int) flags);
+}
+
+void RecordingCanvas::RecordingCanvas::restore() {
+ mState.restore();
+}
+
+void RecordingCanvas::restoreToCount(int saveCount) {
+ mState.restoreToCount(saveCount);
+}
+
+int RecordingCanvas::saveLayer(float left, float top, float right, float bottom,
+ const SkPaint* paint, SaveFlags::Flags flags) {
+ // force matrix/clip isolation for layer
+ flags |= SaveFlags::MatrixClip;
+ bool clippedLayer = flags & SaveFlags::ClipToLayer;
+
+ const Snapshot& previous = *mState.currentSnapshot();
+
+ // initialize the snapshot as though it almost represents an FBO layer so deferred draw
+ // operations will be able to store and restore the current clip and transform info, and
+ // quick rejection will be correct (for display lists)
+
+ const Rect unmappedBounds(left, top, right, bottom);
+
+ // determine clipped bounds relative to previous viewport.
+ Rect visibleBounds = unmappedBounds;
+ previous.transform->mapRect(visibleBounds);
+
+ if (CC_UNLIKELY(!clippedLayer
+ && previous.transform->rectToRect()
+ && visibleBounds.contains(previous.getRenderTargetClip()))) {
+ // unlikely case where an unclipped savelayer is recorded with a clip it can use,
+ // as none of its unaffected/unclipped area is visible
+ clippedLayer = true;
+ flags |= SaveFlags::ClipToLayer;
+ }
+
+ visibleBounds.doIntersect(previous.getRenderTargetClip());
+ visibleBounds.snapToPixelBoundaries();
+ visibleBounds.doIntersect(Rect(previous.getViewportWidth(), previous.getViewportHeight()));
+
+ // Map visible bounds back to layer space, and intersect with parameter bounds
+ Rect layerBounds = visibleBounds;
+ Matrix4 inverse;
+ inverse.loadInverse(*previous.transform);
+ inverse.mapRect(layerBounds);
+ layerBounds.doIntersect(unmappedBounds);
+
+ int saveValue = mState.save((int) flags);
+ Snapshot& snapshot = *mState.writableSnapshot();
+
+ // layerBounds is in original bounds space, but clipped by current recording clip
+ if (layerBounds.isEmpty() || unmappedBounds.isEmpty()) {
+ // Don't bother recording layer, since it's been rejected
+ if (CC_LIKELY(clippedLayer)) {
+ snapshot.resetClip(0, 0, 0, 0);
+ }
+ return saveValue;
+ }
+
+ if (CC_LIKELY(clippedLayer)) {
+ auto previousClip = getRecordedClip(); // note: done before new snapshot's clip has changed
+
+ snapshot.flags |= Snapshot::kFlagIsLayer | Snapshot::kFlagIsFboLayer;
+ snapshot.initializeViewport(unmappedBounds.getWidth(), unmappedBounds.getHeight());
+ snapshot.transform->loadTranslate(-unmappedBounds.left, -unmappedBounds.top, 0.0f);
+
+ Rect clip = layerBounds;
+ clip.translate(-unmappedBounds.left, -unmappedBounds.top);
+ snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom);
+ snapshot.roundRectClipState = nullptr;
+
+ addOp(alloc().create_trivial<BeginLayerOp>(
+ unmappedBounds,
+ *previous.transform, // transform to *draw* with
+ previousClip, // clip to *draw* with
+ refPaint(paint)));
+ } else {
+ snapshot.flags |= Snapshot::kFlagIsLayer;
+
+ addOp(alloc().create_trivial<BeginUnclippedLayerOp>(
+ unmappedBounds,
+ *mState.currentSnapshot()->transform,
+ getRecordedClip(),
+ refPaint(paint)));
+ }
+
+ return saveValue;
+}
+
+// Matrix
+void RecordingCanvas::rotate(float degrees) {
+ if (degrees == 0) return;
+
+ mState.rotate(degrees);
+}
+
+void RecordingCanvas::scale(float sx, float sy) {
+ if (sx == 1 && sy == 1) return;
+
+ mState.scale(sx, sy);
+}
+
+void RecordingCanvas::skew(float sx, float sy) {
+ mState.skew(sx, sy);
+}
+
+void RecordingCanvas::translate(float dx, float dy) {
+ if (dx == 0 && dy == 0) return;
+
+ mState.translate(dx, dy, 0);
+}
+
+// Clip
+bool RecordingCanvas::getClipBounds(SkRect* outRect) const {
+ *outRect = mState.getLocalClipBounds().toSkRect();
+ return !(outRect->isEmpty());
+}
+bool RecordingCanvas::quickRejectRect(float left, float top, float right, float bottom) const {
+ return mState.quickRejectConservative(left, top, right, bottom);
+}
+bool RecordingCanvas::quickRejectPath(const SkPath& path) const {
+ SkRect bounds = path.getBounds();
+ return mState.quickRejectConservative(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
+}
+bool RecordingCanvas::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
+ return mState.clipRect(left, top, right, bottom, op);
+}
+bool RecordingCanvas::clipPath(const SkPath* path, SkRegion::Op op) {
+ return mState.clipPath(path, op);
+}
+bool RecordingCanvas::clipRegion(const SkRegion* region, SkRegion::Op op) {
+ return mState.clipRegion(region, op);
+}
+
+// ----------------------------------------------------------------------------
+// android/graphics/Canvas draw operations
+// ----------------------------------------------------------------------------
+void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) {
+ addOp(alloc().create_trivial<ColorOp>(
+ getRecordedClip(),
+ color,
+ mode));
+}
+
+void RecordingCanvas::drawPaint(const SkPaint& paint) {
+ SkRect bounds;
+ if (getClipBounds(&bounds)) {
+ drawRect(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, paint);
+ }
+}
+
+static Rect calcBoundsOfPoints(const float* points, int floatCount) {
+ Rect unmappedBounds(points[0], points[1], points[0], points[1]);
+ for (int i = 2; i < floatCount; i += 2) {
+ unmappedBounds.expandToCover(points[i], points[i + 1]);
+ }
+ return unmappedBounds;
+}
+
+// Geometry
+void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) {
+ if (floatCount < 2) return;
+ floatCount &= ~0x1; // round down to nearest two
+
+ addOp(alloc().create_trivial<PointsOp>(
+ calcBoundsOfPoints(points, floatCount),
+ *mState.currentSnapshot()->transform,
+ getRecordedClip(),
+ refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
+}
+
+void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) {
+ if (floatCount < 4) return;
+ floatCount &= ~0x3; // round down to nearest four
+
+ addOp(alloc().create_trivial<LinesOp>(
+ calcBoundsOfPoints(points, floatCount),
+ *mState.currentSnapshot()->transform,
+ getRecordedClip(),
+ refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
+}
+
+void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
+ addOp(alloc().create_trivial<RectOp>(
+ Rect(left, top, right, bottom),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ refPaint(&paint)));
+}
+
+void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) {
+ if (rects == nullptr) return;
+
+ Vertex* rectData = (Vertex*) mDisplayList->allocator.create_trivial_array<Vertex>(vertexCount);
+ Vertex* vertex = rectData;
+
+ float left = FLT_MAX;
+ float top = FLT_MAX;
+ float right = FLT_MIN;
+ float bottom = FLT_MIN;
+ for (int index = 0; index < vertexCount; index += 4) {
+ float l = rects[index + 0];
+ float t = rects[index + 1];
+ float r = rects[index + 2];
+ float b = rects[index + 3];
+
+ Vertex::set(vertex++, l, t);
+ Vertex::set(vertex++, r, t);
+ Vertex::set(vertex++, l, b);
+ Vertex::set(vertex++, r, b);
+
+ left = std::min(left, l);
+ top = std::min(top, t);
+ right = std::max(right, r);
+ bottom = std::max(bottom, b);
+ }
+ addOp(alloc().create_trivial<SimpleRectsOp>(
+ Rect(left, top, right, bottom),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ refPaint(paint), rectData, vertexCount));
+}
+
+void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
+ if (paint.getStyle() == SkPaint::kFill_Style
+ && (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) {
+ int count = 0;
+ Vector<float> rects;
+ SkRegion::Iterator it(region);
+ while (!it.done()) {
+ const SkIRect& r = it.rect();
+ rects.push(r.fLeft);
+ rects.push(r.fTop);
+ rects.push(r.fRight);
+ rects.push(r.fBottom);
+ count += 4;
+ it.next();
+ }
+ drawSimpleRects(rects.array(), count, &paint);
+ } else {
+ SkRegion::Iterator it(region);
+ while (!it.done()) {
+ const SkIRect& r = it.rect();
+ drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint);
+ it.next();
+ }
+ }
+}
+void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom,
+ float rx, float ry, const SkPaint& paint) {
+ if (CC_LIKELY(MathUtils::isPositive(rx) || MathUtils::isPositive(ry))) {
+ addOp(alloc().create_trivial<RoundRectOp>(
+ Rect(left, top, right, bottom),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ refPaint(&paint), rx, ry));
+ } else {
+ drawRect(left, top, right, bottom, paint);
+ }
+}
+
+void RecordingCanvas::drawRoundRect(
+ CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top,
+ CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom,
+ CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry,
+ CanvasPropertyPaint* paint) {
+ mDisplayList->ref(left);
+ mDisplayList->ref(top);
+ mDisplayList->ref(right);
+ mDisplayList->ref(bottom);
+ mDisplayList->ref(rx);
+ mDisplayList->ref(ry);
+ mDisplayList->ref(paint);
+ refBitmapsInShader(paint->value.getShader());
+ addOp(alloc().create_trivial<RoundRectPropsOp>(
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ &paint->value,
+ &left->value, &top->value, &right->value, &bottom->value,
+ &rx->value, &ry->value));
+}
+
+void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
+ // TODO: move to Canvas.h
+ if (radius <= 0) return;
+ drawOval(x - radius, y - radius, x + radius, y + radius, paint);
+}
+
+void RecordingCanvas::drawCircle(
+ CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y,
+ CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) {
+ mDisplayList->ref(x);
+ mDisplayList->ref(y);
+ mDisplayList->ref(radius);
+ mDisplayList->ref(paint);
+ refBitmapsInShader(paint->value.getShader());
+ addOp(alloc().create_trivial<CirclePropsOp>(
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ &paint->value,
+ &x->value, &y->value, &radius->value));
+}
+
+void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
+ addOp(alloc().create_trivial<OvalOp>(
+ Rect(left, top, right, bottom),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ refPaint(&paint)));
+}
+
+void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
+ float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
+ if (fabs(sweepAngle) >= 360.0f) {
+ drawOval(left, top, right, bottom, paint);
+ } else {
+ addOp(alloc().create_trivial<ArcOp>(
+ Rect(left, top, right, bottom),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ refPaint(&paint),
+ startAngle, sweepAngle, useCenter));
+ }
+}
+
+void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
+ addOp(alloc().create_trivial<PathOp>(
+ Rect(path.getBounds()),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ refPaint(&paint), refPath(&path)));
+}
+
+void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
+ mDisplayList->pushStagingFunctors.push_back(tree->getFunctor());
+ mDisplayList->ref(tree);
+ addOp(alloc().create_trivial<VectorDrawableOp>(
+ tree,
+ Rect(tree->stagingProperties()->getBounds()),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip()));
+}
+
+// Bitmap-based
+void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
+ save(SaveFlags::Matrix);
+ translate(left, top);
+ drawBitmap(&bitmap, paint);
+ restore();
+}
+
+void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
+ const SkPaint* paint) {
+ if (matrix.isIdentity()) {
+ drawBitmap(&bitmap, paint);
+ } else if (!(matrix.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask))
+ && MathUtils::isPositive(matrix.getScaleX())
+ && MathUtils::isPositive(matrix.getScaleY())) {
+ // SkMatrix::isScaleTranslate() not available in L
+ SkRect src;
+ SkRect dst;
+ bitmap.getBounds(&src);
+ matrix.mapRect(&dst, src);
+ drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
+ dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
+ } else {
+ save(SaveFlags::Matrix);
+ concat(matrix);
+ drawBitmap(&bitmap, paint);
+ restore();
+ }
+}
+
+void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, const SkPaint* paint) {
+ if (srcLeft == 0 && srcTop == 0
+ && srcRight == bitmap.width()
+ && srcBottom == bitmap.height()
+ && (srcBottom - srcTop == dstBottom - dstTop)
+ && (srcRight - srcLeft == dstRight - dstLeft)) {
+ // transform simple rect to rect drawing case into position bitmap ops, since they merge
+ save(SaveFlags::Matrix);
+ translate(dstLeft, dstTop);
+ drawBitmap(&bitmap, paint);
+ restore();
+ } else {
+ addOp(alloc().create_trivial<BitmapRectOp>(
+ Rect(dstLeft, dstTop, dstRight, dstBottom),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ refPaint(paint), refBitmap(bitmap),
+ Rect(srcLeft, srcTop, srcRight, srcBottom)));
+ }
+}
+
+void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+ const float* vertices, const int* colors, const SkPaint* paint) {
+ int vertexCount = (meshWidth + 1) * (meshHeight + 1);
+ addOp(alloc().create_trivial<BitmapMeshOp>(
+ calcBoundsOfPoints(vertices, vertexCount * 2),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ refPaint(paint), refBitmap(bitmap), meshWidth, meshHeight,
+ refBuffer<float>(vertices, vertexCount * 2), // 2 floats per vertex
+ refBuffer<int>(colors, vertexCount))); // 1 color per vertex
+}
+
+void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& patch,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ const SkPaint* paint) {
+ addOp(alloc().create_trivial<PatchOp>(
+ Rect(dstLeft, dstTop, dstRight, dstBottom),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
+}
+
+// Text
+void RecordingCanvas::drawGlyphs(const uint16_t* glyphs, const float* positions, int glyphCount,
+ const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
+ float boundsRight, float boundsBottom, float totalAdvance) {
+ if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+ glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
+ positions = refBuffer<float>(positions, glyphCount * 2);
+
+ // TODO: either must account for text shadow in bounds, or record separate ops for text shadows
+ addOp(alloc().create_trivial<TextOp>(
+ Rect(boundsLeft, boundsTop, boundsRight, boundsBottom),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ refPaint(&paint), glyphs, positions, glyphCount, x, y));
+ drawTextDecorations(x, y, totalAdvance, paint);
+}
+
+void RecordingCanvas::drawGlyphsOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path,
+ float hOffset, float vOffset, const SkPaint& paint) {
+ if (!glyphs || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+ glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
+ addOp(alloc().create_trivial<TextOnPathOp>(
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ refPaint(&paint), glyphs, glyphCount, refPath(&path), hOffset, vOffset));
+}
+
+void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
+ addOp(alloc().create_trivial<BitmapOp>(
+ Rect(bitmap->width(), bitmap->height()),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ refPaint(paint), refBitmap(*bitmap)));
+}
+
+void RecordingCanvas::drawRenderNode(RenderNode* renderNode) {
+ auto&& stagingProps = renderNode->stagingProperties();
+ RenderNodeOp* op = alloc().create_trivial<RenderNodeOp>(
+ Rect(stagingProps.getWidth(), stagingProps.getHeight()),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ renderNode);
+ int opIndex = addOp(op);
+ if (CC_LIKELY(opIndex >= 0)) {
+ int childIndex = mDisplayList->addChild(op);
+
+ // update the chunk's child indices
+ DisplayList::Chunk& chunk = mDisplayList->chunks.back();
+ chunk.endChildIndex = childIndex + 1;
+
+ if (renderNode->stagingProperties().isProjectionReceiver()) {
+ // use staging property, since recording on UI thread
+ mDisplayList->projectionReceiveIndex = opIndex;
+ }
+ }
+}
+
+void RecordingCanvas::drawLayer(DeferredLayerUpdater* layerHandle) {
+ // We ref the DeferredLayerUpdater due to its thread-safe ref-counting semantics.
+ mDisplayList->ref(layerHandle);
+
+ // Note that the backing layer has *not* yet been updated, so don't trust
+ // its width, height, transform, etc...!
+ addOp(alloc().create_trivial<TextureLayerOp>(
+ Rect(layerHandle->getWidth(), layerHandle->getHeight()),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ layerHandle->backingLayer()));
+}
+
+void RecordingCanvas::callDrawGLFunction(Functor* functor,
+ GlFunctorLifecycleListener* listener) {
+ mDisplayList->functors.push_back({functor, listener});
+ mDisplayList->ref(listener);
+ addOp(alloc().create_trivial<FunctorOp>(
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip(),
+ functor));
+}
+
+size_t RecordingCanvas::addOp(RecordedOp* op) {
+ // skip op with empty clip
+ if (op->localClip && op->localClip->rect.isEmpty()) {
+ // NOTE: this rejection happens after op construction/content ref-ing, so content ref'd
+ // and held by renderthread isn't affected by clip rejection.
+ // Could rewind alloc here if desired, but callers would have to not touch op afterwards.
+ return -1;
+ }
+
+ int insertIndex = mDisplayList->ops.size();
+ mDisplayList->ops.push_back(op);
+ if (mDeferredBarrierType != DeferredBarrierType::None) {
+ // op is first in new chunk
+ mDisplayList->chunks.emplace_back();
+ DisplayList::Chunk& newChunk = mDisplayList->chunks.back();
+ newChunk.beginOpIndex = insertIndex;
+ newChunk.endOpIndex = insertIndex + 1;
+ newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder);
+ newChunk.reorderClip = mDeferredBarrierClip;
+
+ int nextChildIndex = mDisplayList->children.size();
+ newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
+ mDeferredBarrierType = DeferredBarrierType::None;
+ } else {
+ // standard case - append to existing chunk
+ mDisplayList->chunks.back().endOpIndex = insertIndex + 1;
+ }
+ return insertIndex;
+}
+
+void RecordingCanvas::refBitmapsInShader(const SkShader* shader) {
+ if (!shader) return;
+
+ // If this paint has an SkShader that has an SkBitmap add
+ // it to the bitmap pile
+ SkBitmap bitmap;
+ SkShader::TileMode xy[2];
+ if (shader->isABitmap(&bitmap, nullptr, xy)) {
+ refBitmap(bitmap);
+ return;
+ }
+ SkShader::ComposeRec rec;
+ if (shader->asACompose(&rec)) {
+ refBitmapsInShader(rec.fShaderA);
+ refBitmapsInShader(rec.fShaderB);
+ return;
+ }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
new file mode 100644
index 000000000000..372be241042a
--- /dev/null
+++ b/libs/hwui/RecordingCanvas.h
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2015 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_RECORDING_CANVAS_H
+#define ANDROID_HWUI_RECORDING_CANVAS_H
+
+#include "CanvasState.h"
+#include "DisplayList.h"
+#include "ResourceCache.h"
+#include "SkiaCanvasProxy.h"
+#include "Snapshot.h"
+#include "hwui/Canvas.h"
+#include "utils/LinearAllocator.h"
+#include "utils/Macros.h"
+#include "utils/NinePatch.h"
+
+#include <SkDrawFilter.h>
+#include <SkPaint.h>
+#include <SkTLazy.h>
+
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+struct ClipBase;
+class DeferredLayerUpdater;
+struct RecordedOp;
+
+class ANDROID_API RecordingCanvas: public Canvas, public CanvasStateClient {
+ enum class DeferredBarrierType {
+ None,
+ InOrder,
+ OutOfOrder,
+ };
+public:
+ RecordingCanvas(size_t width, size_t height);
+ virtual ~RecordingCanvas();
+
+ virtual void resetRecording(int width, int height) override;
+ virtual WARN_UNUSED_RESULT DisplayList* finishRecording() override;
+// ----------------------------------------------------------------------------
+// MISC HWUI OPERATIONS - TODO: CATEGORIZE
+// ----------------------------------------------------------------------------
+ virtual void insertReorderBarrier(bool enableReorder) override;
+
+ virtual void drawLayer(DeferredLayerUpdater* layerHandle) override;
+ virtual void drawRenderNode(RenderNode* renderNode) override;
+ virtual void callDrawGLFunction(Functor* functor,
+ GlFunctorLifecycleListener* listener) override;
+
+// ----------------------------------------------------------------------------
+// CanvasStateClient interface
+// ----------------------------------------------------------------------------
+ virtual void onViewportInitialized() override;
+ virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override;
+ virtual GLuint getTargetFbo() const override { return -1; }
+
+// ----------------------------------------------------------------------------
+// HWUI Canvas draw operations
+// ----------------------------------------------------------------------------
+
+ virtual void drawRoundRect(CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top,
+ CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom,
+ CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry,
+ CanvasPropertyPaint* paint) override;
+ virtual void drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y,
+ CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) override;
+
+// ----------------------------------------------------------------------------
+// android/graphics/Canvas interface
+// ----------------------------------------------------------------------------
+ virtual SkCanvas* asSkCanvas() override;
+
+ virtual void setBitmap(const SkBitmap& bitmap) override {
+ LOG_ALWAYS_FATAL("RecordingCanvas is not backed by a bitmap.");
+ }
+
+ virtual bool isOpaque() override { return false; }
+ virtual int width() override { return mState.getWidth(); }
+ virtual int height() override { return mState.getHeight(); }
+
+ virtual void setHighContrastText(bool highContrastText) override {
+ mHighContrastText = highContrastText;
+ }
+ virtual bool isHighContrastText() override { return mHighContrastText; }
+
+// ----------------------------------------------------------------------------
+// android/graphics/Canvas state operations
+// ----------------------------------------------------------------------------
+ // Save (layer)
+ virtual int getSaveCount() const override { return mState.getSaveCount(); }
+ virtual int save(SaveFlags::Flags flags) override;
+ virtual void restore() override;
+ virtual void restoreToCount(int saveCount) override;
+
+ virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
+ SaveFlags::Flags flags) override;
+ virtual int saveLayerAlpha(float left, float top, float right, float bottom,
+ int alpha, SaveFlags::Flags flags) override {
+ SkPaint paint;
+ paint.setAlpha(alpha);
+ return saveLayer(left, top, right, bottom, &paint, flags);
+ }
+
+ // Matrix
+ virtual void getMatrix(SkMatrix* outMatrix) const override { mState.getMatrix(outMatrix); }
+ virtual void setMatrix(const SkMatrix& matrix) override { mState.setMatrix(matrix); }
+
+ virtual void concat(const SkMatrix& matrix) override { mState.concatMatrix(matrix); }
+ virtual void rotate(float degrees) override;
+ virtual void scale(float sx, float sy) override;
+ virtual void skew(float sx, float sy) override;
+ virtual void translate(float dx, float dy) override;
+
+ // Clip
+ virtual bool getClipBounds(SkRect* outRect) const override;
+ virtual bool quickRejectRect(float left, float top, float right, float bottom) const override;
+ virtual bool quickRejectPath(const SkPath& path) const override;
+
+ virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op) override;
+ virtual bool clipPath(const SkPath* path, SkRegion::Op op) override;
+ virtual bool clipRegion(const SkRegion* region, SkRegion::Op op) override;
+
+ // Misc
+ virtual SkDrawFilter* getDrawFilter() override { return mDrawFilter.get(); }
+ virtual void setDrawFilter(SkDrawFilter* filter) override {
+ mDrawFilter.reset(SkSafeRef(filter));
+ }
+
+// ----------------------------------------------------------------------------
+// android/graphics/Canvas draw operations
+// ----------------------------------------------------------------------------
+ virtual void drawColor(int color, SkXfermode::Mode mode) override;
+ virtual void drawPaint(const SkPaint& paint) override;
+
+ // Geometry
+ virtual void drawPoint(float x, float y, const SkPaint& paint) override {
+ float points[2] = { x, y };
+ drawPoints(points, 2, paint);
+ }
+ virtual void drawPoints(const float* points, int floatCount, const SkPaint& paint) override;
+ virtual void drawLine(float startX, float startY, float stopX, float stopY,
+ const SkPaint& paint) override {
+ float points[4] = { startX, startY, stopX, stopY };
+ drawLines(points, 4, paint);
+ }
+ virtual void drawLines(const float* points, int floatCount, const SkPaint& paint) override;
+ virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override;
+ virtual void drawRegion(const SkRegion& region, const SkPaint& paint) override;
+ virtual void drawRoundRect(float left, float top, float right, float bottom,
+ float rx, float ry, const SkPaint& paint) override;
+ virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) override;
+ virtual void drawOval(float left, float top, float right, float bottom, const SkPaint& paint) override;
+ virtual void drawArc(float left, float top, float right, float bottom,
+ float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) override;
+ virtual void drawPath(const SkPath& path, const SkPaint& paint) override;
+ virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
+ const float* verts, const float* tex, const int* colors,
+ const uint16_t* indices, int indexCount, const SkPaint& paint) override
+ { /* RecordingCanvas does not support drawVertices(); ignore */ }
+
+ virtual void drawVectorDrawable(VectorDrawableRoot* tree) override;
+
+ // Bitmap-based
+ virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) override;
+ virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
+ const SkPaint* paint) override;
+ virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, const SkPaint* paint) override;
+ virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+ const float* vertices, const int* colors, const SkPaint* paint) override;
+ virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ const SkPaint* paint) override;
+
+ // Text
+ virtual bool drawTextAbsolutePos() const override { return false; }
+
+protected:
+ virtual void drawGlyphs(const uint16_t* text, const float* positions, int count,
+ const SkPaint& paint, float x, float y,
+ float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
+ float totalAdvance) override;
+ virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+ float hOffset, float vOffset, const SkPaint& paint) override;
+
+private:
+ const ClipBase* getRecordedClip() {
+ return mState.writableSnapshot()->mutateClipArea().serializeClip(alloc());
+ }
+
+ void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
+ void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint);
+
+
+ size_t addOp(RecordedOp* op);
+// ----------------------------------------------------------------------------
+// lazy object copy
+// ----------------------------------------------------------------------------
+ LinearAllocator& alloc() { return mDisplayList->allocator; }
+
+ void refBitmapsInShader(const SkShader* shader);
+
+ template<class T>
+ inline const T* refBuffer(const T* srcBuffer, int32_t count) {
+ if (!srcBuffer) return nullptr;
+
+ T* dstBuffer = (T*) mDisplayList->allocator.alloc<T>(count * sizeof(T));
+ memcpy(dstBuffer, srcBuffer, count * sizeof(T));
+ return dstBuffer;
+ }
+
+ inline const SkPath* refPath(const SkPath* path) {
+ if (!path) return nullptr;
+
+ // The points/verbs within the path are refcounted so this copy operation
+ // is inexpensive and maintains the generationID of the original path.
+ const SkPath* cachedPath = new SkPath(*path);
+ mDisplayList->pathResources.push_back(cachedPath);
+ return cachedPath;
+ }
+
+ /**
+ * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in
+ * (with deduping based on paint hash / equality check)
+ */
+ inline const SkPaint* refPaint(const SkPaint* paint) {
+ if (!paint) return nullptr;
+
+ // If there is a draw filter apply it here and store the modified paint
+ // so that we don't need to modify the paint every time we access it.
+ SkTLazy<SkPaint> filteredPaint;
+ if (mDrawFilter.get()) {
+ filteredPaint.set(*paint);
+ mDrawFilter->filter(filteredPaint.get(), SkDrawFilter::kPaint_Type);
+ paint = filteredPaint.get();
+ }
+
+ // compute the hash key for the paint and check the cache.
+ const uint32_t key = paint->getHash();
+ const SkPaint* cachedPaint = mPaintMap.valueFor(key);
+ // In the unlikely event that 2 unique paints have the same hash we do a
+ // object equality check to ensure we don't erroneously dedup them.
+ if (cachedPaint == nullptr || *cachedPaint != *paint) {
+ cachedPaint = new SkPaint(*paint);
+ mDisplayList->paints.emplace_back(cachedPaint);
+ // replaceValueFor() performs an add if the entry doesn't exist
+ mPaintMap.replaceValueFor(key, cachedPaint);
+ refBitmapsInShader(cachedPaint->getShader());
+ }
+
+ return cachedPaint;
+ }
+
+ inline const SkRegion* refRegion(const SkRegion* region) {
+ if (!region) {
+ return region;
+ }
+
+ const SkRegion* cachedRegion = mRegionMap.valueFor(region);
+ // TODO: Add generation ID to SkRegion
+ if (cachedRegion == nullptr) {
+ std::unique_ptr<const SkRegion> copy(new SkRegion(*region));
+ cachedRegion = copy.get();
+ mDisplayList->regions.push_back(std::move(copy));
+
+ // replaceValueFor() performs an add if the entry doesn't exist
+ mRegionMap.replaceValueFor(region, cachedRegion);
+ }
+
+ return cachedRegion;
+ }
+
+ inline const SkBitmap* refBitmap(const SkBitmap& bitmap) {
+ // Note that this assumes the bitmap is immutable. There are cases this won't handle
+ // correctly, such as creating the bitmap from scratch, drawing with it, changing its
+ // contents, and drawing again. The only fix would be to always copy it the first time,
+ // which doesn't seem worth the extra cycles for this unlikely case.
+ SkBitmap* localBitmap = alloc().create<SkBitmap>(bitmap);
+ mDisplayList->bitmapResources.push_back(localBitmap);
+ return localBitmap;
+ }
+
+ inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) {
+ mDisplayList->patchResources.push_back(patch);
+ mResourceCache.incrementRefcount(patch);
+ return patch;
+ }
+
+ DefaultKeyedVector<uint32_t, const SkPaint*> mPaintMap;
+ DefaultKeyedVector<const SkPath*, const SkPath*> mPathMap;
+ DefaultKeyedVector<const SkRegion*, const SkRegion*> mRegionMap;
+
+ CanvasState mState;
+ std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy;
+ ResourceCache& mResourceCache;
+ DeferredBarrierType mDeferredBarrierType = DeferredBarrierType::None;
+ const ClipBase* mDeferredBarrierClip = nullptr;
+ DisplayList* mDisplayList = nullptr;
+ bool mHighContrastText = false;
+ SkAutoTUnref<SkDrawFilter> mDrawFilter;
+}; // class RecordingCanvas
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_RECORDING_CANVAS_H
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 4c4cd3da3be4..de4fa55bb508 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-#ifndef ANDROID_HWUI_RECT_H
-#define ANDROID_HWUI_RECT_H
+#pragma once
-#include <cmath>
-#include <algorithm>
-#include <SkRect.h>
+#include "Vertex.h"
#include <utils/Log.h>
-#include "Vertex.h"
+#include <algorithm>
+#include <cmath>
+#include <iomanip>
+#include <ostream>
+#include <SkRect.h>
namespace android {
namespace uirenderer {
@@ -125,25 +126,32 @@ public:
}
bool intersects(float l, float t, float r, float b) const {
- return !intersectWith(l, t, r, b).isEmpty();
+ float tempLeft = std::max(left, l);
+ float tempTop = std::max(top, t);
+ float tempRight = std::min(right, r);
+ float tempBottom = std::min(bottom, b);
+
+ return ((tempLeft < tempRight) && (tempTop < tempBottom)); // !isEmpty
}
bool intersects(const Rect& r) const {
return intersects(r.left, r.top, r.right, r.bottom);
}
- bool intersect(float l, float t, float r, float b) {
- Rect tmp(l, t, r, b);
- intersectWith(tmp);
- if (!tmp.isEmpty()) {
- set(tmp);
- return true;
- }
- return false;
+ /**
+ * This method is named 'doIntersect' instead of 'intersect' so as not to be confused with
+ * SkRect::intersect / android.graphics.Rect#intersect behavior, which do not modify the object
+ * if the intersection of the rects would be empty.
+ */
+ void doIntersect(float l, float t, float r, float b) {
+ left = std::max(left, l);
+ top = std::max(top, t);
+ right = std::min(right, r);
+ bottom = std::min(bottom, b);
}
- bool intersect(const Rect& r) {
- return intersect(r.left, r.top, r.right, r.bottom);
+ void doIntersect(const Rect& r) {
+ doIntersect(r.left, r.top, r.right, r.bottom);
}
inline bool contains(float l, float t, float r, float b) const {
@@ -246,20 +254,24 @@ public:
bottom = ceilf(bottom);
}
- void expandToCoverVertex(float x, float y) {
+ /*
+ * Similar to unionWith, except this makes the assumption that both rects are non-empty
+ * to avoid both emptiness checks.
+ */
+ void expandToCover(const Rect& other) {
+ left = std::min(left, other.left);
+ top = std::min(top, other.top);
+ right = std::max(right, other.right);
+ bottom = std::max(bottom, other.bottom);
+ }
+
+ void expandToCover(float x, float y) {
left = std::min(left, x);
top = std::min(top, y);
right = std::max(right, x);
bottom = std::max(bottom, y);
}
- void expandToCoverRect(float otherLeft, float otherTop, float otherRight, float otherBottom) {
- left = std::min(left, otherLeft);
- top = std::min(top, otherTop);
- right = std::max(right, otherRight);
- bottom = std::max(bottom, otherBottom);
- }
-
SkRect toSkRect() const {
return SkRect::MakeLTRB(left, top, right, bottom);
}
@@ -269,29 +281,26 @@ public:
}
void dump(const char* label = nullptr) const {
- ALOGD("%s[l=%f t=%f r=%f b=%f]", label ? label : "Rect", left, top, right, bottom);
+ ALOGD("%s[l=%.2f t=%.2f r=%.2f b=%.2f]", label ? label : "Rect", left, top, right, bottom);
}
-private:
- void intersectWith(Rect& tmp) const {
- tmp.left = std::max(left, tmp.left);
- tmp.top = std::max(top, tmp.top);
- tmp.right = std::min(right, tmp.right);
- tmp.bottom = std::min(bottom, tmp.bottom);
- }
+ friend std::ostream& operator<<(std::ostream& os, const Rect& rect) {
+ if (rect.isEmpty()) {
+ // Print empty, but continue, since empty rects may still have useful coordinate info
+ os << "(empty)";
+ }
- Rect intersectWith(float l, float t, float r, float b) const {
- Rect tmp;
- tmp.left = std::max(left, l);
- tmp.top = std::max(top, t);
- tmp.right = std::min(right, r);
- tmp.bottom = std::min(bottom, b);
- return tmp;
- }
+ if (rect.left == 0 && rect.top == 0) {
+ return os << "[" << rect.right << " x " << rect.bottom << "]";
+ }
+ return os << "[" << rect.left
+ << " " << rect.top
+ << " " << rect.right
+ << " " << rect.bottom << "]";
+ }
}; // class Rect
}; // namespace uirenderer
}; // namespace android
-#endif // ANDROID_HWUI_RECT_H
diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp
index d0812c96afd7..1ac57cdbac0c 100644
--- a/libs/hwui/RenderBufferCache.cpp
+++ b/libs/hwui/RenderBufferCache.cpp
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
-#include <utils/Log.h>
-
#include "Debug.h"
#include "Properties.h"
#include "RenderBufferCache.h"
+#include <utils/Log.h>
+
+#include <cstdlib>
+
namespace android {
namespace uirenderer {
@@ -40,16 +40,9 @@ namespace uirenderer {
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
-RenderBufferCache::RenderBufferCache(): mSize(0), mMaxSize(MB(DEFAULT_RENDER_BUFFER_CACHE_SIZE)) {
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_RENDER_BUFFER_CACHE_SIZE, property, nullptr) > 0) {
- INIT_LOGD(" Setting render buffer cache size to %sMB", property);
- setMaxSize(MB(atof(property)));
- } else {
- INIT_LOGD(" Using default render buffer cache size of %.2fMB",
- DEFAULT_RENDER_BUFFER_CACHE_SIZE);
- }
-}
+RenderBufferCache::RenderBufferCache()
+ : mSize(0)
+ , mMaxSize(Properties::renderBufferCacheSize) {}
RenderBufferCache::~RenderBufferCache() {
clear();
@@ -67,11 +60,6 @@ uint32_t RenderBufferCache::getMaxSize() {
return mMaxSize;
}
-void RenderBufferCache::setMaxSize(uint32_t maxSize) {
- clear();
- mMaxSize = maxSize;
-}
-
///////////////////////////////////////////////////////////////////////////////
// Caching
///////////////////////////////////////////////////////////////////////////////
@@ -100,9 +88,8 @@ void RenderBufferCache::deleteBuffer(RenderBuffer* buffer) {
}
void RenderBufferCache::clear() {
- size_t count = mCache.size();
- for (size_t i = 0; i < count; i++) {
- deleteBuffer(mCache.itemAt(i).mBuffer);
+ for (auto entry : mCache) {
+ deleteBuffer(entry.mBuffer);
}
mCache.clear();
}
@@ -111,11 +98,11 @@ RenderBuffer* RenderBufferCache::get(GLenum format, const uint32_t width, const
RenderBuffer* buffer = nullptr;
RenderBufferEntry entry(format, width, height);
- ssize_t index = mCache.indexOf(entry);
+ auto iter = mCache.find(entry);
- if (index >= 0) {
- entry = mCache.itemAt(index);
- mCache.removeAt(index);
+ if (iter != mCache.end()) {
+ entry = *iter;
+ mCache.erase(iter);
buffer = entry.mBuffer;
mSize -= buffer->getSize();
@@ -141,16 +128,14 @@ bool RenderBufferCache::put(RenderBuffer* buffer) {
const uint32_t size = buffer->getSize();
if (size < mMaxSize) {
while (mSize + size > mMaxSize) {
- size_t position = 0;
-
- RenderBuffer* victim = mCache.itemAt(position).mBuffer;
+ RenderBuffer* victim = mCache.begin()->mBuffer;
deleteBuffer(victim);
- mCache.removeAt(position);
+ mCache.erase(mCache.begin());
}
RenderBufferEntry entry(buffer);
- mCache.add(entry);
+ mCache.insert(entry);
mSize += size;
RENDER_BUFFER_LOGD("Added %s render buffer (%dx%d)",
diff --git a/libs/hwui/RenderBufferCache.h b/libs/hwui/RenderBufferCache.h
index 6c668b09c40d..f77f4c95b5ba 100644
--- a/libs/hwui/RenderBufferCache.h
+++ b/libs/hwui/RenderBufferCache.h
@@ -20,7 +20,8 @@
#include <GLES2/gl2.h>
#include "RenderBuffer.h"
-#include "utils/SortedList.h"
+
+#include <set>
namespace android {
namespace uirenderer {
@@ -63,10 +64,6 @@ public:
void clear();
/**
- * Sets the maximum size of the cache in bytes.
- */
- void setMaxSize(uint32_t maxSize);
- /**
* Returns the maximum size of the cache in bytes.
*/
uint32_t getMaxSize();
@@ -100,14 +97,8 @@ private:
return compare(*this, other) != 0;
}
- friend inline int strictly_order_type(const RenderBufferEntry& lhs,
- const RenderBufferEntry& rhs) {
- return RenderBufferEntry::compare(lhs, rhs) < 0;
- }
-
- friend inline int compare_type(const RenderBufferEntry& lhs,
- const RenderBufferEntry& rhs) {
- return RenderBufferEntry::compare(lhs, rhs);
+ bool operator<(const RenderBufferEntry& other) const {
+ return RenderBufferEntry::compare(*this, other) < 0;
}
RenderBuffer* mBuffer;
@@ -118,7 +109,7 @@ private:
void deleteBuffer(RenderBuffer* buffer);
- SortedList<RenderBufferEntry> mCache;
+ std::multiset<RenderBufferEntry> mCache;
uint32_t mSize;
uint32_t mMaxSize;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 642ec25dfe83..6e848fddf48f 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -14,20 +14,15 @@
* limitations under the License.
*/
-#define ATRACE_TAG ATRACE_TAG_VIEW
-#define LOG_TAG "OpenGLRenderer"
-
#include "RenderNode.h"
-#include <algorithm>
-#include <string>
-
-#include <SkCanvas.h>
-#include <algorithm>
-
-
#include "DamageAccumulator.h"
#include "Debug.h"
+#if HWUI_NEW_OPS
+#include "BakedOpRenderer.h"
+#include "RecordedOp.h"
+#include "OpDumper.h"
+#endif
#include "DisplayListOp.h"
#include "LayerRenderer.h"
#include "OpenGLRenderer.h"
@@ -36,52 +31,99 @@
#include "utils/TraceUtils.h"
#include "renderthread/CanvasContext.h"
+#include "protos/hwui.pb.h"
+#include "protos/ProtoHelpers.h"
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+
namespace android {
namespace uirenderer {
void RenderNode::debugDumpLayers(const char* prefix) {
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("TODO: dump layer");
+#else
if (mLayer) {
ALOGD("%sNode %p (%s) has layer %p (fbo = %u, wasBuildLayered = %s)",
prefix, this, getName(), mLayer, mLayer->getFbo(),
mLayer->wasBuildLayered ? "true" : "false");
}
- if (mDisplayListData) {
- for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
- mDisplayListData->children()[i]->mRenderNode->debugDumpLayers(prefix);
+#endif
+ if (mDisplayList) {
+ for (auto&& child : mDisplayList->getChildren()) {
+ child->renderNode->debugDumpLayers(prefix);
}
}
}
RenderNode::RenderNode()
: mDirtyPropertyFields(0)
- , mNeedsDisplayListDataSync(false)
- , mDisplayListData(nullptr)
- , mStagingDisplayListData(nullptr)
+ , mNeedsDisplayListSync(false)
+ , mDisplayList(nullptr)
+ , mStagingDisplayList(nullptr)
, mAnimatorManager(*this)
- , mLayer(nullptr)
, mParentCount(0) {
}
RenderNode::~RenderNode() {
- deleteDisplayListData();
- delete mStagingDisplayListData;
+ deleteDisplayList(nullptr);
+ delete mStagingDisplayList;
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL_IF(mLayer, "layer missed detachment!");
+#else
if (mLayer) {
ALOGW("Memory Warning: Layer %p missed its detachment, held on to for far too long!", mLayer);
mLayer->postDecStrong();
mLayer = nullptr;
}
+#endif
}
-void RenderNode::setStagingDisplayList(DisplayListData* data) {
- mNeedsDisplayListDataSync = true;
- delete mStagingDisplayListData;
- mStagingDisplayListData = data;
+void RenderNode::setStagingDisplayList(DisplayList* displayList, TreeObserver* observer) {
+ mNeedsDisplayListSync = true;
+ delete mStagingDisplayList;
+ mStagingDisplayList = displayList;
+ // If mParentCount == 0 we are the sole reference to this RenderNode,
+ // so immediately free the old display list
+ if (!mParentCount && !mStagingDisplayList) {
+ deleteDisplayList(observer);
+ }
}
/**
* This function is a simplified version of replay(), where we simply retrieve and log the
* display list. This function should remain in sync with the replay() function.
*/
+#if HWUI_NEW_OPS
+void RenderNode::output(uint32_t level, const char* label) {
+ ALOGD("%s (%s %p%s%s%s%s%s)",
+ label,
+ getName(),
+ this,
+ (MathUtils::isZero(properties().getAlpha()) ? ", zero alpha" : ""),
+ (properties().hasShadow() ? ", casting shadow" : ""),
+ (isRenderable() ? "" : ", empty"),
+ (properties().getProjectBackwards() ? ", projected" : ""),
+ (mLayer != nullptr ? ", on HW Layer" : ""));
+ properties().debugOutputProperties(level + 1);
+
+ if (mDisplayList) {
+ for (auto&& op : mDisplayList->getOps()) {
+ std::stringstream strout;
+ OpDumper::dump(*op, strout, level + 1);
+ if (op->opId == RecordedOpId::RenderNodeOp) {
+ auto rnOp = reinterpret_cast<const RenderNodeOp*>(op);
+ rnOp->renderNode->output(level + 1, strout.str().c_str());
+ } else {
+ ALOGD("%s", strout.str().c_str());
+ }
+ }
+ }
+ ALOGD("%*s/RenderNode(%s %p)", level * 2, "", getName(), this);
+}
+#else
void RenderNode::output(uint32_t level) {
ALOGD("%*sStart display list (%p, %s%s%s%s%s%s)", (level - 1) * 2, "", this,
getName(),
@@ -90,28 +132,97 @@ void RenderNode::output(uint32_t level) {
(isRenderable() ? "" : ", empty"),
(properties().getProjectBackwards() ? ", projected" : ""),
(mLayer != nullptr ? ", on HW Layer" : ""));
- ALOGD("%*s%s %d", level * 2, "", "Save",
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
-
+ ALOGD("%*s%s %d", level * 2, "", "Save", SaveFlags::MatrixClip);
properties().debugOutputProperties(level);
- int flags = DisplayListOp::kOpLogFlag_Recurse;
- if (mDisplayListData) {
+ if (mDisplayList) {
// TODO: consider printing the chunk boundaries here
- for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
- mDisplayListData->displayListOps[i]->output(level, flags);
+ for (auto&& op : mDisplayList->getOps()) {
+ op->output(level, DisplayListOp::kOpLogFlag_Recurse);
}
}
-
ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, getName());
+ }
+#endif
+
+void RenderNode::copyTo(proto::RenderNode *pnode) {
+ pnode->set_id(static_cast<uint64_t>(
+ reinterpret_cast<uintptr_t>(this)));
+ pnode->set_name(mName.string(), mName.length());
+
+ proto::RenderProperties* pprops = pnode->mutable_properties();
+ pprops->set_left(properties().getLeft());
+ pprops->set_top(properties().getTop());
+ pprops->set_right(properties().getRight());
+ pprops->set_bottom(properties().getBottom());
+ pprops->set_clip_flags(properties().getClippingFlags());
+ pprops->set_alpha(properties().getAlpha());
+ pprops->set_translation_x(properties().getTranslationX());
+ pprops->set_translation_y(properties().getTranslationY());
+ pprops->set_translation_z(properties().getTranslationZ());
+ pprops->set_elevation(properties().getElevation());
+ pprops->set_rotation(properties().getRotation());
+ pprops->set_rotation_x(properties().getRotationX());
+ pprops->set_rotation_y(properties().getRotationY());
+ pprops->set_scale_x(properties().getScaleX());
+ pprops->set_scale_y(properties().getScaleY());
+ pprops->set_pivot_x(properties().getPivotX());
+ pprops->set_pivot_y(properties().getPivotY());
+ pprops->set_has_overlapping_rendering(properties().getHasOverlappingRendering());
+ pprops->set_pivot_explicitly_set(properties().isPivotExplicitlySet());
+ pprops->set_project_backwards(properties().getProjectBackwards());
+ pprops->set_projection_receiver(properties().isProjectionReceiver());
+ set(pprops->mutable_clip_bounds(), properties().getClipBounds());
+
+ const Outline& outline = properties().getOutline();
+ if (outline.getType() != Outline::Type::None) {
+ proto::Outline* poutline = pprops->mutable_outline();
+ poutline->clear_path();
+ if (outline.getType() == Outline::Type::Empty) {
+ poutline->set_type(proto::Outline_Type_Empty);
+ } else if (outline.getType() == Outline::Type::ConvexPath) {
+ poutline->set_type(proto::Outline_Type_ConvexPath);
+ if (const SkPath* path = outline.getPath()) {
+ set(poutline->mutable_path(), *path);
+ }
+ } else if (outline.getType() == Outline::Type::RoundRect) {
+ poutline->set_type(proto::Outline_Type_RoundRect);
+ } else {
+ ALOGW("Uknown outline type! %d", static_cast<int>(outline.getType()));
+ poutline->set_type(proto::Outline_Type_None);
+ }
+ poutline->set_should_clip(outline.getShouldClip());
+ poutline->set_alpha(outline.getAlpha());
+ poutline->set_radius(outline.getRadius());
+ set(poutline->mutable_bounds(), outline.getBounds());
+ } else {
+ pprops->clear_outline();
+ }
+
+ const RevealClip& revealClip = properties().getRevealClip();
+ if (revealClip.willClip()) {
+ proto::RevealClip* prevealClip = pprops->mutable_reveal_clip();
+ prevealClip->set_x(revealClip.getX());
+ prevealClip->set_y(revealClip.getY());
+ prevealClip->set_radius(revealClip.getRadius());
+ } else {
+ pprops->clear_reveal_clip();
+ }
+
+ pnode->clear_children();
+ if (mDisplayList) {
+ for (auto&& child : mDisplayList->getChildren()) {
+ child->renderNode->copyTo(pnode->add_children());
+ }
+ }
}
int RenderNode::getDebugSize() {
int size = sizeof(RenderNode);
- if (mStagingDisplayListData) {
- size += mStagingDisplayListData->getUsedSize();
+ if (mStagingDisplayList) {
+ size += mStagingDisplayList->getUsedSize();
}
- if (mDisplayListData && mDisplayListData != mStagingDisplayListData) {
- size += mDisplayListData->getUsedSize();
+ if (mDisplayList && mDisplayList != mStagingDisplayList) {
+ size += mDisplayList->getUsedSize();
}
return size;
}
@@ -130,6 +241,10 @@ void RenderNode::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
mAnimatorManager.addAnimator(animator);
}
+void RenderNode::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) {
+ mAnimatorManager.removeAnimator(animator);
+}
+
void RenderNode::damageSelf(TreeInfo& info) {
if (isRenderable()) {
if (properties().getClipDamageToBounds()) {
@@ -137,7 +252,7 @@ void RenderNode::damageSelf(TreeInfo& info) {
} else {
// Hope this is big enough?
// TODO: Get this from the display list ops or something
- info.damageAccumulator->dirty(INT_MIN, INT_MIN, INT_MAX, INT_MAX);
+ info.damageAccumulator->dirty(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
}
}
}
@@ -157,13 +272,38 @@ void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) {
}
}
+static layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
+#if HWUI_NEW_OPS
+ return renderState.layerPool().get(renderState, width, height);
+#else
+ return LayerRenderer::createRenderLayer(renderState, width, height);
+#endif
+}
+
+static void destroyLayer(layer_t* layer) {
+#if HWUI_NEW_OPS
+ RenderState& renderState = layer->renderState;
+ renderState.layerPool().putOrDelete(layer);
+#else
+ LayerRenderer::destroyLayer(layer);
+#endif
+}
+
+static bool layerMatchesWidthAndHeight(layer_t* layer, int width, int height) {
+#if HWUI_NEW_OPS
+ return layer->viewportWidth == (uint32_t) width && layer->viewportHeight == (uint32_t)height;
+#else
+ return layer->layer.getWidth() == width && layer->layer.getHeight() == height;
+#endif
+}
+
void RenderNode::pushLayerUpdate(TreeInfo& info) {
LayerType layerType = properties().effectiveLayerType();
// If we are not a layer OR we cannot be rendered (eg, view was detached)
// we need to destroy any Layers we may have had previously
if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable())) {
if (CC_UNLIKELY(mLayer)) {
- LayerRenderer::destroyLayer(mLayer);
+ destroyLayer(mLayer);
mLayer = nullptr;
}
return;
@@ -171,13 +311,22 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
bool transformUpdateNeeded = false;
if (!mLayer) {
- mLayer = LayerRenderer::createRenderLayer(info.renderState, getWidth(), getHeight());
+ mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
+#if !HWUI_NEW_OPS
applyLayerPropertiesToLayer(info);
+#endif
damageSelf(info);
transformUpdateNeeded = true;
- } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
+ } else if (!layerMatchesWidthAndHeight(mLayer, getWidth(), getHeight())) {
+#if HWUI_NEW_OPS
+ RenderState& renderState = mLayer->renderState;
+ if (properties().fitsOnLayer()) {
+ mLayer = renderState.layerPool().resize(mLayer, getWidth(), getHeight());
+ } else {
+#else
if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
- LayerRenderer::destroyLayer(mLayer);
+#endif
+ destroyLayer(mLayer);
mLayer = nullptr;
}
damageSelf(info);
@@ -190,20 +339,30 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
if (!mLayer) {
Caches::getInstance().dumpMemoryUsage();
if (info.errorHandler) {
- std::string msg = "Unable to create layer for ";
- msg += getName();
- info.errorHandler->onError(msg);
+ std::ostringstream err;
+ err << "Unable to create layer for " << getName();
+ const int maxTextureSize = Caches::getInstance().maxTextureSize;
+ if (getWidth() > maxTextureSize || getHeight() > maxTextureSize) {
+ err << ", size " << getWidth() << "x" << getHeight()
+ << " exceeds max size " << maxTextureSize;
+ } else {
+ err << ", see logcat for more info";
+ }
+ info.errorHandler->onError(err.str());
}
return;
}
- if (transformUpdateNeeded) {
+ if (transformUpdateNeeded && mLayer) {
// update the transform in window of the layer to reset its origin wrt light source position
Matrix4 windowTransform;
info.damageAccumulator->computeCurrentTransform(&windowTransform);
mLayer->setWindowTransform(windowTransform);
}
+#if HWUI_NEW_OPS
+ info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
+#else
if (dirty.intersect(0, 0, getWidth(), getHeight())) {
dirty.roundOut(&dirty);
mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom);
@@ -213,13 +372,12 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
if (info.renderer && mLayer->deferredUpdateScheduled) {
info.renderer->pushLayerUpdate(mLayer);
}
+#endif
- if (info.canvasContext) {
- // There might be prefetched layers that need to be accounted for.
- // That might be us, so tell CanvasContext that this layer is in the
- // tree and should not be destroyed.
- info.canvasContext->markLayerInUse(this);
- }
+ // There might be prefetched layers that need to be accounted for.
+ // That might be us, so tell CanvasContext that this layer is in the
+ // tree and should not be destroyed.
+ info.canvasContext.markLayerInUse(this);
}
/**
@@ -242,24 +400,32 @@ void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) {
}
bool willHaveFunctor = false;
- if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayListData) {
- willHaveFunctor = !mStagingDisplayListData->functors.isEmpty();
- } else if (mDisplayListData) {
- willHaveFunctor = !mDisplayListData->functors.isEmpty();
+ if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayList) {
+ willHaveFunctor = !mStagingDisplayList->getFunctors().empty();
+ } else if (mDisplayList) {
+ willHaveFunctor = !mDisplayList->getFunctors().empty();
}
bool childFunctorsNeedLayer = mProperties.prepareForFunctorPresence(
willHaveFunctor, functorsNeedLayer);
+ if (CC_UNLIKELY(mPositionListener.get())) {
+ mPositionListener->onPositionUpdated(*this, info);
+ }
+
prepareLayer(info, animatorDirtyMask);
if (info.mode == TreeInfo::MODE_FULL) {
pushStagingDisplayListChanges(info);
}
- prepareSubTree(info, childFunctorsNeedLayer, mDisplayListData);
+ prepareSubTree(info, childFunctorsNeedLayer, mDisplayList);
pushLayerUpdate(info);
info.damageAccumulator->popTransform();
}
+void RenderNode::syncProperties() {
+ mProperties = mStagingProperties;
+}
+
void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
// Push the animators first so that setupStartValueIfNecessary() is called
// before properties() is trampled by stagingProperties(), as they are
@@ -271,8 +437,10 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
mDirtyPropertyFields = 0;
damageSelf(info);
info.damageAccumulator->popTransform();
- mProperties = mStagingProperties;
+ syncProperties();
+#if !HWUI_NEW_OPS
applyLayerPropertiesToLayer(info);
+#endif
// We could try to be clever and only re-damage if the matrix changed.
// However, we don't need to worry about that. The cost of over-damaging
// here is only going to be a single additional map rect of this node
@@ -283,6 +451,7 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
}
}
+#if !HWUI_NEW_OPS
void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) {
if (CC_LIKELY(!mLayer)) return;
@@ -291,96 +460,104 @@ void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) {
mLayer->setColorFilter(props.colorFilter());
mLayer->setBlend(props.needsBlending());
}
+#endif
-void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) {
- if (mNeedsDisplayListDataSync) {
- mNeedsDisplayListDataSync = false;
- // Make sure we inc first so that we don't fluctuate between 0 and 1,
- // which would thrash the layer cache
- if (mStagingDisplayListData) {
- for (size_t i = 0; i < mStagingDisplayListData->children().size(); i++) {
- mStagingDisplayListData->children()[i]->mRenderNode->incParentRefCount();
- }
+void RenderNode::syncDisplayList(TreeObserver* observer) {
+ // Make sure we inc first so that we don't fluctuate between 0 and 1,
+ // which would thrash the layer cache
+ if (mStagingDisplayList) {
+ for (auto&& child : mStagingDisplayList->getChildren()) {
+ child->renderNode->incParentRefCount();
+ }
+ }
+ deleteDisplayList(observer);
+ mDisplayList = mStagingDisplayList;
+ mStagingDisplayList = nullptr;
+ if (mDisplayList) {
+ for (auto& iter : mDisplayList->getFunctors()) {
+ (*iter.functor)(DrawGlInfo::kModeSync, nullptr);
+ }
+ for (size_t i = 0; i < mDisplayList->getPushStagingFunctors().size(); i++) {
+ (*mDisplayList->getPushStagingFunctors()[i])();
}
+ }
+}
+
+void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) {
+ if (mNeedsDisplayListSync) {
+ mNeedsDisplayListSync = false;
// Damage with the old display list first then the new one to catch any
// changes in isRenderable or, in the future, bounds
damageSelf(info);
- deleteDisplayListData();
- // TODO: Remove this caches stuff
- if (mStagingDisplayListData && mStagingDisplayListData->functors.size()) {
- Caches::getInstance().registerFunctors(mStagingDisplayListData->functors.size());
- }
- mDisplayListData = mStagingDisplayListData;
- mStagingDisplayListData = nullptr;
- if (mDisplayListData) {
- for (size_t i = 0; i < mDisplayListData->functors.size(); i++) {
- (*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr);
- }
- }
+ syncDisplayList(info.observer);
damageSelf(info);
}
}
-void RenderNode::deleteDisplayListData() {
- if (mDisplayListData) {
- for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
- mDisplayListData->children()[i]->mRenderNode->decParentRefCount();
- }
- if (mDisplayListData->functors.size()) {
- Caches::getInstance().unregisterFunctors(mDisplayListData->functors.size());
+void RenderNode::deleteDisplayList(TreeObserver* observer) {
+ if (mDisplayList) {
+ for (auto&& child : mDisplayList->getChildren()) {
+ child->renderNode->decParentRefCount(observer);
}
}
- delete mDisplayListData;
- mDisplayListData = nullptr;
+ delete mDisplayList;
+ mDisplayList = nullptr;
}
-void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayListData* subtree) {
+void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree) {
if (subtree) {
TextureCache& cache = Caches::getInstance().textureCache;
- info.out.hasFunctors |= subtree->functors.size();
- for (size_t i = 0; info.prepareTextures && i < subtree->bitmapResources.size(); i++) {
- info.prepareTextures = cache.prefetchAndMarkInUse(
- info.canvasContext, subtree->bitmapResources[i]);
+ info.out.hasFunctors |= subtree->getFunctors().size();
+ for (auto&& bitmapResource : subtree->getBitmapResources()) {
+ void* ownerToken = &info.canvasContext;
+ info.prepareTextures = cache.prefetchAndMarkInUse(ownerToken, bitmapResource);
}
- for (size_t i = 0; i < subtree->children().size(); i++) {
- DrawRenderNodeOp* op = subtree->children()[i];
- RenderNode* childNode = op->mRenderNode;
- info.damageAccumulator->pushTransform(&op->mTransformFromParent);
+ for (auto&& op : subtree->getChildren()) {
+ RenderNode* childNode = op->renderNode;
+#if HWUI_NEW_OPS
+ info.damageAccumulator->pushTransform(&op->localMatrix);
+ bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip;
+#else
+ info.damageAccumulator->pushTransform(&op->localMatrix);
bool childFunctorsNeedLayer = functorsNeedLayer
// Recorded with non-rect clip, or canvas-rotated by parent
|| op->mRecordedWithPotentialStencilClip;
+#endif
childNode->prepareTreeImpl(info, childFunctorsNeedLayer);
info.damageAccumulator->popTransform();
}
}
}
-void RenderNode::destroyHardwareResources() {
+void RenderNode::destroyHardwareResources(TreeObserver* observer) {
if (mLayer) {
- LayerRenderer::destroyLayer(mLayer);
+ destroyLayer(mLayer);
mLayer = nullptr;
}
- if (mDisplayListData) {
- for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
- mDisplayListData->children()[i]->mRenderNode->destroyHardwareResources();
+ if (mDisplayList) {
+ for (auto&& child : mDisplayList->getChildren()) {
+ child->renderNode->destroyHardwareResources(observer);
}
- if (mNeedsDisplayListDataSync) {
+ if (mNeedsDisplayListSync) {
// Next prepare tree we are going to push a new display list, so we can
// drop our current one now
- deleteDisplayListData();
+ deleteDisplayList(observer);
}
}
}
-void RenderNode::decParentRefCount() {
+void RenderNode::decParentRefCount(TreeObserver* observer) {
LOG_ALWAYS_FATAL_IF(!mParentCount, "already 0!");
mParentCount--;
if (!mParentCount) {
+ if (observer) {
+ observer->onMaybeRemovedFromTree(this);
+ }
// If a child of ours is being attached to our parent then this will incorrectly
// destroy its hardware resources. However, this situation is highly unlikely
// and the failure is "just" that the layer is re-created, so this should
// be safe enough
- destroyHardwareResources();
+ destroyHardwareResources(observer);
}
}
@@ -431,7 +608,7 @@ void RenderNode::setViewProperties(OpenGLRenderer& renderer, T& handler) {
layerBounds.left, layerBounds.top,
layerBounds.right, layerBounds.bottom,
(int) (properties().getAlpha() * 255),
- SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kClipToLayer_SaveFlag);
+ SaveFlags::HasAlphaLayer | SaveFlags::ClipToLayer);
handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
}
@@ -520,46 +697,43 @@ void RenderNode::computeOrdering() {
// TODO: create temporary DDLOp and call computeOrderingImpl on top DisplayList so that
// transform properties are applied correctly to top level children
- if (mDisplayListData == nullptr) return;
- for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
- DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
- childOp->mRenderNode->computeOrderingImpl(childOp,
- properties().getOutline().getPath(), &mProjectedNodes, &mat4::identity());
+ if (mDisplayList == nullptr) return;
+ for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) {
+ renderNodeOp_t* childOp = mDisplayList->getChildren()[i];
+ childOp->renderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity());
}
}
void RenderNode::computeOrderingImpl(
- DrawRenderNodeOp* opState,
- const SkPath* outlineOfProjectionSurface,
- Vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
+ renderNodeOp_t* opState,
+ std::vector<renderNodeOp_t*>* compositedChildrenOfProjectionSurface,
const mat4* transformFromProjectionSurface) {
mProjectedNodes.clear();
- if (mDisplayListData == nullptr || mDisplayListData->isEmpty()) return;
+ if (mDisplayList == nullptr || mDisplayList->isEmpty()) return;
// TODO: should avoid this calculation in most cases
// TODO: just calculate single matrix, down to all leaf composited elements
Matrix4 localTransformFromProjectionSurface(*transformFromProjectionSurface);
- localTransformFromProjectionSurface.multiply(opState->mTransformFromParent);
+ localTransformFromProjectionSurface.multiply(opState->localMatrix);
if (properties().getProjectBackwards()) {
// composited projectee, flag for out of order draw, save matrix, and store in proj surface
- opState->mSkipInOrderDraw = true;
- opState->mTransformFromCompositingAncestor.load(localTransformFromProjectionSurface);
- compositedChildrenOfProjectionSurface->add(opState);
+ opState->skipInOrderDraw = true;
+ opState->transformFromCompositingAncestor = localTransformFromProjectionSurface;
+ compositedChildrenOfProjectionSurface->push_back(opState);
} else {
// standard in order draw
- opState->mSkipInOrderDraw = false;
+ opState->skipInOrderDraw = false;
}
- if (mDisplayListData->children().size() > 0) {
- const bool isProjectionReceiver = mDisplayListData->projectionReceiveIndex >= 0;
+ if (mDisplayList->getChildren().size() > 0) {
+ const bool isProjectionReceiver = mDisplayList->projectionReceiveIndex >= 0;
bool haveAppliedPropertiesToProjection = false;
- for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
- DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
- RenderNode* child = childOp->mRenderNode;
+ for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) {
+ renderNodeOp_t* childOp = mDisplayList->getChildren()[i];
+ RenderNode* child = childOp->renderNode;
- const SkPath* projectionOutline = nullptr;
- Vector<DrawRenderNodeOp*>* projectionChildren = nullptr;
+ std::vector<renderNodeOp_t*>* projectionChildren = nullptr;
const mat4* projectionTransform = nullptr;
if (isProjectionReceiver && !child->properties().getProjectBackwards()) {
// if receiving projections, collect projecting descendant
@@ -567,7 +741,6 @@ void RenderNode::computeOrderingImpl(
// Note that if a direct descendant is projecting backwards, we pass its
// grandparent projection collection, since it shouldn't project onto its
// parent, where it will already be drawing.
- projectionOutline = properties().getOutline().getPath();
projectionChildren = &mProjectedNodes;
projectionTransform = &mat4::identity();
} else {
@@ -575,12 +748,10 @@ void RenderNode::computeOrderingImpl(
applyViewPropertyTransforms(localTransformFromProjectionSurface);
haveAppliedPropertiesToProjection = true;
}
- projectionOutline = outlineOfProjectionSurface;
projectionChildren = compositedChildrenOfProjectionSurface;
projectionTransform = &localTransformFromProjectionSurface;
}
- child->computeOrderingImpl(childOp,
- projectionOutline, projectionChildren, projectionTransform);
+ child->computeOrderingImpl(childOp, projectionChildren, projectionTransform);
}
}
}
@@ -640,26 +811,28 @@ void RenderNode::replay(ReplayStateStruct& replayStruct, const int level) {
issueOperations<ReplayOperationHandler>(replayStruct.mRenderer, handler);
}
-void RenderNode::buildZSortedChildList(const DisplayListData::Chunk& chunk,
- Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes) {
+void RenderNode::buildZSortedChildList(const DisplayList::Chunk& chunk,
+ std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes) {
+#if !HWUI_NEW_OPS
if (chunk.beginChildIndex == chunk.endChildIndex) return;
for (unsigned int i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) {
- DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
- RenderNode* child = childOp->mRenderNode;
+ DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i];
+ RenderNode* child = childOp->renderNode;
float childZ = child->properties().getZ();
if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
- zTranslatedNodes.add(ZDrawRenderNodeOpPair(childZ, childOp));
- childOp->mSkipInOrderDraw = true;
+ zTranslatedNodes.push_back(ZDrawRenderNodeOpPair(childZ, childOp));
+ childOp->skipInOrderDraw = true;
} else if (!child->properties().getProjectBackwards()) {
// regular, in order drawing DisplayList
- childOp->mSkipInOrderDraw = false;
+ childOp->skipInOrderDraw = false;
}
}
// Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order)
std::stable_sort(zTranslatedNodes.begin(), zTranslatedNodes.end());
+#endif
}
template <class T>
@@ -695,7 +868,7 @@ void RenderNode::issueDrawShadowOperation(const Matrix4& transformFromParent, T&
if (revealClipPath) {
frameAllocatedPath = handler.allocPathForFrame();
- Op(*outlinePath, *revealClipPath, kIntersect_PathOp, frameAllocatedPath);
+ Op(*outlinePath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath);
outlinePath = frameAllocatedPath;
}
@@ -711,7 +884,7 @@ void RenderNode::issueDrawShadowOperation(const Matrix4& transformFromParent, T&
clipBoundsPath.addRect(clipBounds.left, clipBounds.top,
clipBounds.right, clipBounds.bottom);
- Op(*outlinePath, clipBoundsPath, kIntersect_PathOp, frameAllocatedPath);
+ Op(*outlinePath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath);
outlinePath = frameAllocatedPath;
}
@@ -724,20 +897,20 @@ void RenderNode::issueDrawShadowOperation(const Matrix4& transformFromParent, T&
template <class T>
void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode,
- const Matrix4& initialTransform, const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
+ const Matrix4& initialTransform, const std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
OpenGLRenderer& renderer, T& handler) {
const int size = zTranslatedNodes.size();
if (size == 0
- || (mode == kNegativeZChildren && zTranslatedNodes[0].key > 0.0f)
- || (mode == kPositiveZChildren && zTranslatedNodes[size - 1].key < 0.0f)) {
+ || (mode == ChildrenSelectMode::NegativeZChildren && zTranslatedNodes[0].key > 0.0f)
+ || (mode == ChildrenSelectMode::PositiveZChildren && zTranslatedNodes[size - 1].key < 0.0f)) {
// no 3d children to draw
return;
}
// Apply the base transform of the parent of the 3d children. This isolates
// 3d children of the current chunk from transformations made in previous chunks.
- int rootRestoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
- renderer.setMatrix(initialTransform);
+ int rootRestoreTo = renderer.save(SaveFlags::Matrix);
+ renderer.setGlobalMatrix(initialTransform);
/**
* Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
@@ -748,7 +921,7 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode,
*/
const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes);
size_t drawIndex, shadowIndex, endIndex;
- if (mode == kNegativeZChildren) {
+ if (mode == ChildrenSelectMode::NegativeZChildren) {
drawIndex = 0;
endIndex = nonNegativeIndex;
shadowIndex = endIndex; // draw no shadows
@@ -765,12 +938,12 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode,
while (shadowIndex < endIndex || drawIndex < endIndex) {
if (shadowIndex < endIndex) {
DrawRenderNodeOp* casterOp = zTranslatedNodes[shadowIndex].value;
- RenderNode* caster = casterOp->mRenderNode;
+ RenderNode* caster = casterOp->renderNode;
const float casterZ = zTranslatedNodes[shadowIndex].key;
// attempt to render the shadow if the caster about to be drawn is its caster,
// OR if its caster's Z value is similar to the previous potential caster
if (shadowIndex == drawIndex || casterZ - lastCasterZ < SHADOW_DELTA) {
- caster->issueDrawShadowOperation(casterOp->mTransformFromParent, handler);
+ caster->issueDrawShadowOperation(casterOp->localMatrix, handler);
lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
shadowIndex++;
@@ -780,14 +953,14 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode,
// only the actual child DL draw needs to be in save/restore,
// since it modifies the renderer's matrix
- int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
+ int restoreTo = renderer.save(SaveFlags::Matrix);
DrawRenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
- renderer.concatMatrix(childOp->mTransformFromParent);
- childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
+ renderer.concatMatrix(childOp->localMatrix);
+ childOp->skipInOrderDraw = false; // this is horrible, I'm so sorry everyone
handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
- childOp->mSkipInOrderDraw = true;
+ childOp->skipInOrderDraw = true;
renderer.restoreToCount(restoreTo);
drawIndex++;
@@ -802,31 +975,36 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T&
int restoreTo = renderer.getSaveCount();
LinearAllocator& alloc = handler.allocator();
- handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
+ handler(new (alloc) SaveOp(SaveFlags::MatrixClip),
PROPERTY_SAVECOUNT, properties().getClipToBounds());
// Transform renderer to match background we're projecting onto
// (by offsetting canvas by translationX/Y of background rendernode, since only those are set)
const DisplayListOp* op =
- (mDisplayListData->displayListOps[mDisplayListData->projectionReceiveIndex]);
+#if HWUI_NEW_OPS
+ nullptr;
+ LOG_ALWAYS_FATAL("unsupported");
+#else
+ (mDisplayList->getOps()[mDisplayList->projectionReceiveIndex]);
+#endif
const DrawRenderNodeOp* backgroundOp = reinterpret_cast<const DrawRenderNodeOp*>(op);
- const RenderProperties& backgroundProps = backgroundOp->mRenderNode->properties();
+ const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
renderer.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
- // If the projection reciever has an outline, we mask projected content to it
+ // If the projection receiver has an outline, we mask projected content to it
// (which we know, apriori, are all tessellated paths)
renderer.setProjectionPathMask(alloc, projectionReceiverOutline);
// draw projected nodes
for (size_t i = 0; i < mProjectedNodes.size(); i++) {
- DrawRenderNodeOp* childOp = mProjectedNodes[i];
+ renderNodeOp_t* childOp = mProjectedNodes[i];
// matrix save, concat, and restore can be done safely without allocating operations
- int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
- renderer.concatMatrix(childOp->mTransformFromCompositingAncestor);
- childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
+ int restoreTo = renderer.save(SaveFlags::Matrix);
+ renderer.concatMatrix(childOp->transformFromCompositingAncestor);
+ childOp->skipInOrderDraw = false; // this is horrible, I'm so sorry everyone
handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
- childOp->mSkipInOrderDraw = true;
+ childOp->skipInOrderDraw = true;
renderer.restoreToCount(restoreTo);
}
@@ -845,13 +1023,17 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T&
*/
template <class T>
void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
- if (mDisplayListData->isEmpty()) {
+ if (mDisplayList->isEmpty()) {
DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", handler.level() * 2, "",
this, getName());
return;
}
+#if HWUI_NEW_OPS
+ const bool drawLayer = false;
+#else
const bool drawLayer = (mLayer && (&renderer != mLayer->renderer.get()));
+#endif
// If we are updating the contents of mLayer, we don't want to apply any of
// the RenderNode's properties to this issueOperations pass. Those will all
// be applied when the layer is drawn, aka when this is true.
@@ -879,16 +1061,19 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
LinearAllocator& alloc = handler.allocator();
int restoreTo = renderer.getSaveCount();
- handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
+ handler(new (alloc) SaveOp(SaveFlags::MatrixClip),
PROPERTY_SAVECOUNT, properties().getClipToBounds());
DISPLAY_LIST_LOGD("%*sSave %d %d", (handler.level() + 1) * 2, "",
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag, restoreTo);
+ SaveFlags::MatrixClip, restoreTo);
if (useViewProperties) {
setViewProperties<T>(renderer, handler);
}
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("legacy op traversal not supported");
+#else
bool quickRejected = properties().getClipToBounds()
&& renderer.quickRejectConservative(0, 0, properties().getWidth(), properties().getHeight());
if (!quickRejected) {
@@ -896,39 +1081,39 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
renderer.setBaseTransform(initialTransform);
if (drawLayer) {
- handler(new (alloc) DrawLayerOp(mLayer, 0, 0),
+ handler(new (alloc) DrawLayerOp(mLayer),
renderer.getSaveCount() - 1, properties().getClipToBounds());
} else {
const int saveCountOffset = renderer.getSaveCount() - 1;
- const int projectionReceiveIndex = mDisplayListData->projectionReceiveIndex;
- for (size_t chunkIndex = 0; chunkIndex < mDisplayListData->getChunks().size(); chunkIndex++) {
- const DisplayListData::Chunk& chunk = mDisplayListData->getChunks()[chunkIndex];
+ const int projectionReceiveIndex = mDisplayList->projectionReceiveIndex;
+ for (size_t chunkIndex = 0; chunkIndex < mDisplayList->getChunks().size(); chunkIndex++) {
+ const DisplayList::Chunk& chunk = mDisplayList->getChunks()[chunkIndex];
- Vector<ZDrawRenderNodeOpPair> zTranslatedNodes;
+ std::vector<ZDrawRenderNodeOpPair> zTranslatedNodes;
buildZSortedChildList(chunk, zTranslatedNodes);
- issueOperationsOf3dChildren(kNegativeZChildren,
+ issueOperationsOf3dChildren(ChildrenSelectMode::NegativeZChildren,
initialTransform, zTranslatedNodes, renderer, handler);
-
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
- DisplayListOp *op = mDisplayListData->displayListOps[opIndex];
+ DisplayListOp *op = mDisplayList->getOps()[opIndex];
#if DEBUG_DISPLAY_LIST
op->output(handler.level() + 1);
#endif
handler(op, saveCountOffset, properties().getClipToBounds());
- if (CC_UNLIKELY(!mProjectedNodes.isEmpty() && projectionReceiveIndex >= 0 &&
+ if (CC_UNLIKELY(!mProjectedNodes.empty() && projectionReceiveIndex >= 0 &&
opIndex == static_cast<size_t>(projectionReceiveIndex))) {
issueOperationsOfProjectedChildren(renderer, handler);
}
}
- issueOperationsOf3dChildren(kPositiveZChildren,
+ issueOperationsOf3dChildren(ChildrenSelectMode::PositiveZChildren,
initialTransform, zTranslatedNodes, renderer, handler);
}
}
}
+#endif
DISPLAY_LIST_LOGD("%*sRestoreToCount %d", (handler.level() + 1) * 2, "", restoreTo);
handler(new (alloc) RestoreToCountOp(restoreTo),
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 025a4a416e4c..acdc3d835b4d 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -16,17 +16,12 @@
#ifndef RENDERNODE_H
#define RENDERNODE_H
-#ifndef LOG_TAG
- #define LOG_TAG "OpenGLRenderer"
-#endif
-
#include <SkCamera.h>
#include <SkMatrix.h>
#include <utils/LinearAllocator.h>
#include <utils/RefBase.h>
#include <utils/String8.h>
-#include <utils/Vector.h>
#include <cutils/compiler.h>
@@ -34,10 +29,12 @@
#include "AnimatorManager.h"
#include "Debug.h"
-#include "Matrix.h"
#include "DisplayList.h"
+#include "Matrix.h"
#include "RenderProperties.h"
+#include <vector>
+
class SkBitmap;
class SkPaint;
class SkPath;
@@ -46,33 +43,52 @@ class SkRegion;
namespace android {
namespace uirenderer {
-class DisplayListOp;
+class CanvasState;
class DisplayListCanvas;
+class DisplayListOp;
class OpenGLRenderer;
class Rect;
-class Layer;
class SkiaShader;
+#if HWUI_NEW_OPS
+class FrameBuilder;
+class OffscreenBuffer;
+struct RenderNodeOp;
+typedef OffscreenBuffer layer_t;
+typedef RenderNodeOp renderNodeOp_t;
+#else
+class Layer;
+typedef Layer layer_t;
+typedef DrawRenderNodeOp renderNodeOp_t;
+#endif
+
class ClipRectOp;
+class DrawRenderNodeOp;
class SaveLayerOp;
class SaveOp;
class RestoreToCountOp;
-class DrawRenderNodeOp;
class TreeInfo;
+class TreeObserver;
+
+namespace proto {
+class RenderNode;
+}
/**
* Primary class for storing recorded canvas commands, as well as per-View/ViewGroup display properties.
*
* Recording of canvas commands is somewhat similar to SkPicture, except the canvas-recording
- * functionality is split between DisplayListCanvas (which manages the recording), DisplayListData
+ * functionality is split between DisplayListCanvas (which manages the recording), DisplayList
* (which holds the actual data), and DisplayList (which holds properties and performs playback onto
* a renderer).
*
- * Note that DisplayListData is swapped out from beneath an individual DisplayList when a view's
- * recorded stream of canvas operations is refreshed. The DisplayList (and its properties) stay
+ * Note that DisplayList is swapped out from beneath an individual RenderNode when a view's
+ * recorded stream of canvas operations is refreshed. The RenderNode (and its properties) stay
* attached.
*/
class RenderNode : public VirtualLightRefBase {
+friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties
+friend class FrameBuilder;
public:
enum DirtyPropertyMask {
GENERIC = 1 << 1,
@@ -99,25 +115,29 @@ public:
kReplayFlag_ClipChildren = 0x1
};
- static void outputLogBuffer(int fd);
void debugDumpLayers(const char* prefix);
- ANDROID_API void setStagingDisplayList(DisplayListData* newData);
+ ANDROID_API void setStagingDisplayList(DisplayList* newData, TreeObserver* observer);
void computeOrdering();
void defer(DeferStateStruct& deferStruct, const int level);
void replay(ReplayStateStruct& replayStruct, const int level);
+#if HWUI_NEW_OPS
+ ANDROID_API void output(uint32_t level = 0, const char* label = "Root");
+#else
ANDROID_API void output(uint32_t level = 1);
+#endif
ANDROID_API int getDebugSize();
+ void copyTo(proto::RenderNode* node);
bool isRenderable() const {
- return mDisplayListData && !mDisplayListData->isEmpty();
+ return mDisplayList && !mDisplayList->isEmpty();
}
bool hasProjectionReceiver() const {
- return mDisplayListData && mDisplayListData->projectionReceiveIndex >= 0;
+ return mDisplayList && mDisplayList->projectionReceiveIndex >= 0;
}
const char* getName() const {
@@ -135,6 +155,14 @@ public:
}
}
+ VirtualLightRefBase* getUserContext() const {
+ return mUserContext.get();
+ }
+
+ void setUserContext(VirtualLightRefBase* context) {
+ mUserContext = context;
+ }
+
bool isPropertyFieldDirty(DirtyPropertyMask field) const {
return mDirtyPropertyFields & field;
}
@@ -159,56 +187,97 @@ public:
return mStagingProperties;
}
- int getWidth() {
+ int getWidth() const {
return properties().getWidth();
}
- int getHeight() {
+ int getHeight() const {
return properties().getHeight();
}
ANDROID_API virtual void prepareTree(TreeInfo& info);
- void destroyHardwareResources();
+ void destroyHardwareResources(TreeObserver* observer);
// UI thread only!
ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
+ void removeAnimator(const sp<BaseRenderNodeAnimator>& animator);
+
+ // This can only happen during pushStaging()
+ void onAnimatorTargetChanged(BaseRenderNodeAnimator* animator) {
+ mAnimatorManager.onAnimatorTargetChanged(animator);
+ }
AnimatorManager& animators() { return mAnimatorManager; }
void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const;
+ bool nothingToDraw() const {
+ const Outline& outline = properties().getOutline();
+ return mDisplayList == nullptr
+ || properties().getAlpha() <= 0
+ || (outline.getShouldClip() && outline.isEmpty())
+ || properties().getScaleX() == 0
+ || properties().getScaleY() == 0;
+ }
+
+ const DisplayList* getDisplayList() const {
+ return mDisplayList;
+ }
+#if HWUI_NEW_OPS
+ OffscreenBuffer* getLayer() const { return mLayer; }
+ OffscreenBuffer** getLayerHandle() { return &mLayer; } // ugh...
+#endif
+
+ class ANDROID_API PositionListener {
+ public:
+ virtual ~PositionListener() {}
+ virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) = 0;
+ };
+
+ // Note this is not thread safe, this needs to be called
+ // before the RenderNode is used for drawing.
+ // RenderNode takes ownership of the pointer
+ ANDROID_API void setPositionListener(PositionListener* listener) {
+ mPositionListener.reset(listener);
+ }
+
+ // This is only modified in MODE_FULL, so it can be safely accessed
+ // on the UI thread.
+ ANDROID_API bool hasParents() {
+ return mParentCount;
+ }
+
private:
typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
- static size_t findNonNegativeIndex(const Vector<ZDrawRenderNodeOpPair>& nodes) {
+ static size_t findNonNegativeIndex(const std::vector<ZDrawRenderNodeOpPair>& nodes) {
for (size_t i = 0; i < nodes.size(); i++) {
if (nodes[i].key >= 0.0f) return i;
}
return nodes.size();
}
- enum ChildrenSelectMode {
- kNegativeZChildren,
- kPositiveZChildren
+ enum class ChildrenSelectMode {
+ NegativeZChildren,
+ PositiveZChildren
};
- void computeOrderingImpl(DrawRenderNodeOp* opState,
- const SkPath* outlineOfProjectionSurface,
- Vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
+ void computeOrderingImpl(renderNodeOp_t* opState,
+ std::vector<renderNodeOp_t*>* compositedChildrenOfProjectionSurface,
const mat4* transformFromProjectionSurface);
template <class T>
inline void setViewProperties(OpenGLRenderer& renderer, T& handler);
- void buildZSortedChildList(const DisplayListData::Chunk& chunk,
- Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes);
+ void buildZSortedChildList(const DisplayList::Chunk& chunk,
+ std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes);
template<class T>
inline void issueDrawShadowOperation(const Matrix4& transformFromParent, T& handler);
template <class T>
inline void issueOperationsOf3dChildren(ChildrenSelectMode mode,
- const Matrix4& initialTransform, const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
+ const Matrix4& initialTransform, const std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
OpenGLRenderer& renderer, T& handler);
template <class T>
@@ -235,51 +304,60 @@ private:
const char* mText;
};
+
+ void syncProperties();
+ void syncDisplayList(TreeObserver* observer);
+
void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer);
void pushStagingPropertiesChanges(TreeInfo& info);
void pushStagingDisplayListChanges(TreeInfo& info);
- void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayListData* subtree);
+ void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree);
+#if !HWUI_NEW_OPS
void applyLayerPropertiesToLayer(TreeInfo& info);
+#endif
void prepareLayer(TreeInfo& info, uint32_t dirtyMask);
void pushLayerUpdate(TreeInfo& info);
- void deleteDisplayListData();
+ void deleteDisplayList(TreeObserver* observer);
void damageSelf(TreeInfo& info);
void incParentRefCount() { mParentCount++; }
- void decParentRefCount();
+ void decParentRefCount(TreeObserver* observer);
String8 mName;
+ sp<VirtualLightRefBase> mUserContext;
uint32_t mDirtyPropertyFields;
RenderProperties mProperties;
RenderProperties mStagingProperties;
- bool mNeedsDisplayListDataSync;
- // WARNING: Do not delete this directly, you must go through deleteDisplayListData()!
- DisplayListData* mDisplayListData;
- DisplayListData* mStagingDisplayListData;
+ bool mNeedsDisplayListSync;
+ // WARNING: Do not delete this directly, you must go through deleteDisplayList()!
+ DisplayList* mDisplayList;
+ DisplayList* mStagingDisplayList;
friend class AnimatorManager;
AnimatorManager mAnimatorManager;
// Owned by RT. Lifecycle is managed by prepareTree(), with the exception
// being in ~RenderNode() which may happen on any thread.
- Layer* mLayer;
+ layer_t* mLayer = nullptr;
/**
* Draw time state - these properties are only set and used during rendering
*/
// for projection surfaces, contains a list of all children items
- Vector<DrawRenderNodeOp*> mProjectedNodes;
+ std::vector<renderNodeOp_t*> mProjectedNodes;
// How many references our parent(s) have to us. Typically this should alternate
// between 2 and 1 (when a staging push happens we inc first then dec)
// When this hits 0 we are no longer in the tree, so any hardware resources
// (specifically Layers) should be released.
// This is *NOT* thread-safe, and should therefore only be tracking
- // mDisplayListData, not mStagingDisplayListData.
+ // mDisplayList, not mStagingDisplayList.
uint32_t mParentCount;
+
+ std::unique_ptr<PositionListener> mPositionListener;
}; // class RenderNode
} /* namespace uirenderer */
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index 4f6ef4ef9e3d..5ebf5458da18 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -14,13 +14,10 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include "RenderProperties.h"
#include <utils/Trace.h>
-#include <SkCanvas.h>
#include <SkColorFilter.h>
#include <SkMatrix.h>
#include <SkPath.h>
@@ -28,6 +25,7 @@
#include "Matrix.h"
#include "OpenGLRenderer.h"
+#include "hwui/Canvas.h"
#include "utils/MathUtils.h"
namespace android {
@@ -54,11 +52,8 @@ bool LayerProperties::setColorFilter(SkColorFilter* filter) {
bool LayerProperties::setFromPaint(const SkPaint* paint) {
bool changed = false;
- SkXfermode::Mode mode;
- int alpha;
- OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode);
- changed |= setAlpha(static_cast<uint8_t>(alpha));
- changed |= setXferMode(mode);
+ changed |= setAlpha(static_cast<uint8_t>(PaintUtils::getAlphaDirect(paint)));
+ changed |= setXferMode(PaintUtils::getXfermodeDirect(paint));
changed |= setColorFilter(paint ? paint->getColorFilter() : nullptr);
return changed;
}
@@ -72,23 +67,6 @@ LayerProperties& LayerProperties::operator=(const LayerProperties& other) {
return *this;
}
-RenderProperties::PrimitiveFields::PrimitiveFields()
- : mClippingFlags(CLIP_TO_BOUNDS)
- , mProjectBackwards(false)
- , mProjectionReceiver(false)
- , mAlpha(1)
- , mHasOverlappingRendering(true)
- , mElevation(0)
- , mTranslationX(0), mTranslationY(0), mTranslationZ(0)
- , mRotation(0), mRotationX(0), mRotationY(0)
- , mScaleX(1), mScaleY(1)
- , mPivotX(0), mPivotY(0)
- , mLeft(0), mTop(0), mRight(0), mBottom(0)
- , mWidth(0), mHeight(0)
- , mPivotExplicitlySet(false)
- , mMatrixOrPivotDirty(false) {
-}
-
RenderProperties::ComputedFields::ComputedFields()
: mTransformMatrix(nullptr) {
}
@@ -124,22 +102,23 @@ RenderProperties& RenderProperties::operator=(const RenderProperties& other) {
void RenderProperties::debugOutputProperties(const int level) const {
if (mPrimitiveFields.mLeft != 0 || mPrimitiveFields.mTop != 0) {
- ALOGD("%*sTranslate (left, top) %d, %d", level * 2, "", mPrimitiveFields.mLeft, mPrimitiveFields.mTop);
+ ALOGD("%*s(Translate (left, top) %d, %d)", level * 2, "",
+ mPrimitiveFields.mLeft, mPrimitiveFields.mTop);
}
if (mStaticMatrix) {
- ALOGD("%*sConcatMatrix (static) %p: " SK_MATRIX_STRING,
+ ALOGD("%*s(ConcatMatrix (static) %p: " SK_MATRIX_STRING ")",
level * 2, "", mStaticMatrix, SK_MATRIX_ARGS(mStaticMatrix));
}
if (mAnimationMatrix) {
- ALOGD("%*sConcatMatrix (animation) %p: " SK_MATRIX_STRING,
+ ALOGD("%*s(ConcatMatrix (animation) %p: " SK_MATRIX_STRING ")",
level * 2, "", mAnimationMatrix, SK_MATRIX_ARGS(mAnimationMatrix));
}
if (hasTransformMatrix()) {
if (isTransformTranslateOnly()) {
- ALOGD("%*sTranslate %.2f, %.2f, %.2f",
+ ALOGD("%*s(Translate %.2f, %.2f, %.2f)",
level * 2, "", getTranslationX(), getTranslationY(), getZ());
} else {
- ALOGD("%*sConcatMatrix %p: " SK_MATRIX_STRING,
+ ALOGD("%*s(ConcatMatrix %p: " SK_MATRIX_STRING ")",
level * 2, "", mComputedFields.mTransformMatrix, SK_MATRIX_ARGS(mComputedFields.mTransformMatrix));
}
}
@@ -154,7 +133,7 @@ void RenderProperties::debugOutputProperties(const int level) const {
if (CC_LIKELY(isLayer || !getHasOverlappingRendering())) {
// simply scale rendering content's alpha
- ALOGD("%*sScaleAlpha %.2f", level * 2, "", mPrimitiveFields.mAlpha);
+ ALOGD("%*s(ScaleAlpha %.2f)", level * 2, "", mPrimitiveFields.mAlpha);
} else {
// savelayeralpha to create an offscreen buffer to apply alpha
Rect layerBounds(0, 0, getWidth(), getHeight());
@@ -162,21 +141,37 @@ void RenderProperties::debugOutputProperties(const int level) const {
getClippingRectForFlags(clipFlags, &layerBounds);
clipFlags = 0; // all clipping done by savelayer
}
- ALOGD("%*sSaveLayerAlpha %d, %d, %d, %d, %d, 0x%x", level * 2, "",
+ ALOGD("%*s(SaveLayerAlpha %d, %d, %d, %d, %d, 0x%x)", level * 2, "",
(int)layerBounds.left, (int)layerBounds.top,
(int)layerBounds.right, (int)layerBounds.bottom,
(int)(mPrimitiveFields.mAlpha * 255),
- SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kClipToLayer_SaveFlag);
+ SaveFlags::HasAlphaLayer | SaveFlags::ClipToLayer);
}
-
-
}
+
if (clipFlags) {
Rect clipRect;
getClippingRectForFlags(clipFlags, &clipRect);
- ALOGD("%*sClipRect %d, %d, %d, %d", level * 2, "",
+ ALOGD("%*s(ClipRect %d, %d, %d, %d)", level * 2, "",
(int)clipRect.left, (int)clipRect.top, (int)clipRect.right, (int)clipRect.bottom);
}
+
+ if (getRevealClip().willClip()) {
+ Rect bounds;
+ getRevealClip().getBounds(&bounds);
+ ALOGD("%*s(Clip to reveal clip with bounds %.2f %.2f %.2f %.2f)", level * 2, "",
+ RECT_ARGS(bounds));
+ }
+
+ auto& outline = mPrimitiveFields.mOutline;
+ if (outline.getShouldClip()) {
+ if (outline.isEmpty()) {
+ ALOGD("%*s(Clip to empty outline)", level * 2, "");
+ } else if (outline.willClip()) {
+ ALOGD("%*s(Clip to outline with bounds %.2f %.2f %.2f %.2f)", level * 2, "",
+ RECT_ARGS(outline.getBounds()));
+ }
+ }
}
void RenderProperties::updateMatrix() {
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 11abd701b9d8..395279806adb 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -16,23 +16,24 @@
#ifndef RENDERNODEPROPERTIES_H
#define RENDERNODEPROPERTIES_H
-#include <algorithm>
-#include <stddef.h>
-#include <vector>
-#include <cutils/compiler.h>
-#include <androidfw/ResourceTypes.h>
-#include <utils/Log.h>
+#include "Caches.h"
+#include "DeviceInfo.h"
+#include "Rect.h"
+#include "RevealClip.h"
+#include "Outline.h"
+#include "utils/MathUtils.h"
#include <SkCamera.h>
#include <SkMatrix.h>
#include <SkRegion.h>
#include <SkXfermode.h>
-#include "Caches.h"
-#include "Rect.h"
-#include "RevealClip.h"
-#include "Outline.h"
-#include "utils/MathUtils.h"
+#include <algorithm>
+#include <stddef.h>
+#include <vector>
+#include <cutils/compiler.h>
+#include <androidfw/ResourceTypes.h>
+#include <utils/Log.h>
class SkBitmap;
class SkColorFilter;
@@ -203,8 +204,8 @@ public:
return RP_SET(mPrimitiveFields.mProjectBackwards, shouldProject);
}
- bool setProjectionReceiver(bool shouldRecieve) {
- return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldRecieve);
+ bool setProjectionReceiver(bool shouldReceive) {
+ return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldReceive);
}
bool isProjectionReceiver() const {
@@ -417,7 +418,7 @@ public:
return false;
}
- float getLeft() const {
+ int getLeft() const {
return mPrimitiveFields.mLeft;
}
@@ -432,7 +433,7 @@ public:
return false;
}
- float getTop() const {
+ int getTop() const {
return mPrimitiveFields.mTop;
}
@@ -447,7 +448,7 @@ public:
return false;
}
- float getRight() const {
+ int getRight() const {
return mPrimitiveFields.mRight;
}
@@ -462,7 +463,7 @@ public:
return false;
}
- float getBottom() const {
+ int getBottom() const {
return mPrimitiveFields.mBottom;
}
@@ -541,11 +542,15 @@ public:
return mPrimitiveFields.mClippingFlags & CLIP_TO_BOUNDS;
}
+ const Rect& getClipBounds() const {
+ return mPrimitiveFields.mClipBounds;
+ }
+
void getClippingRectForFlags(uint32_t flags, Rect* outRect) const {
if (flags & CLIP_TO_BOUNDS) {
outRect->set(0, 0, getWidth(), getHeight());
if (flags & CLIP_TO_CLIP_BOUNDS) {
- outRect->intersect(mPrimitiveFields.mClipBounds);
+ outRect->doIntersect(mPrimitiveFields.mClipBounds);
}
} else {
outRect->set(mPrimitiveFields.mClipBounds);
@@ -603,11 +608,15 @@ public:
&& getOutline().getAlpha() != 0.0f;
}
+ bool fitsOnLayer() const {
+ const DeviceInfo* deviceInfo = DeviceInfo::get();
+ return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize()
+ && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize();
+ }
+
bool promotedToLayer() const {
- const int maxTextureSize = Caches::getInstance().maxTextureSize;
return mLayerProperties.mType == LayerType::None
- && mPrimitiveFields.mWidth <= maxTextureSize
- && mPrimitiveFields.mHeight <= maxTextureSize
+ && fitsOnLayer()
&& (mComputedFields.mNeedLayerForFunctors
|| (!MathUtils::isZero(mPrimitiveFields.mAlpha)
&& mPrimitiveFields.mAlpha < 1
@@ -621,25 +630,23 @@ public:
private:
// Rendering properties
struct PrimitiveFields {
- PrimitiveFields();
-
+ int mLeft = 0, mTop = 0, mRight = 0, mBottom = 0;
+ int mWidth = 0, mHeight = 0;
+ int mClippingFlags = CLIP_TO_BOUNDS;
+ float mAlpha = 1;
+ float mTranslationX = 0, mTranslationY = 0, mTranslationZ = 0;
+ float mElevation = 0;
+ float mRotation = 0, mRotationX = 0, mRotationY = 0;
+ float mScaleX = 1, mScaleY = 1;
+ float mPivotX = 0, mPivotY = 0;
+ bool mHasOverlappingRendering = false;
+ bool mPivotExplicitlySet = false;
+ bool mMatrixOrPivotDirty = false;
+ bool mProjectBackwards = false;
+ bool mProjectionReceiver = false;
+ Rect mClipBounds;
Outline mOutline;
RevealClip mRevealClip;
- int mClippingFlags;
- bool mProjectBackwards;
- bool mProjectionReceiver;
- float mAlpha;
- bool mHasOverlappingRendering;
- float mElevation;
- float mTranslationX, mTranslationY, mTranslationZ;
- float mRotation, mRotationX, mRotationY;
- float mScaleX, mScaleY;
- float mPivotX, mPivotY;
- int mLeft, mTop, mRight, mBottom;
- int mWidth, mHeight;
- bool mPivotExplicitlySet;
- bool mMatrixOrPivotDirty;
- Rect mClipBounds;
} mPrimitiveFields;
SkMatrix* mStaticMatrix;
diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp
index 75d81346d62a..b26e433cfa4c 100644
--- a/libs/hwui/ResourceCache.cpp
+++ b/libs/hwui/ResourceCache.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include "ResourceCache.h"
#include "Caches.h"
diff --git a/libs/hwui/RevealClip.h b/libs/hwui/RevealClip.h
index 0084a8edccfc..63821dddd369 100644
--- a/libs/hwui/RevealClip.h
+++ b/libs/hwui/RevealClip.h
@@ -51,7 +51,10 @@ public:
outBounds->set(mX - mRadius, mY - mRadius,
mX + mRadius, mY + mRadius);
}
+
float getRadius() const { return mRadius; }
+ float getX() const { return mX; }
+ float getY() const { return mY; }
const SkPath* getPath() const {
if (!mShouldClip) return nullptr;
diff --git a/libs/hwui/ShadowTessellator.cpp b/libs/hwui/ShadowTessellator.cpp
index 6c8665b50c53..e94a70a9f80d 100644
--- a/libs/hwui/ShadowTessellator.cpp
+++ b/libs/hwui/ShadowTessellator.cpp
@@ -14,13 +14,9 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-#define ATRACE_TAG ATRACE_TAG_VIEW
-
#include <math.h>
#include <utils/Log.h>
#include <utils/Trace.h>
-#include <utils/Vector.h>
#include <utils/MathUtils.h>
#include "AmbientShadow.h"
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 644a4f305a2e..ce67554645d1 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -14,18 +14,28 @@
* limitations under the License.
*/
-#include "Canvas.h"
+#include "CanvasProperty.h"
+#include "Layer.h"
+#include "RenderNode.h"
+#include "hwui/Canvas.h"
#include <SkCanvas.h>
#include <SkClipStack.h>
+#include <SkDrawable.h>
#include <SkDevice.h>
#include <SkDeque.h>
#include <SkDrawFilter.h>
#include <SkGraphics.h>
+#include <SkImage.h>
#include <SkShader.h>
#include <SkTArray.h>
+#include <SkTLazy.h>
#include <SkTemplates.h>
+#include "VectorDrawable.h"
+
+#include <memory>
+
namespace android {
// Holds an SkCanvas reference plus additional native data.
@@ -49,25 +59,41 @@ public:
return mCanvas.get();
}
+ virtual void resetRecording(int width, int height) override {
+ LOG_ALWAYS_FATAL("SkiaCanvas cannot be reset as a recording canvas");
+ }
+
+ virtual uirenderer::DisplayList* finishRecording() override {
+ LOG_ALWAYS_FATAL("SkiaCanvas does not produce a DisplayList");
+ return nullptr;
+ }
+ virtual void insertReorderBarrier(bool enableReorder) override {
+ LOG_ALWAYS_FATAL("SkiaCanvas does not support reordering barriers");
+ }
+
virtual void setBitmap(const SkBitmap& bitmap) override;
virtual bool isOpaque() override;
virtual int width() override;
virtual int height() override;
+ virtual void setHighContrastText(bool highContrastText) override {
+ mHighContrastText = highContrastText;
+ }
+ virtual bool isHighContrastText() override { return mHighContrastText; }
+
virtual int getSaveCount() const override;
- virtual int save(SkCanvas::SaveFlags flags) override;
+ virtual int save(SaveFlags::Flags flags) override;
virtual void restore() override;
virtual void restoreToCount(int saveCount) override;
virtual int saveLayer(float left, float top, float right, float bottom,
- const SkPaint* paint, SkCanvas::SaveFlags flags) override;
+ const SkPaint* paint, SaveFlags::Flags flags) override;
virtual int saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, SkCanvas::SaveFlags flags) override;
+ int alpha, SaveFlags::Flags flags) override;
virtual void getMatrix(SkMatrix* outMatrix) const override;
virtual void setMatrix(const SkMatrix& matrix) override;
- virtual void setLocalMatrix(const SkMatrix& matrix) override { this->setMatrix(matrix); }
virtual void concat(const SkMatrix& matrix) override;
virtual void rotate(float degrees) override;
virtual void scale(float sx, float sy) override;
@@ -95,6 +121,7 @@ public:
virtual void drawLines(const float* points, int count, const SkPaint& paint) override;
virtual void drawRect(float left, float top, float right, float bottom,
const SkPaint& paint) override;
+ virtual void drawRegion(const SkRegion& region, const SkPaint& paint) override;
virtual void drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint& paint) override;
virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) override;
@@ -116,34 +143,51 @@ public:
float dstRight, float dstBottom, const SkPaint* paint) override;
virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
const float* vertices, const int* colors, const SkPaint* paint) override;
+ virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ const SkPaint* paint) override;
- virtual void drawText(const uint16_t* text, const float* positions, int count,
+ virtual bool drawTextAbsolutePos() const override { return true; }
+ virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
+
+ virtual void drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
+ uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
+ uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
+ uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* paint) override;
+ virtual void drawCircle(uirenderer::CanvasPropertyPrimitive* x,
+ uirenderer::CanvasPropertyPrimitive* y, uirenderer::CanvasPropertyPrimitive* radius,
+ uirenderer::CanvasPropertyPaint* paint) override;
+
+ virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override;
+ virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override;
+ virtual void callDrawGLFunction(Functor* functor,
+ uirenderer::GlFunctorLifecycleListener* listener) override;
+
+protected:
+ virtual void drawGlyphs(const uint16_t* text, const float* positions, int count,
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) override;
- virtual void drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) override;
- virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+ virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) override;
- virtual bool drawTextAbsolutePos() const override { return true; }
-
private:
struct SaveRec {
- int saveCount;
- SkCanvas::SaveFlags saveFlags;
+ int saveCount;
+ SaveFlags::Flags saveFlags;
};
- void recordPartialSave(SkCanvas::SaveFlags flags);
+ bool mHighContrastText = false;
+
+ void recordPartialSave(SaveFlags::Flags flags);
void saveClipsForFrame(SkTArray<SkClipStack::Element>& clips, int frameSaveCount);
void applyClips(const SkTArray<SkClipStack::Element>& clips);
void drawPoints(const float* points, int count, const SkPaint& paint,
SkCanvas::PointMode mode);
- void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
SkAutoTUnref<SkCanvas> mCanvas;
- SkAutoTDelete<SkDeque> mSaveStack; // lazily allocated, tracks partial saves.
+ std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves.
};
Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
@@ -182,15 +226,13 @@ private:
void SkiaCanvas::setBitmap(const SkBitmap& bitmap) {
SkCanvas* newCanvas = new SkCanvas(bitmap);
- SkASSERT(newCanvas);
if (!bitmap.isNull()) {
// Copy the canvas matrix & clip state.
newCanvas->setMatrix(mCanvas->getTotalMatrix());
- if (NULL != mCanvas->getDevice() && NULL != newCanvas->getDevice()) {
- ClipCopier copier(newCanvas);
- mCanvas->replayClips(&copier);
- }
+
+ ClipCopier copier(newCanvas);
+ mCanvas->replayClips(&copier);
}
// unrefs the existing canvas
@@ -205,15 +247,15 @@ void SkiaCanvas::setBitmap(const SkBitmap& bitmap) {
// ----------------------------------------------------------------------------
bool SkiaCanvas::isOpaque() {
- return mCanvas->getDevice()->accessBitmap(false).isOpaque();
+ return mCanvas->imageInfo().isOpaque();
}
int SkiaCanvas::width() {
- return mCanvas->getBaseLayerSize().width();
+ return mCanvas->imageInfo().width();
}
int SkiaCanvas::height() {
- return mCanvas->getBaseLayerSize().height();
+ return mCanvas->imageInfo().height();
}
// ----------------------------------------------------------------------------
@@ -224,17 +266,21 @@ int SkiaCanvas::getSaveCount() const {
return mCanvas->getSaveCount();
}
-int SkiaCanvas::save(SkCanvas::SaveFlags flags) {
+int SkiaCanvas::save(SaveFlags::Flags flags) {
int count = mCanvas->save();
recordPartialSave(flags);
return count;
}
+// The SkiaCanvas::restore operation layers on the capability to preserve
+// either (or both) the matrix and/or clip state after a SkCanvas::restore
+// operation. It does this by explicitly saving off the clip & matrix state
+// when requested and playing it back after the SkCanvas::restore.
void SkiaCanvas::restore() {
const SaveRec* rec = (NULL == mSaveStack.get())
? NULL
: static_cast<SaveRec*>(mSaveStack->back());
- int currentSaveCount = mCanvas->getSaveCount() - 1;
+ int currentSaveCount = mCanvas->getSaveCount();
SkASSERT(NULL == rec || currentSaveCount >= rec->saveCount);
if (NULL == rec || rec->saveCount != currentSaveCount) {
@@ -243,8 +289,8 @@ void SkiaCanvas::restore() {
return;
}
- bool preserveMatrix = !(rec->saveFlags & SkCanvas::kMatrix_SaveFlag);
- bool preserveClip = !(rec->saveFlags & SkCanvas::kClip_SaveFlag);
+ bool preserveMatrix = !(rec->saveFlags & SaveFlags::Matrix);
+ bool preserveClip = !(rec->saveFlags & SaveFlags::Clip);
SkMatrix savedMatrix;
if (preserveMatrix) {
@@ -252,8 +298,9 @@ void SkiaCanvas::restore() {
}
SkTArray<SkClipStack::Element> savedClips;
+ int topClipStackFrame = mCanvas->getClipStack()->getSaveCount();
if (preserveClip) {
- saveClipsForFrame(savedClips, currentSaveCount);
+ saveClipsForFrame(savedClips, topClipStackFrame);
}
mCanvas->restore();
@@ -262,7 +309,11 @@ void SkiaCanvas::restore() {
mCanvas->setMatrix(savedMatrix);
}
- if (preserveClip && !savedClips.empty()) {
+ if (preserveClip && !savedClips.empty() &&
+ topClipStackFrame != mCanvas->getClipStack()->getSaveCount()) {
+ // Only reapply the saved clips if the top clip stack frame was actually
+ // popped by restore(). If it wasn't, it means it doesn't belong to the
+ // restored canvas frame (SkCanvas lazy save/restore kicked in).
applyClips(savedClips);
}
@@ -275,58 +326,79 @@ void SkiaCanvas::restoreToCount(int restoreCount) {
}
}
+static inline SkCanvas::SaveLayerFlags layerFlags(SaveFlags::Flags flags) {
+ SkCanvas::SaveLayerFlags layerFlags = 0;
+
+ if (!(flags & SaveFlags::HasAlphaLayer)) {
+ layerFlags |= SkCanvas::kIsOpaque_SaveLayerFlag;
+ }
+
+ if (!(flags & SaveFlags::ClipToLayer)) {
+ layerFlags |= SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag;
+ }
+
+ return layerFlags;
+}
+
int SkiaCanvas::saveLayer(float left, float top, float right, float bottom,
- const SkPaint* paint, SkCanvas::SaveFlags flags) {
- SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
- int count = mCanvas->saveLayer(&bounds, paint, flags | SkCanvas::kMatrixClip_SaveFlag);
+ const SkPaint* paint, SaveFlags::Flags flags) {
+ const SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
+ const SkCanvas::SaveLayerRec rec(&bounds, paint, layerFlags(flags));
+
+ int count = mCanvas->saveLayer(rec);
recordPartialSave(flags);
return count;
}
int SkiaCanvas::saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, SkCanvas::SaveFlags flags) {
- SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
- int count = mCanvas->saveLayerAlpha(&bounds, alpha, flags | SkCanvas::kMatrixClip_SaveFlag);
- recordPartialSave(flags);
- return count;
+ int alpha, SaveFlags::Flags flags) {
+ SkTLazy<SkPaint> alphaPaint;
+ if (static_cast<unsigned>(alpha) < 0xFF) {
+ alphaPaint.init()->setAlpha(alpha);
+ }
+
+ return this->saveLayer(left, top, right, bottom, alphaPaint.getMaybeNull(),
+ flags);
}
// ----------------------------------------------------------------------------
// functions to emulate legacy SaveFlags (i.e. independent matrix/clip flags)
// ----------------------------------------------------------------------------
-void SkiaCanvas::recordPartialSave(SkCanvas::SaveFlags flags) {
+void SkiaCanvas::recordPartialSave(SaveFlags::Flags flags) {
// A partial save is a save operation which doesn't capture the full canvas state.
- // (either kMatrix_SaveFlags or kClip_SaveFlag is missing).
+ // (either SaveFlags::Matrix or SaveFlags::Clip is missing).
// Mask-out non canvas state bits.
- flags = static_cast<SkCanvas::SaveFlags>(flags & SkCanvas::kMatrixClip_SaveFlag);
+ flags &= SaveFlags::MatrixClip;
- if (SkCanvas::kMatrixClip_SaveFlag == flags) {
+ if (flags == SaveFlags::MatrixClip) {
// not a partial save.
return;
}
if (NULL == mSaveStack.get()) {
- mSaveStack.reset(SkNEW_ARGS(SkDeque, (sizeof(struct SaveRec), 8)));
+ mSaveStack.reset(new SkDeque(sizeof(struct SaveRec), 8));
}
SaveRec* rec = static_cast<SaveRec*>(mSaveStack->push_back());
- // Store the save counter in the SkClipStack domain.
- // (0-based, equal to the number of save ops on the stack).
- rec->saveCount = mCanvas->getSaveCount() - 1;
+ rec->saveCount = mCanvas->getSaveCount();
rec->saveFlags = flags;
}
-void SkiaCanvas::saveClipsForFrame(SkTArray<SkClipStack::Element>& clips, int frameSaveCount) {
+void SkiaCanvas::saveClipsForFrame(SkTArray<SkClipStack::Element>& clips,
+ int saveCountToBackup) {
+ // Each SkClipStack::Element stores the index of the canvas save
+ // with which it is associated. Backup only those Elements that
+ // are associated with 'saveCountToBackup'
SkClipStack::Iter clipIterator(*mCanvas->getClipStack(),
SkClipStack::Iter::kTop_IterStart);
- while (const SkClipStack::Element* elem = clipIterator.next()) {
- if (elem->getSaveCount() < frameSaveCount) {
- // done with the current frame.
+ while (const SkClipStack::Element* elem = clipIterator.prev()) {
+ if (elem->getSaveCount() < saveCountToBackup) {
+ // done with the target save count.
break;
}
- SkASSERT(elem->getSaveCount() == frameSaveCount);
+ SkASSERT(elem->getSaveCount() == saveCountToBackup);
clips.push_back(*elem);
}
}
@@ -474,13 +546,12 @@ void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint
SkCanvas::PointMode mode) {
// convert the floats into SkPoints
count >>= 1; // now it is the number of points
- SkAutoSTMalloc<32, SkPoint> storage(count);
- SkPoint* pts = storage.get();
+ std::unique_ptr<SkPoint[]> pts(new SkPoint[count]);
for (int i = 0; i < count; i++) {
pts[i].set(points[0], points[1]);
points += 2;
}
- mCanvas->drawPoints(mode, count, pts, paint);
+ mCanvas->drawPoints(mode, count, pts.get(), paint);
}
@@ -507,6 +578,14 @@ void SkiaCanvas::drawRect(float left, float top, float right, float bottom,
}
+void SkiaCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
+ SkRegion::Iterator it(region);
+ while (!it.done()) {
+ mCanvas->drawRect(SkRect::Make(it.rect()), paint);
+ it.next();
+ }
+}
+
void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint& paint) {
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
@@ -562,7 +641,7 @@ void SkiaCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
float dstRight, float dstBottom, const SkPaint* paint) {
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- mCanvas->drawBitmapRectToRect(bitmap, &srcRect, dstRect, paint);
+ mCanvas->drawBitmapRect(bitmap, srcRect, dstRect, paint);
}
void SkiaCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
@@ -584,7 +663,7 @@ void SkiaCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshH
#ifndef SK_SCALAR_IS_FLOAT
SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid");
#endif
- SkAutoMalloc storage(storageSize);
+ std::unique_ptr<char[]> storage(new char[storageSize]);
SkPoint* texs = (SkPoint*)storage.get();
uint16_t* indices = (uint16_t*)(texs + ptCount);
@@ -662,43 +741,113 @@ void SkiaCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshH
indexCount, tmpPaint);
}
+void SkiaCanvas::drawNinePatch(const SkBitmap& bitmap, const Res_png_9patch& chunk,
+ float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) {
+ SkRect bounds = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
+ NinePatch::Draw(mCanvas, bounds, bitmap, chunk, paint, nullptr);
+}
+
+void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
+ vectorDrawable->drawStaging(this);
+}
+
// ----------------------------------------------------------------------------
// Canvas draw operations: Text
// ----------------------------------------------------------------------------
-void SkiaCanvas::drawText(const uint16_t* text, const float* positions, int count,
+void SkiaCanvas::drawGlyphs(const uint16_t* text, const float* positions, int count,
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) {
- // Set align to left for drawing, as we don't want individual
- // glyphs centered or right-aligned; the offset above takes
- // care of all alignment.
- SkPaint paintCopy(paint);
- paintCopy.setTextAlign(SkPaint::kLeft_Align);
+ static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
+ mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paint);
+ drawTextDecorations(x, y, totalAdvance, paint);
+}
- SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats);
- mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paintCopy);
+void SkiaCanvas::drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+ float hOffset, float vOffset, const SkPaint& paint) {
+ mCanvas->drawTextOnPathHV(glyphs, count << 1, path, hOffset, vOffset, paint);
}
-void SkiaCanvas::drawPosText(const uint16_t* text, const float* positions, int count, int posCount,
- const SkPaint& paint) {
- SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
- int indx;
- for (indx = 0; indx < posCount; indx++) {
- posPtr[indx].fX = positions[indx << 1];
- posPtr[indx].fY = positions[(indx << 1) + 1];
- }
+// ----------------------------------------------------------------------------
+// Canvas draw operations: Animations
+// ----------------------------------------------------------------------------
- SkPaint paintCopy(paint);
- paintCopy.setTextEncoding(SkPaint::kUTF16_TextEncoding);
- mCanvas->drawPosText(text, count, posPtr, paintCopy);
+class AnimatedRoundRect : public SkDrawable {
+ public:
+ AnimatedRoundRect(uirenderer::CanvasPropertyPrimitive* left,
+ uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
+ uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
+ uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* p) :
+ mLeft(left), mTop(top), mRight(right), mBottom(bottom), mRx(rx), mRy(ry), mPaint(p) {}
+
+ protected:
+ virtual SkRect onGetBounds() override {
+ return SkRect::MakeLTRB(mLeft->value, mTop->value, mRight->value, mBottom->value);
+ }
+ virtual void onDraw(SkCanvas* canvas) override {
+ SkRect rect = SkRect::MakeLTRB(mLeft->value, mTop->value, mRight->value, mBottom->value);
+ canvas->drawRoundRect(rect, mRx->value, mRy->value, mPaint->value);
+ }
+
+ private:
+ sp<uirenderer::CanvasPropertyPrimitive> mLeft;
+ sp<uirenderer::CanvasPropertyPrimitive> mTop;
+ sp<uirenderer::CanvasPropertyPrimitive> mRight;
+ sp<uirenderer::CanvasPropertyPrimitive> mBottom;
+ sp<uirenderer::CanvasPropertyPrimitive> mRx;
+ sp<uirenderer::CanvasPropertyPrimitive> mRy;
+ sp<uirenderer::CanvasPropertyPaint> mPaint;
+};
- delete[] posPtr;
+class AnimatedCircle : public SkDrawable {
+ public:
+ AnimatedCircle(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y,
+ uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint) :
+ mX(x), mY(y), mRadius(radius), mPaint(paint) {}
+
+ protected:
+ virtual SkRect onGetBounds() override {
+ const float x = mX->value;
+ const float y = mY->value;
+ const float radius = mRadius->value;
+ return SkRect::MakeLTRB(x - radius, y - radius, x + radius, y + radius);
+ }
+ virtual void onDraw(SkCanvas* canvas) override {
+ canvas->drawCircle(mX->value, mY->value, mRadius->value, mPaint->value);
+ }
+
+ private:
+ sp<uirenderer::CanvasPropertyPrimitive> mX;
+ sp<uirenderer::CanvasPropertyPrimitive> mY;
+ sp<uirenderer::CanvasPropertyPrimitive> mRadius;
+ sp<uirenderer::CanvasPropertyPaint> mPaint;
+};
+
+void SkiaCanvas::drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
+ uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
+ uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
+ uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* paint) {
+ SkAutoTUnref<AnimatedRoundRect> drawable(
+ new AnimatedRoundRect(left, top, right, bottom, rx, ry, paint));
+ mCanvas->drawDrawable(drawable.get());
}
-void SkiaCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
- float hOffset, float vOffset, const SkPaint& paint) {
- mCanvas->drawTextOnPathHV(glyphs, count << 1, path, hOffset, vOffset, paint);
+void SkiaCanvas::drawCircle(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y,
+ uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint) {
+ SkAutoTUnref<AnimatedCircle> drawable(new AnimatedCircle(x, y, radius, paint));
+ mCanvas->drawDrawable(drawable.get());
}
+// ----------------------------------------------------------------------------
+// Canvas draw operations: View System
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::drawLayer(uirenderer::DeferredLayerUpdater* layer) { }
+
+void SkiaCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) { }
+
+void SkiaCanvas::callDrawGLFunction(Functor* functor,
+ uirenderer::GlFunctorLifecycleListener* listener) { }
+
} // namespace android
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index d96ca2afed00..9df32b28bf3b 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -18,6 +18,13 @@
#include <cutils/log.h>
#include <SkPatchUtils.h>
+#include <SkPaint.h>
+#include <SkPath.h>
+#include <SkPixelRef.h>
+#include <SkRect.h>
+#include <SkRRect.h>
+
+#include <memory>
namespace android {
namespace uirenderer {
@@ -38,7 +45,7 @@ void SkiaCanvasProxy::onDrawPoints(PointMode pointMode, size_t count, const SkPo
}
// convert the SkPoints into floats
- SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats);
+ static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
const size_t floatCount = count << 1;
const float* floatArray = &pts[0].fX;
@@ -96,12 +103,31 @@ void SkiaCanvasProxy::onDrawPath(const SkPath& path, const SkPaint& paint) {
void SkiaCanvasProxy::onDrawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
const SkPaint* paint) {
- mCanvas->drawBitmap(bitmap, left, top, paint);
+ SkPixelRef* pxRef = bitmap.pixelRef();
+
+ // HWUI doesn't support extractSubset(), so convert any subsetted bitmap into
+ // a drawBitmapRect(); pass through an un-subsetted bitmap.
+ if (pxRef && bitmap.dimensions() != pxRef->info().dimensions()) {
+ SkBitmap fullBitmap;
+ fullBitmap.setInfo(pxRef->info());
+ fullBitmap.setPixelRef(pxRef, 0, 0);
+ SkIPoint origin = bitmap.pixelRefOrigin();
+ mCanvas->drawBitmap(fullBitmap, origin.fX, origin.fY,
+ origin.fX + bitmap.dimensions().width(),
+ origin.fY + bitmap.dimensions().height(),
+ left, top,
+ left + bitmap.dimensions().width(),
+ top + bitmap.dimensions().height(),
+ paint);
+ } else {
+ mCanvas->drawBitmap(bitmap, left, top, paint);
+ }
}
void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* srcPtr,
- const SkRect& dst, const SkPaint* paint, DrawBitmapRectFlags) {
+ const SkRect& dst, const SkPaint* paint, SrcRectConstraint) {
SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(bitmap.width(), bitmap.height());
+ // TODO: if bitmap is a subset, do we need to add pixelRefOrigin to src?
mCanvas->drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
}
@@ -112,14 +138,6 @@ void SkiaCanvasProxy::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& ce
SkDEBUGFAIL("SkiaCanvasProxy::onDrawBitmapNine is not yet supported");
}
-void SkiaCanvasProxy::onDrawSprite(const SkBitmap& bitmap, int left, int top,
- const SkPaint* paint) {
- mCanvas->save(SkCanvas::kMatrixClip_SaveFlag);
- mCanvas->setLocalMatrix(SkMatrix::I());
- mCanvas->drawBitmap(bitmap, left, top, paint);
- mCanvas->restore();
-}
-
void SkiaCanvasProxy::onDrawVertices(VertexMode mode, int vertexCount, const SkPoint vertices[],
const SkPoint texs[], const SkColor colors[], SkXfermode*, const uint16_t indices[],
int indexCount, const SkPaint& paint) {
@@ -127,7 +145,7 @@ void SkiaCanvasProxy::onDrawVertices(VertexMode mode, int vertexCount, const SkP
return;
}
// convert the SkPoints into floats
- SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats);
+ static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
const int floatCount = vertexCount << 1;
const float* vArray = &vertices[0].fX;
const float* tArray = (texs) ? &texs[0].fX : NULL;
@@ -141,18 +159,32 @@ SkSurface* SkiaCanvasProxy::onNewSurface(const SkImageInfo&, const SkSurfaceProp
}
void SkiaCanvasProxy::willSave() {
- mCanvas->save(SkCanvas::kMatrixClip_SaveFlag);
+ mCanvas->save(android::SaveFlags::MatrixClip);
}
-SkCanvas::SaveLayerStrategy SkiaCanvasProxy::willSaveLayer(const SkRect* rectPtr,
- const SkPaint* paint, SaveFlags flags) {
+static inline SaveFlags::Flags saveFlags(SkCanvas::SaveLayerFlags layerFlags) {
+ SaveFlags::Flags saveFlags = 0;
+
+ if (!(layerFlags & SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag)) {
+ saveFlags |= SaveFlags::ClipToLayer;
+ }
+
+ if (!(layerFlags & SkCanvas::kIsOpaque_SaveLayerFlag)) {
+ saveFlags |= SaveFlags::HasAlphaLayer;
+ }
+
+ return saveFlags;
+}
+
+SkCanvas::SaveLayerStrategy SkiaCanvasProxy::getSaveLayerStrategy(const SaveLayerRec& saveLayerRec) {
SkRect rect;
- if (rectPtr) {
- rect = *rectPtr;
- } else if(!mCanvas->getClipBounds(&rect)) {
+ if (saveLayerRec.fBounds) {
+ rect = *saveLayerRec.fBounds;
+ } else if (!mCanvas->getClipBounds(&rect)) {
rect = SkRect::MakeEmpty();
}
- mCanvas->saveLayer(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint, flags);
+ mCanvas->saveLayer(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, saveLayerRec.fPaint,
+ saveFlags(saveLayerRec.fSaveLayerFlags));
return SkCanvas::kNoLayer_SaveLayerStrategy;
}
@@ -165,9 +197,7 @@ void SkiaCanvasProxy::didConcat(const SkMatrix& matrix) {
}
void SkiaCanvasProxy::didSetMatrix(const SkMatrix& matrix) {
- // SkCanvas setMatrix() is relative to the Canvas origin, but OpenGLRenderer's
- // setMatrix() is relative to device origin; call setLocalMatrix() instead.
- mCanvas->setLocalMatrix(matrix);
+ mCanvas->setMatrix(matrix);
}
void SkiaCanvasProxy::onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
@@ -191,7 +221,8 @@ public:
glyphIDs = (uint16_t*)text;
count = byteLength >> 1;
} else {
- storage.reset(byteLength); // ensures space for one glyph per ID given UTF8 encoding.
+ // ensure space for one glyph per ID given UTF8 encoding.
+ storage.reset(new uint16_t[byteLength]);
glyphIDs = storage.get();
count = paint.textToGlyphs(text, byteLength, storage.get());
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
@@ -202,7 +233,7 @@ public:
uint16_t* glyphIDs;
int count;
private:
- SkAutoSTMalloc<32, uint16_t> storage;
+ std::unique_ptr<uint16_t[]> storage;
};
void SkiaCanvasProxy::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
@@ -211,8 +242,8 @@ void SkiaCanvasProxy::onDrawText(const void* text, size_t byteLength, SkScalar x
GlyphIDConverter glyphs(text, byteLength, origPaint);
// compute the glyph positions
- SkAutoSTMalloc<32, SkPoint> pointStorage(glyphs.count);
- SkAutoSTMalloc<32, SkScalar> glyphWidths(glyphs.count);
+ std::unique_ptr<SkPoint[]> pointStorage(new SkPoint[glyphs.count]);
+ std::unique_ptr<SkScalar[]> glyphWidths(new SkScalar[glyphs.count]);
glyphs.paint.getTextWidths(glyphs.glyphIDs, glyphs.count << 1, glyphWidths.get());
// compute conservative bounds
@@ -258,8 +289,8 @@ void SkiaCanvasProxy::onDrawText(const void* text, size_t byteLength, SkScalar x
}
}
- SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats);
- mCanvas->drawText(glyphs.glyphIDs, &pointStorage[0].fX, glyphs.count, glyphs.paint,
+ static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
+ mCanvas->drawGlyphs(glyphs.glyphIDs, &pointStorage[0].fX, glyphs.count, glyphs.paint,
x, y, bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0);
}
@@ -271,7 +302,7 @@ void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const S
// convert to relative positions if necessary
int x, y;
const SkPoint* posArray;
- SkAutoSTMalloc<32, SkPoint> pointStorage;
+ std::unique_ptr<SkPoint[]> pointStorage;
if (mCanvas->drawTextAbsolutePos()) {
x = 0;
y = 0;
@@ -279,41 +310,46 @@ void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const S
} else {
x = pos[0].fX;
y = pos[0].fY;
- posArray = pointStorage.reset(glyphs.count);
+ pointStorage.reset(new SkPoint[glyphs.count]);
for (int i = 0; i < glyphs.count; i++) {
- pointStorage[i].fX = pos[i].fX- x;
- pointStorage[i].fY = pos[i].fY- y;
+ pointStorage[i].fX = pos[i].fX - x;
+ pointStorage[i].fY = pos[i].fY - y;
}
+ posArray = pointStorage.get();
}
- // compute conservative bounds
- // NOTE: We could call the faster paint.getFontBounds for a less accurate,
- // but even more conservative bounds if this is too slow.
+ // Compute conservative bounds. If the content has already been processed
+ // by Minikin then it had already computed these bounds. Unfortunately,
+ // there is no way to capture those bounds as part of the Skia drawPosText
+ // API so we need to do that computation again here.
SkRect bounds;
- glyphs.paint.measureText(glyphs.glyphIDs, glyphs.count << 1, &bounds);
- bounds.offset(x, y);
+ for (int i = 0; i < glyphs.count; i++) {
+ SkRect glyphBounds;
+ glyphs.paint.measureText(&glyphs.glyphIDs[i], sizeof(uint16_t), &glyphBounds);
+ glyphBounds.offset(pos[i].fX, pos[i].fY);
+ bounds.join(glyphBounds);
+ }
- SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats);
- mCanvas->drawText(glyphs.glyphIDs, &posArray[0].fX, glyphs.count, glyphs.paint, x, y,
+ static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
+ mCanvas->drawGlyphs(glyphs.glyphIDs, &posArray[0].fX, glyphs.count, glyphs.paint, x, y,
bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0);
}
void SkiaCanvasProxy::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
SkScalar constY, const SkPaint& paint) {
const size_t pointCount = byteLength >> 1;
- SkAutoSTMalloc<32, SkPoint> storage(pointCount);
- SkPoint* pts = storage.get();
+ std::unique_ptr<SkPoint[]> pts(new SkPoint[pointCount]);
for (size_t i = 0; i < pointCount; i++) {
pts[i].set(xpos[i], constY);
}
- this->onDrawPosText(text, byteLength, pts, paint);
+ this->onDrawPosText(text, byteLength, pts.get(), paint);
}
void SkiaCanvasProxy::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
const SkMatrix* matrix, const SkPaint& origPaint) {
// convert to glyphIDs if necessary
GlyphIDConverter glyphs(text, byteLength, origPaint);
- mCanvas->drawTextOnPath(glyphs.glyphIDs, glyphs.count, path, 0, 0, glyphs.paint);
+ mCanvas->drawGlyphsOnPath(glyphs.glyphIDs, glyphs.count, path, 0, 0, glyphs.paint);
}
void SkiaCanvasProxy::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h
index 0de965094e64..973c55fe2236 100644
--- a/libs/hwui/SkiaCanvasProxy.h
+++ b/libs/hwui/SkiaCanvasProxy.h
@@ -20,7 +20,7 @@
#include <cutils/compiler.h>
#include <SkCanvas.h>
-#include "Canvas.h"
+#include "hwui/Canvas.h"
namespace android {
namespace uirenderer {
@@ -47,7 +47,7 @@ protected:
virtual SkSurface* onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override;
virtual void willSave() override;
- virtual SaveLayerStrategy willSaveLayer(const SkRect*, const SkPaint*, SaveFlags) override;
+ virtual SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override;
virtual void willRestore() override;
virtual void didConcat(const SkMatrix&) override;
@@ -63,11 +63,9 @@ protected:
virtual void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top,
const SkPaint*) override;
virtual void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst,
- const SkPaint* paint, DrawBitmapRectFlags flags) override;
+ const SkPaint* paint, SrcRectConstraint) override;
virtual void onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
const SkRect& dst, const SkPaint*) override;
- virtual void onDrawSprite(const SkBitmap&, int left, int top,
- const SkPaint*) override;
virtual void onDrawVertices(VertexMode, int vertexCount, const SkPoint vertices[],
const SkPoint texs[], const SkColor colors[], SkXfermode*,
const uint16_t indices[], int indexCount,
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 81d8516168dd..6f4a6839be4e 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include "SkiaShader.h"
#include "Caches.h"
@@ -34,12 +32,19 @@ namespace uirenderer {
// Support
///////////////////////////////////////////////////////////////////////////////
-static const GLenum gTileModes[] = {
+static constexpr GLenum gTileModes[] = {
GL_CLAMP_TO_EDGE, // == SkShader::kClamp_TileMode
GL_REPEAT, // == SkShader::kRepeat_Mode
GL_MIRRORED_REPEAT // == SkShader::kMirror_TileMode
};
+static_assert(gTileModes[SkShader::kClamp_TileMode] == GL_CLAMP_TO_EDGE,
+ "SkShader TileModes have changed");
+static_assert(gTileModes[SkShader::kRepeat_TileMode] == GL_REPEAT,
+ "SkShader TileModes have changed");
+static_assert(gTileModes[SkShader::kMirror_TileMode] == GL_MIRRORED_REPEAT,
+ "SkShader TileModes have changed");
+
/**
* This function does not work for n == 0.
*/
@@ -52,7 +57,7 @@ static inline void bindUniformColor(int slot, FloatColor color) {
}
static inline void bindTexture(Caches* caches, Texture* texture, GLenum wrapS, GLenum wrapT) {
- caches->textureState().bindTexture(texture->id);
+ caches->textureState().bindTexture(texture->id());
texture->setWrapST(wrapS, wrapT);
}
@@ -199,7 +204,7 @@ bool tryStoreBitmap(Caches& caches, const SkShader& shader, const Matrix4& model
SkiaShaderData::BitmapShaderData* outData) {
SkBitmap bitmap;
SkShader::TileMode xy[2];
- if (shader.asABitmap(&bitmap, nullptr, xy) != SkShader::kDefault_BitmapType) {
+ if (!shader.isABitmap(&bitmap, nullptr, xy)) {
return false;
}
@@ -214,8 +219,8 @@ bool tryStoreBitmap(Caches& caches, const SkShader& shader, const Matrix4& model
outData->bitmapSampler = (*textureUnit)++;
- const float width = outData->bitmapTexture->width;
- const float height = outData->bitmapTexture->height;
+ const float width = outData->bitmapTexture->width();
+ const float height = outData->bitmapTexture->height();
description->hasBitmap = true;
if (!caches.extensions().hasNPot()
@@ -267,7 +272,7 @@ SkiaShaderType getComposeSubType(const SkShader& shader) {
}
// The shader is not a gradient. Check for a bitmap shader.
- if (shader.asABitmap(nullptr, nullptr, nullptr) == SkShader::kDefault_BitmapType) {
+ if (shader.isABitmap()) {
return kBitmap_SkiaShaderType;
}
return kNone_SkiaShaderType;
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index beb2e1d0481c..2c9c9d90f686 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include "Snapshot.h"
-#include <SkCanvas.h>
+#include "hwui/Canvas.h"
namespace android {
namespace uirenderer {
@@ -46,7 +44,7 @@ Snapshot::Snapshot()
* Copies the specified snapshot/ The specified snapshot is stored as
* the previous snapshot.
*/
-Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags)
+Snapshot::Snapshot(Snapshot* s, int saveFlags)
: flags(0)
, previous(s)
, layer(s->layer)
@@ -59,14 +57,14 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags)
, mClipArea(nullptr)
, mViewportData(s->mViewportData)
, mRelativeLightCenter(s->mRelativeLightCenter) {
- if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
- mTransformRoot.load(*s->transform);
+ if (saveFlags & SaveFlags::Matrix) {
+ mTransformRoot = *s->transform;
transform = &mTransformRoot;
} else {
transform = s->transform;
}
- if (saveFlags & SkCanvas::kClip_SaveFlag) {
+ if (saveFlags & SaveFlags::Clip) {
mClipAreaRoot = s->getClipArea();
mClipArea = &mClipAreaRoot;
} else {
@@ -85,24 +83,24 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags)
// Clipping
///////////////////////////////////////////////////////////////////////////////
-bool Snapshot::clipRegionTransformed(const SkRegion& region, SkRegion::Op op) {
+void Snapshot::clipRegionTransformed(const SkRegion& region, SkRegion::Op op) {
flags |= Snapshot::kFlagClipSet;
- return mClipArea->clipRegion(region, op);
+ mClipArea->clipRegion(region, op);
}
-bool Snapshot::clip(float left, float top, float right, float bottom, SkRegion::Op op) {
+void Snapshot::clip(const Rect& localClip, SkRegion::Op op) {
flags |= Snapshot::kFlagClipSet;
- return mClipArea->clipRectWithTransform(left, top, right, bottom, transform, op);
+ mClipArea->clipRectWithTransform(localClip, transform, op);
}
-bool Snapshot::clipPath(const SkPath& path, SkRegion::Op op) {
+void Snapshot::clipPath(const SkPath& path, SkRegion::Op op) {
flags |= Snapshot::kFlagClipSet;
- return mClipArea->clipPathWithTransform(path, transform, op);
+ mClipArea->clipPathWithTransform(path, transform, op);
}
void Snapshot::setClip(float left, float top, float right, float bottom) {
- mClipArea->setClip(left, top, right, bottom);
flags |= Snapshot::kFlagClipSet;
+ mClipArea->setClip(left, top, right, bottom);
}
bool Snapshot::hasPerspectiveTransform() const {
@@ -132,6 +130,9 @@ void Snapshot::resetClip(float left, float top, float right, float bottom) {
///////////////////////////////////////////////////////////////////////////////
void Snapshot::resetTransform(float x, float y, float z) {
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("not supported - light center managed differently");
+#else
// before resetting, map current light pos with inverse of current transform
Vector3 center = mRelativeLightCenter;
mat4 inverse;
@@ -141,16 +142,20 @@ void Snapshot::resetTransform(float x, float y, float z) {
transform = &mTransformRoot;
transform->loadTranslate(x, y, z);
+#endif
}
void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("not supported - not needed by new ops");
+#else
// build (reverse ordered) list of the stack of snapshots, terminated with a NULL
Vector<const Snapshot*> snapshotList;
snapshotList.push(nullptr);
const Snapshot* current = this;
do {
snapshotList.push(current);
- current = current->previous.get();
+ current = current->previous;
} while (current);
// traverse the list, adding in each transform that contributes to the total transform
@@ -169,6 +174,7 @@ void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
outTransform->multiply(*(current->transform));
}
}
+#endif
}
///////////////////////////////////////////////////////////////////////////////
@@ -192,8 +198,7 @@ void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& boun
state->highPriority = highPriority;
// store the inverse drawing matrix
- Matrix4 roundRectDrawingMatrix;
- roundRectDrawingMatrix.load(getOrthoMatrix());
+ Matrix4 roundRectDrawingMatrix = getOrthoMatrix();
roundRectDrawingMatrix.multiply(*transform);
state->matrix.loadInverse(roundRectDrawingMatrix);
@@ -222,15 +227,46 @@ void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& boun
}
void Snapshot::setProjectionPathMask(LinearAllocator& allocator, const SkPath* path) {
+#if HWUI_NEW_OPS
+ // TODO: remove allocator param for HWUI_NEW_OPS
+ projectionPathMask = path;
+#else
if (path) {
ProjectionPathMask* mask = new (allocator) ProjectionPathMask;
mask->projectionMask = path;
buildScreenSpaceTransform(&(mask->projectionMaskTransform));
-
projectionPathMask = mask;
} else {
projectionPathMask = nullptr;
}
+#endif
+}
+
+static Snapshot* getClipRoot(Snapshot* target) {
+ while (target->previous && target->previous->previous) {
+ target = target->previous;
+ }
+ return target;
+}
+
+const ClipBase* Snapshot::serializeIntersectedClip(LinearAllocator& allocator,
+ const ClipBase* recordedClip, const Matrix4& recordedClipTransform) {
+ auto target = this;
+ if (CC_UNLIKELY(recordedClip && recordedClip->intersectWithRoot)) {
+ // Clip must be intersected with root, instead of current clip.
+ target = getClipRoot(this);
+ }
+
+ return target->mClipArea->serializeIntersectedClip(allocator,
+ recordedClip, recordedClipTransform);
+}
+
+void Snapshot::applyClip(const ClipBase* recordedClip, const Matrix4& transform) {
+ if (CC_UNLIKELY(recordedClip && recordedClip->intersectWithRoot)) {
+ // current clip is being replaced, but must intersect with clip root
+ *mClipArea = *(getClipRoot(this)->mClipArea);
+ }
+ mClipArea->applyClip(recordedClip, transform);
}
///////////////////////////////////////////////////////////////////////////////
@@ -243,7 +279,7 @@ bool Snapshot::isIgnored() const {
void Snapshot::dump() const {
ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d",
- this, flags, previous.get(), getViewportHeight(), isIgnored(), !mClipArea->isSimple());
+ this, flags, previous, getViewportHeight(), isIgnored(), !mClipArea->isSimple());
const Rect& clipRect(mClipArea->getClipRect());
ALOGD(" ClipRect %.1f %.1f %.1f %.1f, clip simple %d",
clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, mClipArea->isSimple());
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index af6ad727da85..d8f926ef3925 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -44,9 +44,9 @@ namespace uirenderer {
*/
class RoundRectClipState {
public:
- /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/
+ static void* operator new(size_t size) = delete;
static void* operator new(size_t size, LinearAllocator& allocator) {
- return allocator.alloc(size);
+ return allocator.alloc<RoundRectClipState>(size);
}
bool areaRequiresRoundRectClip(const Rect& rect) const {
@@ -63,11 +63,12 @@ public:
float radius;
};
+// TODO: remove for HWUI_NEW_OPS
class ProjectionPathMask {
public:
- /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/
+ static void* operator new(size_t size) = delete;
static void* operator new(size_t size, LinearAllocator& allocator) {
- return allocator.alloc(size);
+ return allocator.alloc<ProjectionPathMask>(size);
}
const SkPath* projectionMask;
@@ -83,11 +84,11 @@ public:
* Each snapshot has a link to a previous snapshot, indicating the previous
* state of the renderer.
*/
-class Snapshot: public LightRefBase<Snapshot> {
+class Snapshot {
public:
Snapshot();
- Snapshot(const sp<Snapshot>& s, int saveFlags);
+ Snapshot(Snapshot* s, int saveFlags);
/**
* Various flags set on ::flags.
@@ -116,7 +117,7 @@ public:
* Indicates that this snapshot or an ancestor snapshot is
* an FBO layer.
*/
- kFlagFboTarget = 0x8,
+ kFlagFboTarget = 0x8, // TODO: remove for HWUI_NEW_OPS
};
/**
@@ -124,26 +125,25 @@ public:
* the specified operation. The specified rectangle is transformed
* by this snapshot's trasnformation.
*/
- bool clip(float left, float top, float right, float bottom,
- SkRegion::Op op = SkRegion::kIntersect_Op);
+ void clip(const Rect& localClip, SkRegion::Op op);
/**
* Modifies the current clip with the new clip rectangle and
* the specified operation. The specified rectangle is considered
* already transformed.
*/
- bool clipTransformed(const Rect& r, SkRegion::Op op = SkRegion::kIntersect_Op);
+ void clipTransformed(const Rect& r, SkRegion::Op op = SkRegion::kIntersect_Op);
/**
* Modifies the current clip with the specified region and operation.
* The specified region is considered already transformed.
*/
- bool clipRegionTransformed(const SkRegion& region, SkRegion::Op op);
+ void clipRegionTransformed(const SkRegion& region, SkRegion::Op op);
/**
* Modifies the current clip with the specified path and operation.
*/
- bool clipPath(const SkPath& path, SkRegion::Op op);
+ void clipPath(const SkPath& path, SkRegion::Op op);
/**
* Sets the current clip.
@@ -159,16 +159,20 @@ public:
/**
* Returns the current clip in render target coordinates.
*/
- const Rect& getRenderTargetClip() { return mClipArea->getClipRect(); }
+ const Rect& getRenderTargetClip() const { return mClipArea->getClipRect(); }
/*
* Accessor functions so that the clip area can stay private
*/
bool clipIsEmpty() const { return mClipArea->isEmpty(); }
- const Rect& getClipRect() const { return mClipArea->getClipRect(); }
const SkRegion& getClipRegion() const { return mClipArea->getClipRegion(); }
bool clipIsSimple() const { return mClipArea->isSimple(); }
const ClipArea& getClipArea() const { return *mClipArea; }
+ ClipArea& mutateClipArea() { return *mClipArea; }
+
+ WARN_UNUSED_RESULT const ClipBase* serializeIntersectedClip(LinearAllocator& allocator,
+ const ClipBase* recordedClip, const Matrix4& recordedClipTransform);
+ void applyClip(const ClipBase* clip, const Matrix4& transform);
/**
* Resets the clip to the specified rect.
@@ -220,6 +224,7 @@ public:
* Fills outTransform with the current, total transform to screen space,
* across layer boundaries.
*/
+ // TODO: remove for HWUI_NEW_OPS
void buildScreenSpaceTransform(Matrix4* outTransform) const;
/**
@@ -230,7 +235,7 @@ public:
/**
* Previous snapshot.
*/
- sp<Snapshot> previous;
+ Snapshot* previous;
/**
* A pointer to the currently active layer.
@@ -295,9 +300,13 @@ public:
const RoundRectClipState* roundRectClipState;
/**
- * Current projection masking path - used exclusively to mask tessellated circles.
+ * Current projection masking path - used exclusively to mask projected, tessellated circles.
*/
+#if HWUI_NEW_OPS
+ const SkPath* projectionPathMask;
+#else
const ProjectionPathMask* projectionPathMask;
+#endif
void dump() const;
diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
index 7a2b9af2ba57..759e39b2e4e2 100644
--- a/libs/hwui/SpotShadow.cpp
+++ b/libs/hwui/SpotShadow.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
// The highest z value can't be higher than (CASTER_Z_CAP_RATIO * light.z)
#define CASTER_Z_CAP_RATIO 0.95f
@@ -48,17 +46,18 @@
#define TRANSFORMED_PENUMBRA_ALPHA 1.0f
#define TRANSFORMED_UMBRA_ALPHA 0.0f
-#include <algorithm>
-#include <math.h>
-#include <stdlib.h>
-#include <utils/Log.h>
+#include "SpotShadow.h"
#include "ShadowTessellator.h"
-#include "SpotShadow.h"
#include "Vertex.h"
#include "VertexBuffer.h"
#include "utils/MathUtils.h"
+#include <algorithm>
+#include <math.h>
+#include <stdlib.h>
+#include <utils/Log.h>
+
// TODO: After we settle down the new algorithm, we can remove the old one and
// its utility functions.
// Right now, we still need to keep it for comparison purpose and future expansion.
@@ -545,7 +544,7 @@ void SpotShadow::createSpotShadow(bool isCasterOpaque, const Vector3& lightCente
}
float ratioVI = outlineData[i].radius / distOutline;
- minRaitoVI = MathUtils::min(minRaitoVI, ratioVI);
+ minRaitoVI = std::min(minRaitoVI, ratioVI);
if (ratioVI >= (1 - FAKE_UMBRA_SIZE_RATIO)) {
ratioVI = (1 - FAKE_UMBRA_SIZE_RATIO);
}
@@ -742,7 +741,7 @@ inline void genNewPenumbraAndPairWithUmbra(const Vector2* penumbra, int penumbra
// vertex's location.
int newPenumbraNumber = indexDelta - 1;
- float accumulatedDeltaLength[newPenumbraNumber];
+ float accumulatedDeltaLength[indexDelta];
float totalDeltaLength = 0;
// To save time, cache the previous umbra vertex info outside the loop
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp
index 17cb3a7fd6fd..cfdc0848c8b0 100644
--- a/libs/hwui/TessellationCache.cpp
+++ b/libs/hwui/TessellationCache.cpp
@@ -35,13 +35,14 @@ namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
TessellationCache::Description::Description()
- : type(kNone)
+ : type(Type::None)
, scaleX(1.0f)
, scaleY(1.0f)
, aa(false)
, cap(SkPaint::kDefault_Cap)
, style(SkPaint::kFill_Style)
, strokeWidth(1.0f) {
+ // Shape bits should be set to zeroes, because they are used for hash calculation.
memset(&shape, 0, sizeof(Shape));
}
@@ -52,11 +53,30 @@ TessellationCache::Description::Description(Type type, const Matrix4& transform,
, style(paint.getStyle())
, strokeWidth(paint.getStrokeWidth()) {
PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY);
+ // Shape bits should be set to zeroes, because they are used for hash calculation.
memset(&shape, 0, sizeof(Shape));
}
+bool TessellationCache::Description::operator==(const TessellationCache::Description& rhs) const {
+ if (type != rhs.type) return false;
+ if (scaleX != rhs.scaleX) return false;
+ if (scaleY != rhs.scaleY) return false;
+ if (aa != rhs.aa) return false;
+ if (cap != rhs.cap) return false;
+ if (style != rhs.style) return false;
+ if (strokeWidth != rhs.strokeWidth) return false;
+ if (type == Type::None) return true;
+ const Shape::RoundRect& lRect = shape.roundRect;
+ const Shape::RoundRect& rRect = rhs.shape.roundRect;
+
+ if (lRect.width != rRect.width) return false;
+ if (lRect.height != rRect.height) return false;
+ if (lRect.rx != rRect.rx) return false;
+ return lRect.ry == rRect.ry;
+}
+
hash_t TessellationCache::Description::hash() const {
- uint32_t hash = JenkinsHashMix(0, type);
+ uint32_t hash = JenkinsHashMix(0, static_cast<int>(type));
hash = JenkinsHashMix(hash, aa);
hash = JenkinsHashMix(hash, cap);
hash = JenkinsHashMix(hash, style);
@@ -77,17 +97,23 @@ void TessellationCache::Description::setupMatrixAndPaint(Matrix4* matrix, SkPain
TessellationCache::ShadowDescription::ShadowDescription()
: nodeKey(nullptr) {
- memset(&matrixData, 0, 16 * sizeof(float));
+ memset(&matrixData, 0, sizeof(matrixData));
}
-TessellationCache::ShadowDescription::ShadowDescription(const void* nodeKey, const Matrix4* drawTransform)
+TessellationCache::ShadowDescription::ShadowDescription(const SkPath* nodeKey, const Matrix4* drawTransform)
: nodeKey(nodeKey) {
- memcpy(&matrixData, drawTransform->data, 16 * sizeof(float));
+ memcpy(&matrixData, drawTransform->data, sizeof(matrixData));
+}
+
+bool TessellationCache::ShadowDescription::operator==(
+ const TessellationCache::ShadowDescription& rhs) const {
+ return nodeKey == rhs.nodeKey
+ && memcmp(&matrixData, &rhs.matrixData, sizeof(matrixData)) == 0;
}
hash_t TessellationCache::ShadowDescription::hash() const {
uint32_t hash = JenkinsHashMixBytes(0, (uint8_t*) &nodeKey, sizeof(const void*));
- hash = JenkinsHashMixBytes(hash, (uint8_t*) &matrixData, 16 * sizeof(float));
+ hash = JenkinsHashMixBytes(hash, (uint8_t*) &matrixData, sizeof(matrixData));
return JenkinsHashWhiten(hash);
}
@@ -160,45 +186,6 @@ private:
// Shadow tessellation task processing
///////////////////////////////////////////////////////////////////////////////
-class ShadowTask : public Task<TessellationCache::vertexBuffer_pair_t*> {
-public:
- ShadowTask(const Matrix4* drawTransform, const Rect& localClip, bool opaque,
- const SkPath* casterPerimeter, const Matrix4* transformXY, const Matrix4* transformZ,
- const Vector3& lightCenter, float lightRadius)
- : drawTransform(*drawTransform)
- , localClip(localClip)
- , opaque(opaque)
- , casterPerimeter(*casterPerimeter)
- , transformXY(*transformXY)
- , transformZ(*transformZ)
- , lightCenter(lightCenter)
- , lightRadius(lightRadius) {
- }
-
- ~ShadowTask() {
- TessellationCache::vertexBuffer_pair_t* bufferPair = getResult();
- delete bufferPair->getFirst();
- delete bufferPair->getSecond();
- delete bufferPair;
- }
-
- /* Note - we deep copy all task parameters, because *even though* pointers into Allocator
- * controlled objects (like the SkPath and Matrix4s) should be safe for the entire frame,
- * certain Allocators are destroyed before trim() is called to flush incomplete tasks.
- *
- * These deep copies could be avoided, long term, by cancelling or flushing outstanding tasks
- * before tearning down single-frame LinearAllocators.
- */
- const Matrix4 drawTransform;
- const Rect localClip;
- bool opaque;
- const SkPath casterPerimeter;
- const Matrix4 transformXY;
- const Matrix4 transformZ;
- const Vector3 lightCenter;
- const float lightRadius;
-};
-
static void mapPointFakeZ(Vector3& point, const mat4* transformXY, const mat4* transformZ) {
// map z coordinate with true 3d matrix
point.z = transformZ->mapZ(point);
@@ -217,7 +204,7 @@ static void reverseVertexArray(Vertex* polygon, int len) {
}
}
-static void tessellateShadows(
+void tessellateShadows(
const Matrix4* drawTransform, const Rect* localClip,
bool isCasterOpaque, const SkPath* casterPerimeter,
const Matrix4* casterTransformXY, const Matrix4* casterTransformZ,
@@ -225,13 +212,13 @@ static void tessellateShadows(
VertexBuffer& ambientBuffer, VertexBuffer& spotBuffer) {
// tessellate caster outline into a 2d polygon
- Vector<Vertex> casterVertices2d;
+ std::vector<Vertex> casterVertices2d;
const float casterRefinementThreshold = 2.0f;
PathTessellator::approximatePathOutlineVertices(*casterPerimeter,
casterRefinementThreshold, casterVertices2d);
// Shadow requires CCW for now. TODO: remove potential double-reverse
- reverseVertexArray(casterVertices2d.editArray(), casterVertices2d.size());
+ reverseVertexArray(&casterVertices2d.front(), casterVertices2d.size());
if (casterVertices2d.size() == 0) return;
@@ -250,7 +237,7 @@ static void tessellateShadows(
// map the centroid of the caster into 3d
Vector2 centroid = ShadowTessellator::centroid2d(
- reinterpret_cast<const Vector2*>(casterVertices2d.array()),
+ reinterpret_cast<const Vector2*>(&casterVertices2d.front()),
casterVertexCount);
Vector3 centroid3d = {centroid.x, centroid.y, 0};
mapPointFakeZ(centroid3d, casterTransformXY, casterTransformZ);
@@ -281,23 +268,21 @@ static void tessellateShadows(
spotBuffer);
}
-class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t*> {
+class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t> {
public:
ShadowProcessor(Caches& caches)
- : TaskProcessor<TessellationCache::vertexBuffer_pair_t*>(&caches.tasks) {}
+ : TaskProcessor<TessellationCache::vertexBuffer_pair_t>(&caches.tasks) {}
~ShadowProcessor() {}
- virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t*> >& task) override {
- ShadowTask* t = static_cast<ShadowTask*>(task.get());
+ virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t> >& task) override {
+ TessellationCache::ShadowTask* t = static_cast<TessellationCache::ShadowTask*>(task.get());
ATRACE_NAME("shadow tessellation");
- VertexBuffer* ambientBuffer = new VertexBuffer;
- VertexBuffer* spotBuffer = new VertexBuffer;
tessellateShadows(&t->drawTransform, &t->localClip, t->opaque, &t->casterPerimeter,
&t->transformXY, &t->transformZ, t->lightCenter, t->lightRadius,
- *ambientBuffer, *spotBuffer);
+ t->ambientBuffer, t->spotBuffer);
- t->setResult(new TessellationCache::vertexBuffer_pair_t(ambientBuffer, spotBuffer));
+ t->setResult(TessellationCache::vertexBuffer_pair_t(&t->ambientBuffer, &t->spotBuffer));
}
};
@@ -306,18 +291,9 @@ public:
///////////////////////////////////////////////////////////////////////////////
TessellationCache::TessellationCache()
- : mSize(0)
- , mMaxSize(MB(DEFAULT_VERTEX_CACHE_SIZE))
+ : mMaxSize(Properties::tessellationCacheSize)
, mCache(LruCache<Description, Buffer*>::kUnlimitedCapacity)
, mShadowCache(LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*>::kUnlimitedCapacity) {
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_VERTEX_CACHE_SIZE, property, nullptr) > 0) {
- INIT_LOGD(" Setting %s cache size to %sMB", name, property);
- setMaxSize(MB(atof(property)));
- } else {
- INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_VERTEX_CACHE_SIZE);
- }
-
mCache.setOnEntryRemovedListener(&mBufferRemovedListener);
mShadowCache.setOnEntryRemovedListener(&mBufferPairRemovedListener);
mDebugEnabled = Properties::debugLevel & kDebugCaches;
@@ -344,13 +320,6 @@ uint32_t TessellationCache::getMaxSize() {
return mMaxSize;
}
-void TessellationCache::setMaxSize(uint32_t maxSize) {
- mMaxSize = maxSize;
- while (mSize > mMaxSize) {
- mCache.removeOldest();
- }
-}
-
///////////////////////////////////////////////////////////////////////////////
// Caching
///////////////////////////////////////////////////////////////////////////////
@@ -412,7 +381,23 @@ void TessellationCache::getShadowBuffers(const Matrix4* drawTransform, const Rec
task = static_cast<ShadowTask*>(mShadowCache.get(key));
}
LOG_ALWAYS_FATAL_IF(task == nullptr, "shadow not precached");
- outBuffers = *(task->getResult());
+ outBuffers = task->getResult();
+}
+
+sp<TessellationCache::ShadowTask> TessellationCache::getShadowTask(
+ const Matrix4* drawTransform, const Rect& localClip,
+ bool opaque, const SkPath* casterPerimeter,
+ const Matrix4* transformXY, const Matrix4* transformZ,
+ const Vector3& lightCenter, float lightRadius) {
+ ShadowDescription key(casterPerimeter, drawTransform);
+ ShadowTask* task = static_cast<ShadowTask*>(mShadowCache.get(key));
+ if (!task) {
+ precacheShadows(drawTransform, localClip, opaque, casterPerimeter,
+ transformXY, transformZ, lightCenter, lightRadius);
+ task = static_cast<ShadowTask*>(mShadowCache.get(key));
+ }
+ LOG_ALWAYS_FATAL_IF(task == nullptr, "shadow not precached");
+ return task;
}
///////////////////////////////////////////////////////////////////////////////
@@ -469,7 +454,7 @@ static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& d
TessellationCache::Buffer* TessellationCache::getRoundRectBuffer(
const Matrix4& transform, const SkPaint& paint,
float width, float height, float rx, float ry) {
- Description entry(Description::kRoundRect, transform, paint);
+ Description entry(Description::Type::RoundRect, transform, paint);
entry.shape.roundRect.width = width;
entry.shape.roundRect.height = height;
entry.shape.roundRect.rx = rx;
diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h
index 3efeaf64d486..6141b4ef63d7 100644
--- a/libs/hwui/TessellationCache.h
+++ b/libs/hwui/TessellationCache.h
@@ -17,18 +17,24 @@
#ifndef ANDROID_HWUI_TESSELLATION_CACHE_H
#define ANDROID_HWUI_TESSELLATION_CACHE_H
-#include <utils/LruCache.h>
-#include <utils/Mutex.h>
-#include <utils/Vector.h>
-
#include "Debug.h"
+#include "Matrix.h"
+#include "Rect.h"
+#include "Vector.h"
+#include "VertexBuffer.h"
+#include "thread/TaskProcessor.h"
#include "utils/Macros.h"
#include "utils/Pair.h"
+#include <SkPaint.h>
+#include <SkPath.h>
+
+#include <utils/LruCache.h>
+#include <utils/Mutex.h>
+#include <utils/StrongPointer.h>
+
class SkBitmap;
class SkCanvas;
-class SkPaint;
-class SkPath;
struct SkRect;
namespace android {
@@ -46,10 +52,10 @@ public:
typedef Pair<VertexBuffer*, VertexBuffer*> vertexBuffer_pair_t;
struct Description {
- DESCRIPTION_TYPE(Description);
- enum Type {
- kNone,
- kRoundRect,
+ HASHABLE_TYPE(Description);
+ enum class Type {
+ None,
+ RoundRect,
};
Type type;
@@ -70,18 +76,50 @@ public:
Description();
Description(Type type, const Matrix4& transform, const SkPaint& paint);
- hash_t hash() const;
void setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const;
};
struct ShadowDescription {
- DESCRIPTION_TYPE(ShadowDescription);
- const void* nodeKey;
+ HASHABLE_TYPE(ShadowDescription);
+ const SkPath* nodeKey;
float matrixData[16];
ShadowDescription();
- ShadowDescription(const void* nodeKey, const Matrix4* drawTransform);
- hash_t hash() const;
+ ShadowDescription(const SkPath* nodeKey, const Matrix4* drawTransform);
+ };
+
+ class ShadowTask : public Task<vertexBuffer_pair_t> {
+ public:
+ ShadowTask(const Matrix4* drawTransform, const Rect& localClip, bool opaque,
+ const SkPath* casterPerimeter, const Matrix4* transformXY, const Matrix4* transformZ,
+ const Vector3& lightCenter, float lightRadius)
+ : drawTransform(*drawTransform)
+ , localClip(localClip)
+ , opaque(opaque)
+ , casterPerimeter(*casterPerimeter)
+ , transformXY(*transformXY)
+ , transformZ(*transformZ)
+ , lightCenter(lightCenter)
+ , lightRadius(lightRadius) {
+ }
+
+ /* Note - we deep copy all task parameters, because *even though* pointers into Allocator
+ * controlled objects (like the SkPath and Matrix4s) should be safe for the entire frame,
+ * certain Allocators are destroyed before trim() is called to flush incomplete tasks.
+ *
+ * These deep copies could be avoided, long term, by canceling or flushing outstanding
+ * tasks before tearing down single-frame LinearAllocators.
+ */
+ const Matrix4 drawTransform;
+ const Rect localClip;
+ bool opaque;
+ const SkPath casterPerimeter;
+ const Matrix4 transformXY;
+ const Matrix4 transformZ;
+ const Vector3 lightCenter;
+ const float lightRadius;
+ VertexBuffer ambientBuffer;
+ VertexBuffer spotBuffer;
};
TessellationCache();
@@ -91,11 +129,6 @@ public:
* Clears the cache. This causes all TessellationBuffers to be deleted.
*/
void clear();
-
- /**
- * Sets the maximum size of the cache in bytes.
- */
- void setMaxSize(uint32_t maxSize);
/**
* Returns the maximum size of the cache in bytes.
*/
@@ -128,17 +161,22 @@ public:
const VertexBuffer* getRoundRect(const Matrix4& transform, const SkPaint& paint,
float width, float height, float rx, float ry);
+ // TODO: delete these when switching to HWUI_NEW_OPS
void precacheShadows(const Matrix4* drawTransform, const Rect& localClip,
bool opaque, const SkPath* casterPerimeter,
const Matrix4* transformXY, const Matrix4* transformZ,
const Vector3& lightCenter, float lightRadius);
-
void getShadowBuffers(const Matrix4* drawTransform, const Rect& localClip,
bool opaque, const SkPath* casterPerimeter,
const Matrix4* transformXY, const Matrix4* transformZ,
const Vector3& lightCenter, float lightRadius,
vertexBuffer_pair_t& outBuffers);
+ sp<ShadowTask> getShadowTask(const Matrix4* drawTransform, const Rect& localClip,
+ bool opaque, const SkPath* casterPerimeter,
+ const Matrix4* transformXY, const Matrix4* transformZ,
+ const Vector3& lightCenter, float lightRadius);
+
private:
class Buffer;
class TessellationTask;
@@ -153,8 +191,7 @@ private:
Buffer* getOrCreateBuffer(const Description& entry, Tessellator tessellator);
- uint32_t mSize;
- uint32_t mMaxSize;
+ const uint32_t mMaxSize;
bool mDebugEnabled;
@@ -173,12 +210,12 @@ private:
///////////////////////////////////////////////////////////////////////////////
// Shadow tessellation caching
///////////////////////////////////////////////////////////////////////////////
- sp<TaskProcessor<vertexBuffer_pair_t*> > mShadowProcessor;
+ sp<TaskProcessor<vertexBuffer_pair_t> > mShadowProcessor;
// holds a pointer, and implicit strong ref to each shadow task of the frame
- LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*> mShadowCache;
- class BufferPairRemovedListener : public OnEntryRemoved<ShadowDescription, Task<vertexBuffer_pair_t*>*> {
- void operator()(ShadowDescription& description, Task<vertexBuffer_pair_t*>*& bufferPairTask) override {
+ LruCache<ShadowDescription, Task<vertexBuffer_pair_t>*> mShadowCache;
+ class BufferPairRemovedListener : public OnEntryRemoved<ShadowDescription, Task<vertexBuffer_pair_t>*> {
+ void operator()(ShadowDescription& description, Task<vertexBuffer_pair_t>*& bufferPairTask) override {
bufferPairTask->decStrong(nullptr);
}
};
@@ -186,6 +223,13 @@ private:
}; // class TessellationCache
+void tessellateShadows(
+ const Matrix4* drawTransform, const Rect* localClip,
+ bool isCasterOpaque, const SkPath* casterPerimeter,
+ const Matrix4* casterTransformXY, const Matrix4* casterTransformZ,
+ const Vector3& lightCenter, float lightRadius,
+ VertexBuffer& ambientBuffer, VertexBuffer& spotBuffer);
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp
index 8b1d4cb2196c..e1f0b2a20172 100644
--- a/libs/hwui/TextDropShadowCache.cpp
+++ b/libs/hwui/TextDropShadowCache.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <utils/JenkinsHash.h>
#include "Caches.h"
@@ -32,20 +30,19 @@ namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
hash_t ShadowText::hash() const {
- uint32_t charCount = len / sizeof(char16_t);
- uint32_t hash = JenkinsHashMix(0, len);
+ uint32_t hash = JenkinsHashMix(0, glyphCount);
hash = JenkinsHashMix(hash, android::hash_type(radius));
hash = JenkinsHashMix(hash, android::hash_type(textSize));
hash = JenkinsHashMix(hash, android::hash_type(typeface));
hash = JenkinsHashMix(hash, flags);
hash = JenkinsHashMix(hash, android::hash_type(italicStyle));
hash = JenkinsHashMix(hash, android::hash_type(scaleX));
- if (text) {
+ if (glyphs) {
hash = JenkinsHashMixShorts(
- hash, reinterpret_cast<const uint16_t*>(text), charCount);
+ hash, reinterpret_cast<const uint16_t*>(glyphs), glyphCount);
}
if (positions) {
- for (uint32_t i = 0; i < charCount * 2; i++) {
+ for (uint32_t i = 0; i < glyphCount * 2; i++) {
hash = JenkinsHashMix(hash, android::hash_type(positions[i]));
}
}
@@ -53,7 +50,7 @@ hash_t ShadowText::hash() const {
}
int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) {
- int deltaInt = int(lhs.len) - int(rhs.len);
+ int deltaInt = int(lhs.glyphCount) - int(rhs.glyphCount);
if (deltaInt != 0) return deltaInt;
deltaInt = lhs.flags - rhs.flags;
@@ -74,11 +71,11 @@ int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) {
if (lhs.scaleX < rhs.scaleX) return -1;
if (lhs.scaleX > rhs.scaleX) return +1;
- if (lhs.text != rhs.text) {
- if (!lhs.text) return -1;
- if (!rhs.text) return +1;
+ if (lhs.glyphs != rhs.glyphs) {
+ if (!lhs.glyphs) return -1;
+ if (!rhs.glyphs) return +1;
- deltaInt = memcmp(lhs.text, rhs.text, lhs.len);
+ deltaInt = memcmp(lhs.glyphs, rhs.glyphs, lhs.glyphCount * sizeof(glyph_t));
if (deltaInt != 0) return deltaInt;
}
@@ -86,7 +83,7 @@ int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) {
if (!lhs.positions) return -1;
if (!rhs.positions) return +1;
- return memcmp(lhs.positions, rhs.positions, lhs.len << 2);
+ return memcmp(lhs.positions, rhs.positions, lhs.glyphCount * sizeof(float) * 2);
}
return 0;
@@ -96,36 +93,21 @@ int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) {
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
-TextDropShadowCache::TextDropShadowCache():
- mCache(LruCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity),
- mSize(0), mMaxSize(MB(DEFAULT_DROP_SHADOW_CACHE_SIZE)) {
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_DROP_SHADOW_CACHE_SIZE, property, nullptr) > 0) {
- INIT_LOGD(" Setting drop shadow cache size to %sMB", property);
- setMaxSize(MB(atof(property)));
- } else {
- INIT_LOGD(" Using default drop shadow cache size of %.2fMB",
- DEFAULT_DROP_SHADOW_CACHE_SIZE);
- }
-
- init();
-}
+TextDropShadowCache::TextDropShadowCache()
+ : TextDropShadowCache(Properties::textDropShadowCacheSize) {}
-TextDropShadowCache::TextDropShadowCache(uint32_t maxByteSize):
- mCache(LruCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity),
- mSize(0), mMaxSize(maxByteSize) {
- init();
+TextDropShadowCache::TextDropShadowCache(uint32_t maxByteSize)
+ : mCache(LruCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity)
+ , mSize(0)
+ , mMaxSize(maxByteSize) {
+ mCache.setOnEntryRemovedListener(this);
+ mDebugEnabled = Properties::debugLevel & kDebugMoreCaches;
}
TextDropShadowCache::~TextDropShadowCache() {
mCache.clear();
}
-void TextDropShadowCache::init() {
- mCache.setOnEntryRemovedListener(this);
- mDebugEnabled = Properties::debugLevel & kDebugMoreCaches;
-}
-
///////////////////////////////////////////////////////////////////////////////
// Size management
///////////////////////////////////////////////////////////////////////////////
@@ -138,20 +120,13 @@ uint32_t TextDropShadowCache::getMaxSize() {
return mMaxSize;
}
-void TextDropShadowCache::setMaxSize(uint32_t maxSize) {
- mMaxSize = maxSize;
- while (mSize > mMaxSize) {
- mCache.removeOldest();
- }
-}
-
///////////////////////////////////////////////////////////////////////////////
// Callbacks
///////////////////////////////////////////////////////////////////////////////
void TextDropShadowCache::operator()(ShadowText&, ShadowTexture*& texture) {
if (texture) {
- mSize -= texture->bitmapSize;
+ mSize -= texture->objectSize();
if (mDebugEnabled) {
ALOGD("Shadow texture deleted, size = %d", texture->bitmapSize);
@@ -170,16 +145,16 @@ void TextDropShadowCache::clear() {
mCache.clear();
}
-ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text, uint32_t len,
- int numGlyphs, float radius, const float* positions) {
- ShadowText entry(paint, radius, len, text, positions);
+ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs,
+ float radius, const float* positions) {
+ ShadowText entry(paint, radius, numGlyphs, glyphs, positions);
ShadowTexture* texture = mCache.get(entry);
if (!texture) {
SkPaint paintCopy(*paint);
paintCopy.setTextAlign(SkPaint::kLeft_Align);
- FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, text, 0,
- len, numGlyphs, radius, positions);
+ FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, glyphs, numGlyphs,
+ radius, positions);
if (!shadow.image) {
return nullptr;
@@ -190,30 +165,23 @@ ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text,
texture = new ShadowTexture(caches);
texture->left = shadow.penX;
texture->top = shadow.penY;
- texture->width = shadow.width;
- texture->height = shadow.height;
texture->generation = 0;
texture->blend = true;
const uint32_t size = shadow.width * shadow.height;
- texture->bitmapSize = size;
// Don't even try to cache a bitmap that's bigger than the cache
if (size < mMaxSize) {
while (mSize + size > mMaxSize) {
- mCache.removeOldest();
+ LOG_ALWAYS_FATAL_IF(!mCache.removeOldest(),
+ "Failed to remove oldest from cache. mSize = %"
+ PRIu32 ", mCache.size() = %zu", mSize, mCache.size());
}
}
- glGenTextures(1, &texture->id);
-
- caches.textureState().bindTexture(texture->id);
// Textures are Alpha8
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-
- glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
+ texture->upload(GL_ALPHA, shadow.width, shadow.height,
GL_ALPHA, GL_UNSIGNED_BYTE, shadow.image);
-
texture->setFilter(GL_LINEAR);
texture->setWrap(GL_CLAMP_TO_EDGE);
@@ -224,7 +192,7 @@ ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text,
entry.copyTextLocally();
- mSize += size;
+ mSize += texture->objectSize();
mCache.put(entry, texture);
} else {
texture->cleanup = true;
diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h
index caf089f6d2a5..d536c40756ff 100644
--- a/libs/hwui/TextDropShadowCache.h
+++ b/libs/hwui/TextDropShadowCache.h
@@ -34,27 +34,22 @@ class Caches;
class FontRenderer;
struct ShadowText {
- ShadowText(): len(0), radius(0.0f), textSize(0.0f), typeface(nullptr),
- flags(0), italicStyle(0.0f), scaleX(0), text(nullptr), positions(nullptr) {
+ ShadowText(): glyphCount(0), radius(0.0f), textSize(0.0f), typeface(nullptr),
+ flags(0), italicStyle(0.0f), scaleX(0), glyphs(nullptr), positions(nullptr) {
}
// len is the number of bytes in text
- ShadowText(const SkPaint* paint, float radius, uint32_t len, const char* srcText,
- const float* positions):
- len(len), radius(radius), positions(positions) {
- // TODO: Propagate this through the API, we should not cast here
- text = (const char16_t*) srcText;
-
- textSize = paint->getTextSize();
- typeface = paint->getTypeface();
-
- flags = 0;
- if (paint->isFakeBoldText()) {
- flags |= Font::kFakeBold;
- }
-
- italicStyle = paint->getTextSkewX();
- scaleX = paint->getTextScaleX();
+ ShadowText(const SkPaint* paint, float radius, uint32_t glyphCount, const glyph_t* srcGlyphs,
+ const float* positions)
+ : glyphCount(glyphCount)
+ , radius(radius)
+ , textSize(paint->getTextSize())
+ , typeface(paint->getTypeface())
+ , flags(paint->isFakeBoldText() ? Font::kFakeBold : 0)
+ , italicStyle(paint->getTextSkewX())
+ , scaleX(paint->getTextScaleX())
+ , glyphs(srcGlyphs)
+ , positions(positions) {
}
~ShadowText() {
@@ -73,24 +68,23 @@ struct ShadowText {
}
void copyTextLocally() {
- uint32_t charCount = len / sizeof(char16_t);
- str.setTo((const char16_t*) text, charCount);
- text = str.string();
+ str.setTo(reinterpret_cast<const char16_t*>(glyphs), glyphCount);
+ glyphs = reinterpret_cast<const glyph_t*>(str.string());
if (positions != nullptr) {
positionsCopy.clear();
- positionsCopy.appendArray(positions, charCount * 2);
+ positionsCopy.appendArray(positions, glyphCount * 2);
positions = positionsCopy.array();
}
}
- uint32_t len;
+ uint32_t glyphCount;
float radius;
float textSize;
SkTypeface* typeface;
uint32_t flags;
float italicStyle;
float scaleX;
- const char16_t* text;
+ const glyph_t* glyphs;
const float* positions;
// Not directly used to compute the cache key
@@ -136,7 +130,7 @@ public:
*/
void operator()(ShadowText& text, ShadowTexture*& texture) override;
- ShadowTexture* get(const SkPaint* paint, const char* text, uint32_t len,
+ ShadowTexture* get(const SkPaint* paint, const glyph_t* text,
int numGlyphs, float radius, const float* positions);
/**
@@ -149,10 +143,6 @@ public:
}
/**
- * Sets the maximum size of the cache in bytes.
- */
- void setMaxSize(uint32_t maxSize);
- /**
* Returns the maximum size of the cache in bytes.
*/
uint32_t getMaxSize();
@@ -162,13 +152,11 @@ public:
uint32_t getSize();
private:
- void init();
-
LruCache<ShadowText, ShadowTexture*> mCache;
uint32_t mSize;
- uint32_t mMaxSize;
- FontRenderer* mRenderer;
+ const uint32_t mMaxSize;
+ FontRenderer* mRenderer = nullptr;
bool mDebugEnabled;
}; // class TextDropShadowCache
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 593e91818093..4f49a3518be0 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -14,27 +14,45 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
+#include "Caches.h"
+#include "Texture.h"
+#include "utils/GLUtils.h"
+#include "utils/TraceUtils.h"
#include <utils/Log.h>
-#include "Caches.h"
-#include "Texture.h"
+#include <SkCanvas.h>
namespace android {
namespace uirenderer {
+static int bytesPerPixel(GLint glFormat) {
+ switch (glFormat) {
+ // The wrapped-texture case, usually means a SurfaceTexture
+ case 0:
+ return 0;
+ case GL_ALPHA:
+ return 1;
+ case GL_RGB:
+ return 3;
+ case GL_RGBA:
+ return 4;
+ case GL_RGBA16F:
+ return 16;
+ default:
+ LOG_ALWAYS_FATAL("UNKNOWN FORMAT %d", glFormat);
+ }
+}
+
void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force,
GLenum renderTarget) {
- if (mFirstWrap || force || wrapS != mWrapS || wrapT != mWrapT) {
- mFirstWrap = false;
-
+ if (force || wrapS != mWrapS || wrapT != mWrapT) {
mWrapS = wrapS;
mWrapT = wrapT;
if (bindTexture) {
- mCaches.textureState().bindTexture(renderTarget, id);
+ mCaches.textureState().bindTexture(renderTarget, mId);
}
glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS);
@@ -45,14 +63,12 @@ void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force
void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool force,
GLenum renderTarget) {
- if (mFirstFilter || force || min != mMinFilter || mag != mMagFilter) {
- mFirstFilter = false;
-
+ if (force || min != mMinFilter || mag != mMagFilter) {
mMinFilter = min;
mMagFilter = mag;
if (bindTexture) {
- mCaches.textureState().bindTexture(renderTarget, id);
+ mCaches.textureState().bindTexture(renderTarget, mId);
}
if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR;
@@ -62,8 +78,197 @@ void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool for
}
}
-void Texture::deleteTexture() const {
- mCaches.textureState().deleteTexture(id);
+void Texture::deleteTexture() {
+ mCaches.textureState().deleteTexture(mId);
+ mId = 0;
+}
+
+bool Texture::updateSize(uint32_t width, uint32_t height, GLint format) {
+ if (mWidth == width && mHeight == height && mFormat == format) {
+ return false;
+ }
+ mWidth = width;
+ mHeight = height;
+ mFormat = format;
+ notifySizeChanged(mWidth * mHeight * bytesPerPixel(mFormat));
+ return true;
+}
+
+void Texture::resetCachedParams() {
+ mWrapS = GL_REPEAT;
+ mWrapT = GL_REPEAT;
+ mMinFilter = GL_NEAREST_MIPMAP_LINEAR;
+ mMagFilter = GL_LINEAR;
+}
+
+void Texture::upload(GLint internalformat, uint32_t width, uint32_t height,
+ GLenum format, GLenum type, const void* pixels) {
+ GL_CHECKPOINT(MODERATE);
+ bool needsAlloc = updateSize(width, height, internalformat);
+ if (!mId) {
+ glGenTextures(1, &mId);
+ needsAlloc = true;
+ resetCachedParams();
+ }
+ mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId);
+ if (needsAlloc) {
+ glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
+ format, type, pixels);
+ } else if (pixels) {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
+ format, type, pixels);
+ }
+ GL_CHECKPOINT(MODERATE);
+}
+
+static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei stride, GLsizei bpp,
+ GLsizei width, GLsizei height, const GLvoid * data) {
+
+ const bool useStride = stride != width
+ && Caches::getInstance().extensions().hasUnpackRowLength();
+ if ((stride == width) || useStride) {
+ if (useStride) {
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
+ }
+
+ if (resize) {
+ glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
+ } else {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
+ }
+
+ if (useStride) {
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ }
+ } else {
+ // With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer
+ // if the stride doesn't match the width
+
+ GLvoid * temp = (GLvoid *) malloc(width * height * bpp);
+ if (!temp) return;
+
+ uint8_t * pDst = (uint8_t *)temp;
+ uint8_t * pSrc = (uint8_t *)data;
+ for (GLsizei i = 0; i < height; i++) {
+ memcpy(pDst, pSrc, width * bpp);
+ pDst += width * bpp;
+ pSrc += stride * bpp;
+ }
+
+ if (resize) {
+ glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp);
+ } else {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp);
+ }
+
+ free(temp);
+ }
+}
+
+static void uploadSkBitmapToTexture(const SkBitmap& bitmap,
+ bool resize, GLenum format, GLenum type) {
+ uploadToTexture(resize, format, type, bitmap.rowBytesAsPixels(), bitmap.bytesPerPixel(),
+ bitmap.width(), bitmap.height(), bitmap.getPixels());
+}
+
+static void colorTypeToGlFormatAndType(SkColorType colorType,
+ GLint* outFormat, GLint* outType) {
+ switch (colorType) {
+ case kAlpha_8_SkColorType:
+ *outFormat = GL_ALPHA;
+ *outType = GL_UNSIGNED_BYTE;
+ break;
+ case kRGB_565_SkColorType:
+ *outFormat = GL_RGB;
+ *outType = GL_UNSIGNED_SHORT_5_6_5;
+ break;
+ // ARGB_4444 and Index_8 are both upconverted to RGBA_8888
+ case kARGB_4444_SkColorType:
+ case kIndex_8_SkColorType:
+ case kN32_SkColorType:
+ *outFormat = GL_RGBA;
+ *outType = GL_UNSIGNED_BYTE;
+ break;
+ case kGray_8_SkColorType:
+ *outFormat = GL_LUMINANCE;
+ *outType = GL_UNSIGNED_BYTE;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType);
+ break;
+ }
+}
+
+void Texture::upload(const SkBitmap& bitmap) {
+ SkAutoLockPixels alp(bitmap);
+
+ if (!bitmap.readyToDraw()) {
+ ALOGE("Cannot generate texture from bitmap");
+ return;
+ }
+
+ ATRACE_FORMAT("Upload %ux%u Texture", bitmap.width(), bitmap.height());
+
+ // We could also enable mipmapping if both bitmap dimensions are powers
+ // of 2 but we'd have to deal with size changes. Let's keep this simple
+ const bool canMipMap = mCaches.extensions().hasNPot();
+
+ // If the texture had mipmap enabled but not anymore,
+ // force a glTexImage2D to discard the mipmap levels
+ bool needsAlloc = canMipMap && mipMap && !bitmap.hasHardwareMipMap();
+ bool setDefaultParams = false;
+
+ if (!mId) {
+ glGenTextures(1, &mId);
+ needsAlloc = true;
+ setDefaultParams = true;
+ }
+
+ GLint format, type;
+ colorTypeToGlFormatAndType(bitmap.colorType(), &format, &type);
+
+ if (updateSize(bitmap.width(), bitmap.height(), format)) {
+ needsAlloc = true;
+ }
+
+ blend = !bitmap.isOpaque();
+ mCaches.textureState().bindTexture(mId);
+
+ if (CC_UNLIKELY(bitmap.colorType() == kARGB_4444_SkColorType
+ || bitmap.colorType() == kIndex_8_SkColorType)) {
+ SkBitmap rgbaBitmap;
+ rgbaBitmap.allocPixels(SkImageInfo::MakeN32(mWidth, mHeight,
+ bitmap.alphaType()));
+ rgbaBitmap.eraseColor(0);
+
+ SkCanvas canvas(rgbaBitmap);
+ canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr);
+
+ uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, format, type);
+ } else {
+ uploadSkBitmapToTexture(bitmap, needsAlloc, format, type);
+ }
+
+ if (canMipMap) {
+ mipMap = bitmap.hasHardwareMipMap();
+ if (mipMap) {
+ glGenerateMipmap(GL_TEXTURE_2D);
+ }
+ }
+
+ if (setDefaultParams) {
+ setFilter(GL_NEAREST);
+ setWrap(GL_CLAMP_TO_EDGE);
+ }
+}
+
+void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint format) {
+ mId = id;
+ mWidth = width;
+ mHeight = height;
+ mFormat = format;
+ // We're wrapping an existing texture, so don't double count this memory
+ notifySizeChanged(0);
}
}; // namespace uirenderer
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 4bcd96dd32f7..9749f734fd1f 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -17,20 +17,27 @@
#ifndef ANDROID_HWUI_TEXTURE_H
#define ANDROID_HWUI_TEXTURE_H
+#include "GpuMemoryTracker.h"
+
#include <GLES2/gl2.h>
+#include <SkBitmap.h>
namespace android {
namespace uirenderer {
class Caches;
class UvMapper;
+class Layer;
/**
* Represents an OpenGL texture.
*/
-class Texture {
+class Texture : public GpuMemoryTracker {
public:
- Texture(Caches& caches) : mCaches(caches) { }
+ Texture(Caches& caches)
+ : GpuMemoryTracker(GpuObjectType::Texture)
+ , mCaches(caches)
+ { }
virtual ~Texture() { }
@@ -53,28 +60,63 @@ public:
/**
* Convenience method to call glDeleteTextures() on this texture's id.
*/
- void deleteTexture() const;
+ void deleteTexture();
/**
- * Name of the texture.
+ * Sets the width, height, and format of the texture along with allocating
+ * the texture ID. Does nothing if the width, height, and format are already
+ * the requested values.
+ *
+ * The image data is undefined after calling this.
*/
- GLuint id = 0;
+ void resize(uint32_t width, uint32_t height, GLint format) {
+ upload(format, width, height, format, GL_UNSIGNED_BYTE, nullptr);
+ }
+
/**
- * Generation of the backing bitmap,
+ * Updates this Texture with the contents of the provided SkBitmap,
+ * also setting the appropriate width, height, and format. It is not necessary
+ * to call resize() prior to this.
+ *
+ * Note this does not set the generation from the SkBitmap.
*/
- uint32_t generation = 0;
+ void upload(const SkBitmap& source);
+
/**
- * Indicates whether the texture requires blending.
+ * Basically glTexImage2D/glTexSubImage2D.
*/
- bool blend = false;
+ void upload(GLint internalformat, uint32_t width, uint32_t height,
+ GLenum format, GLenum type, const void* pixels);
+
+ /**
+ * Wraps an existing texture.
+ */
+ void wrap(GLuint id, uint32_t width, uint32_t height, GLint format);
+
+ GLuint id() const {
+ return mId;
+ }
+
+ uint32_t width() const {
+ return mWidth;
+ }
+
+ uint32_t height() const {
+ return mHeight;
+ }
+
+ GLint format() const {
+ return mFormat;
+ }
+
/**
- * Width of the backing bitmap.
+ * Generation of the backing bitmap,
*/
- uint32_t width = 0;
+ uint32_t generation = 0;
/**
- * Height of the backing bitmap.
+ * Indicates whether the texture requires blending.
*/
- uint32_t height = 0;
+ bool blend = false;
/**
* Indicates whether this texture should be cleaned up after use.
*/
@@ -100,36 +142,45 @@ public:
void* isInUse = nullptr;
private:
- /**
- * Last wrap modes set on this texture.
+ // TODO: Temporarily grant private access to Layer, remove once
+ // Layer can be de-tangled from being a dual-purpose render target
+ // and external texture wrapper
+ friend class Layer;
+
+ // Returns true if the size changed, false if it was the same
+ bool updateSize(uint32_t width, uint32_t height, GLint format);
+ void resetCachedParams();
+
+ GLuint mId = 0;
+ uint32_t mWidth = 0;
+ uint32_t mHeight = 0;
+ GLint mFormat = 0;
+
+ /* See GLES spec section 3.8.14
+ * "In the initial state, the value assigned to TEXTURE_MIN_FILTER is
+ * NEAREST_MIPMAP_LINEAR and the value for TEXTURE_MAG_FILTER is LINEAR.
+ * s, t, and r wrap modes are all set to REPEAT."
*/
- GLenum mWrapS = GL_CLAMP_TO_EDGE;
- GLenum mWrapT = GL_CLAMP_TO_EDGE;
-
- /**
- * Last filters set on this texture.
- */
- GLenum mMinFilter = GL_NEAREST;
- GLenum mMagFilter = GL_NEAREST;
-
- bool mFirstFilter = true;
- bool mFirstWrap = true;
+ GLenum mWrapS = GL_REPEAT;
+ GLenum mWrapT = GL_REPEAT;
+ GLenum mMinFilter = GL_NEAREST_MIPMAP_LINEAR;
+ GLenum mMagFilter = GL_LINEAR;
Caches& mCaches;
}; // struct Texture
class AutoTexture {
public:
- AutoTexture(const Texture* texture): mTexture(texture) { }
+ AutoTexture(Texture* texture)
+ : texture(texture) {}
~AutoTexture() {
- if (mTexture && mTexture->cleanup) {
- mTexture->deleteTexture();
- delete mTexture;
+ if (texture && texture->cleanup) {
+ texture->deleteTexture();
+ delete texture;
}
}
-private:
- const Texture* mTexture;
+ Texture* const texture;
}; // class AutoTexture
}; // namespace uirenderer
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index fda009108aba..ade8600ab78b 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -14,14 +14,8 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-#define ATRACE_TAG ATRACE_TAG_VIEW
-
#include <GLES2/gl2.h>
-#include <SkCanvas.h>
-#include <SkPixelRef.h>
-
#include <utils/Mutex.h>
#include "AssetAtlas.h"
@@ -41,26 +35,9 @@ namespace uirenderer {
TextureCache::TextureCache()
: mCache(LruCache<uint32_t, Texture*>::kUnlimitedCapacity)
, mSize(0)
- , mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE))
- , mFlushRate(DEFAULT_TEXTURE_CACHE_FLUSH_RATE)
+ , mMaxSize(Properties::textureCacheSize)
+ , mFlushRate(Properties::textureCacheFlushRate)
, mAssetAtlas(nullptr) {
- char property[PROPERTY_VALUE_MAX];
- if (property_get(PROPERTY_TEXTURE_CACHE_SIZE, property, nullptr) > 0) {
- INIT_LOGD(" Setting texture cache size to %sMB", property);
- setMaxSize(MB(atof(property)));
- } else {
- INIT_LOGD(" Using default texture cache size of %.2fMB", DEFAULT_TEXTURE_CACHE_SIZE);
- }
-
- if (property_get(PROPERTY_TEXTURE_CACHE_FLUSH_RATE, property, nullptr) > 0) {
- float flushRate = atof(property);
- INIT_LOGD(" Setting texture cache flush rate to %.2f%%", flushRate * 100.0f);
- setFlushRate(flushRate);
- } else {
- INIT_LOGD(" Using default texture cache flush rate of %.2f%%",
- DEFAULT_TEXTURE_CACHE_FLUSH_RATE * 100.0f);
- }
-
mCache.setOnEntryRemovedListener(this);
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
@@ -85,17 +62,6 @@ uint32_t TextureCache::getMaxSize() {
return mMaxSize;
}
-void TextureCache::setMaxSize(uint32_t maxSize) {
- mMaxSize = maxSize;
- while (mSize > mMaxSize) {
- mCache.removeOldest();
- }
-}
-
-void TextureCache::setFlushRate(float flushRate) {
- mFlushRate = std::max(0.0f, std::min(1.0f, flushRate));
-}
-
///////////////////////////////////////////////////////////////////////////////
// Callbacks
///////////////////////////////////////////////////////////////////////////////
@@ -144,7 +110,7 @@ bool TextureCache::canMakeTextureFromBitmap(const SkBitmap* bitmap) {
// in the cache (and is thus added to the cache)
Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) {
if (CC_LIKELY(mAssetAtlas != nullptr) && atlasUsageType == AtlasUsageType::Use) {
- AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap);
+ AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap->pixelRef());
if (CC_UNLIKELY(entry)) {
return entry->texture;
}
@@ -172,7 +138,8 @@ Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType a
if (canCache) {
texture = new Texture(Caches::getInstance());
texture->bitmapSize = size;
- generateTexture(bitmap, texture, false);
+ texture->generation = bitmap->getGenerationID();
+ texture->upload(*bitmap);
mSize += size;
TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d",
@@ -185,7 +152,8 @@ Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType a
} else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) {
// Texture was in the cache but is dirty, re-upload
// TODO: Re-adjust the cache size if the bitmap's dimensions have changed
- generateTexture(bitmap, texture, true);
+ texture->upload(*bitmap);
+ texture->generation = bitmap->getGenerationID();
}
return texture;
@@ -210,7 +178,8 @@ Texture* TextureCache::get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType
const uint32_t size = bitmap->rowBytes() * bitmap->height();
texture = new Texture(Caches::getInstance());
texture->bitmapSize = size;
- generateTexture(bitmap, texture, false);
+ texture->upload(*bitmap);
+ texture->generation = bitmap->getGenerationID();
texture->cleanup = true;
}
@@ -219,14 +188,14 @@ Texture* TextureCache::get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType
void TextureCache::releaseTexture(uint32_t pixelRefStableID) {
Mutex::Autolock _l(mLock);
- mGarbage.push(pixelRefStableID);
+ mGarbage.push_back(pixelRefStableID);
}
void TextureCache::clearGarbage() {
Mutex::Autolock _l(mLock);
size_t count = mGarbage.size();
for (size_t i = 0; i < count; i++) {
- uint32_t pixelRefId = mGarbage.itemAt(i);
+ uint32_t pixelRefId = mGarbage[i];
mCache.remove(pixelRefId);
}
mGarbage.clear();
@@ -252,133 +221,5 @@ void TextureCache::flush() {
}
}
-void TextureCache::generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate) {
- SkAutoLockPixels alp(*bitmap);
-
- if (!bitmap->readyToDraw()) {
- ALOGE("Cannot generate texture from bitmap");
- return;
- }
-
- ATRACE_FORMAT("Upload %ux%u Texture", bitmap->width(), bitmap->height());
-
- // We could also enable mipmapping if both bitmap dimensions are powers
- // of 2 but we'd have to deal with size changes. Let's keep this simple
- const bool canMipMap = Caches::getInstance().extensions().hasNPot();
-
- // If the texture had mipmap enabled but not anymore,
- // force a glTexImage2D to discard the mipmap levels
- const bool resize = !regenerate || bitmap->width() != int(texture->width) ||
- bitmap->height() != int(texture->height) ||
- (regenerate && canMipMap && texture->mipMap && !bitmap->hasHardwareMipMap());
-
- if (!regenerate) {
- glGenTextures(1, &texture->id);
- }
-
- texture->generation = bitmap->getGenerationID();
- texture->width = bitmap->width();
- texture->height = bitmap->height();
-
- Caches::getInstance().textureState().bindTexture(texture->id);
-
- switch (bitmap->colorType()) {
- case kAlpha_8_SkColorType:
- uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(),
- texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels());
- texture->blend = true;
- break;
- case kRGB_565_SkColorType:
- uploadToTexture(resize, GL_RGB, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(),
- texture->width, texture->height, GL_UNSIGNED_SHORT_5_6_5, bitmap->getPixels());
- texture->blend = false;
- break;
- case kN32_SkColorType:
- uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(),
- texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels());
- // Do this after calling getPixels() to make sure Skia's deferred
- // decoding happened
- texture->blend = !bitmap->isOpaque();
- break;
- case kARGB_4444_SkColorType:
- case kIndex_8_SkColorType:
- uploadLoFiTexture(resize, bitmap, texture->width, texture->height);
- texture->blend = !bitmap->isOpaque();
- break;
- default:
- ALOGW("Unsupported bitmap colorType: %d", bitmap->colorType());
- break;
- }
-
- if (canMipMap) {
- texture->mipMap = bitmap->hasHardwareMipMap();
- if (texture->mipMap) {
- glGenerateMipmap(GL_TEXTURE_2D);
- }
- }
-
- if (!regenerate) {
- texture->setFilter(GL_NEAREST);
- texture->setWrap(GL_CLAMP_TO_EDGE);
- }
-}
-
-void TextureCache::uploadLoFiTexture(bool resize, const SkBitmap* bitmap,
- uint32_t width, uint32_t height) {
- SkBitmap rgbaBitmap;
- rgbaBitmap.allocPixels(SkImageInfo::MakeN32(width, height, bitmap->alphaType()));
- rgbaBitmap.eraseColor(0);
-
- SkCanvas canvas(rgbaBitmap);
- canvas.drawBitmap(*bitmap, 0.0f, 0.0f, nullptr);
-
- uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), rgbaBitmap.bytesPerPixel(),
- width, height, GL_UNSIGNED_BYTE, rgbaBitmap.getPixels());
-}
-
-void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei stride, GLsizei bpp,
- GLsizei width, GLsizei height, GLenum type, const GLvoid * data) {
- glPixelStorei(GL_UNPACK_ALIGNMENT, bpp);
- const bool useStride = stride != width
- && Caches::getInstance().extensions().hasUnpackRowLength();
- if ((stride == width) || useStride) {
- if (useStride) {
- glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
- }
-
- if (resize) {
- glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
- } else {
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
- }
-
- if (useStride) {
- glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
- }
- } else {
- // With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer
- // if the stride doesn't match the width
-
- GLvoid * temp = (GLvoid *) malloc(width * height * bpp);
- if (!temp) return;
-
- uint8_t * pDst = (uint8_t *)temp;
- uint8_t * pSrc = (uint8_t *)data;
- for (GLsizei i = 0; i < height; i++) {
- memcpy(pDst, pSrc, width * bpp);
- pDst += width * bpp;
- pSrc += stride * bpp;
- }
-
- if (resize) {
- glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp);
- } else {
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp);
- }
-
- free(temp);
- }
-}
-
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 7a7ee5aeb554..a4317cee73fd 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -21,10 +21,12 @@
#include <utils/LruCache.h>
#include <utils/Mutex.h>
-#include <utils/Vector.h>
#include "Debug.h"
+#include <vector>
+#include <unordered_map>
+
namespace android {
namespace uirenderer {
@@ -107,10 +109,6 @@ public:
void clear();
/**
- * Sets the maximum size of the cache in bytes.
- */
- void setMaxSize(uint32_t maxSize);
- /**
* Returns the maximum size of the cache in bytes.
*/
uint32_t getMaxSize();
@@ -124,11 +122,6 @@ public:
* is defined by the flush rate.
*/
void flush();
- /**
- * Indicates the percentage of the cache to retain when a
- * memory trim is requested (see Caches::flush).
- */
- void setFlushRate(float flushRate);
void setAssetAtlas(AssetAtlas* assetAtlas);
@@ -143,29 +136,17 @@ private:
Texture* get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType);
Texture* getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType);
- /**
- * Generates the texture from a bitmap into the specified texture structure.
- *
- * @param regenerate If true, the bitmap data is reuploaded into the texture, but
- * no new texture is generated.
- */
- void generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate = false);
-
- void uploadLoFiTexture(bool resize, const SkBitmap* bitmap, uint32_t width, uint32_t height);
- void uploadToTexture(bool resize, GLenum format, GLsizei stride, GLsizei bpp,
- GLsizei width, GLsizei height, GLenum type, const GLvoid * data);
-
LruCache<uint32_t, Texture*> mCache;
uint32_t mSize;
- uint32_t mMaxSize;
+ const uint32_t mMaxSize;
GLint mMaxTextureSize;
- float mFlushRate;
+ const float mFlushRate;
bool mDebugEnabled;
- Vector<uint32_t> mGarbage;
+ std::vector<uint32_t> mGarbage;
mutable Mutex mLock;
AssetAtlas* mAssetAtlas;
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index ed853f72539d..ac2bdccf5255 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -16,11 +16,11 @@
#ifndef TREEINFO_H
#define TREEINFO_H
-#include <string>
+#include "utils/Macros.h"
#include <utils/Timers.h>
-#include "utils/Macros.h"
+#include <string>
namespace android {
namespace uirenderer {
@@ -30,7 +30,9 @@ class CanvasContext;
}
class DamageAccumulator;
+class LayerUpdateQueue;
class OpenGLRenderer;
+class RenderNode;
class RenderState;
class ErrorHandler {
@@ -40,6 +42,17 @@ protected:
~ErrorHandler() {}
};
+class TreeObserver {
+public:
+ // Called when a RenderNode's parent count hits 0.
+ // Due to the unordered nature of tree pushes, once prepareTree
+ // is finished it is possible that the node was "resurrected" and has
+ // a non-zero parent count.
+ virtual void onMaybeRemovedFromTree(RenderNode* node) {}
+protected:
+ ~TreeObserver() {}
+};
+
// This would be a struct, but we want to PREVENT_COPY_AND_ASSIGN
class TreeInfo {
PREVENT_COPY_AND_ASSIGN(TreeInfo);
@@ -55,70 +68,59 @@ public:
MODE_RT_ONLY,
};
- explicit TreeInfo(TraversalMode mode, RenderState& renderState)
- : mode(mode)
- , prepareTextures(mode == MODE_FULL)
- , runAnimations(true)
- , damageAccumulator(nullptr)
- , renderState(renderState)
- , renderer(nullptr)
- , errorHandler(nullptr)
- , canvasContext(nullptr)
- {}
-
- explicit TreeInfo(TraversalMode mode, const TreeInfo& clone)
- : mode(mode)
- , prepareTextures(mode == MODE_FULL)
- , runAnimations(clone.runAnimations)
- , damageAccumulator(clone.damageAccumulator)
- , renderState(clone.renderState)
- , renderer(clone.renderer)
- , errorHandler(clone.errorHandler)
- , canvasContext(clone.canvasContext)
+ TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext)
+ : mode(mode)
+ , prepareTextures(mode == MODE_FULL)
+ , canvasContext(canvasContext)
{}
- const TraversalMode mode;
+ TraversalMode mode;
// TODO: Remove this? Currently this is used to signal to stop preparing
// textures if we run out of cache space.
bool prepareTextures;
+ renderthread::CanvasContext& canvasContext;
// TODO: buildLayer uses this to suppress running any animations, but this
// should probably be refactored somehow. The reason this is done is
// because buildLayer is not setup for injecting the animationHook, as well
// as this being otherwise wasted work as all the animators will be
// re-evaluated when the frame is actually drawn
- bool runAnimations;
+ bool runAnimations = true;
// Must not be null during actual usage
- DamageAccumulator* damageAccumulator;
- RenderState& renderState;
+ DamageAccumulator* damageAccumulator = nullptr;
+
+#if HWUI_NEW_OPS
+ LayerUpdateQueue* layerUpdateQueue = nullptr;
+#else
// The renderer that will be drawing the next frame. Use this to push any
// layer updates or similar. May be NULL.
- OpenGLRenderer* renderer;
- ErrorHandler* errorHandler;
- // May be NULL (TODO: can it really?)
- renderthread::CanvasContext* canvasContext;
+ OpenGLRenderer* renderer = nullptr;
+#endif
+ ErrorHandler* errorHandler = nullptr;
+
+ // Optional, may be nullptr. Used to allow things to observe interesting
+ // tree state changes
+ TreeObserver* observer = nullptr;
+
+ int32_t windowInsetLeft = 0;
+ int32_t windowInsetTop = 0;
+ bool updateWindowPositions = false;
struct Out {
- Out()
- : hasFunctors(false)
- , hasAnimations(false)
- , requiresUiRedraw(false)
- , canDrawThisFrame(true)
- {}
- bool hasFunctors;
+ bool hasFunctors = false;
// This is only updated if evaluateAnimations is true
- bool hasAnimations;
+ bool hasAnimations = false;
// This is set to true if there is an animation that RenderThread cannot
// animate itself, such as if hasFunctors is true
// This is only set if hasAnimations is true
- bool requiresUiRedraw;
+ 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
// production is outrunning consumption
// NOTE that if this is false CanvasContext will set either requiresUiRedraw
// *OR* will post itself for the next vsync automatically, use this
// only to avoid calling draw()
- bool canDrawThisFrame;
+ bool canDrawThisFrame = true;
} out;
// TODO: Damage calculations
diff --git a/libs/hwui/Vector.h b/libs/hwui/Vector.h
index 7c3f2fd379e4..6367dbd7b660 100644
--- a/libs/hwui/Vector.h
+++ b/libs/hwui/Vector.h
@@ -135,8 +135,8 @@ public:
}
- void dump() {
- ALOGD("Vector3[%.2f, %.2f, %.2f]", x, y, z);
+ void dump(const char* label = "Vector3") const {
+ ALOGD("%s[%.2f, %.2f, %.2f]", label, x, y, z);
}
};
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
new file mode 100644
index 000000000000..ac17ed2eb735
--- /dev/null
+++ b/libs/hwui/VectorDrawable.cpp
@@ -0,0 +1,588 @@
+/*
+ * Copyright (C) 2015 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 "VectorDrawable.h"
+
+#include "PathParser.h"
+#include "SkColorFilter.h"
+#include "SkImageInfo.h"
+#include "SkShader.h"
+#include <utils/Log.h>
+#include "utils/Macros.h"
+#include "utils/VectorDrawableUtils.h"
+
+#include <math.h>
+#include <string.h>
+
+namespace android {
+namespace uirenderer {
+namespace VectorDrawable {
+
+const int Tree::MAX_CACHED_BITMAP_SIZE = 2048;
+
+void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY,
+ bool useStagingData) {
+ float matrixScale = getMatrixScale(groupStackedMatrix);
+ if (matrixScale == 0) {
+ // When either x or y is scaled to 0, we don't need to draw anything.
+ return;
+ }
+
+ SkMatrix pathMatrix(groupStackedMatrix);
+ pathMatrix.postScale(scaleX, scaleY);
+
+ //TODO: try apply the path matrix to the canvas instead of creating a new path.
+ SkPath renderPath;
+ renderPath.reset();
+
+ if (useStagingData) {
+ SkPath tmpPath;
+ getStagingPath(&tmpPath);
+ renderPath.addPath(tmpPath, pathMatrix);
+ } else {
+ renderPath.addPath(getUpdatedPath(), pathMatrix);
+ }
+
+ float minScale = fmin(scaleX, scaleY);
+ float strokeScale = minScale * matrixScale;
+ drawPath(outCanvas, renderPath, strokeScale, pathMatrix, useStagingData);
+}
+
+void Path::dump() {
+ ALOGD("Path: %s has %zu points", mName.c_str(), mProperties.getData().points.size());
+}
+
+float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) {
+ // Given unit vectors A = (0, 1) and B = (1, 0).
+ // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
+ // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
+ // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
+ // If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
+ //
+ // For non-skew case, which is most of the cases, matrix scale is computing exactly the
+ // scale on x and y axis, and take the minimal of these two.
+ // For skew case, an unit square will mapped to a parallelogram. And this function will
+ // return the minimal height of the 2 bases.
+ SkVector skVectors[2];
+ skVectors[0].set(0, 1);
+ skVectors[1].set(1, 0);
+ groupStackedMatrix.mapVectors(skVectors, 2);
+ float scaleX = hypotf(skVectors[0].fX, skVectors[0].fY);
+ float scaleY = hypotf(skVectors[1].fX, skVectors[1].fY);
+ float crossProduct = skVectors[0].cross(skVectors[1]);
+ float maxScale = fmax(scaleX, scaleY);
+
+ float matrixScale = 0;
+ if (maxScale > 0) {
+ matrixScale = fabs(crossProduct) / maxScale;
+ }
+ return matrixScale;
+}
+
+// Called from UI thread during the initial setup/theme change.
+Path::Path(const char* pathStr, size_t strLength) {
+ PathParser::ParseResult result;
+ Data data;
+ PathParser::getPathDataFromAsciiString(&data, &result, pathStr, strLength);
+ mStagingProperties.setData(data);
+}
+
+Path::Path(const Path& path) : Node(path) {
+ mStagingProperties.syncProperties(path.mStagingProperties);
+}
+
+const SkPath& Path::getUpdatedPath() {
+ if (mSkPathDirty) {
+ mSkPath.reset();
+ VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
+ mSkPathDirty = false;
+ }
+ return mSkPath;
+}
+
+void Path::getStagingPath(SkPath* outPath) {
+ outPath->reset();
+ VectorDrawableUtils::verbsToPath(outPath, mStagingProperties.getData());
+}
+
+void Path::syncProperties() {
+ if (mStagingPropertiesDirty) {
+ mProperties.syncProperties(mStagingProperties);
+ } else {
+ mStagingProperties.syncProperties(mProperties);
+ }
+ mStagingPropertiesDirty = false;
+}
+
+FullPath::FullPath(const FullPath& path) : Path(path) {
+ mStagingProperties.syncProperties(path.mStagingProperties);
+}
+
+static void applyTrim(SkPath* outPath, const SkPath& inPath, float trimPathStart, float trimPathEnd,
+ float trimPathOffset) {
+ if (trimPathStart == 0.0f && trimPathEnd == 1.0f) {
+ *outPath = inPath;
+ return;
+ }
+ outPath->reset();
+ if (trimPathStart == trimPathEnd) {
+ // Trimmed path should be empty.
+ return;
+ }
+ SkPathMeasure measure(inPath, false);
+ float len = SkScalarToFloat(measure.getLength());
+ float start = len * fmod((trimPathStart + trimPathOffset), 1.0f);
+ float end = len * fmod((trimPathEnd + trimPathOffset), 1.0f);
+
+ if (start > end) {
+ measure.getSegment(start, len, outPath, true);
+ if (end > 0) {
+ measure.getSegment(0, end, outPath, true);
+ }
+ } else {
+ measure.getSegment(start, end, outPath, true);
+ }
+}
+
+const SkPath& FullPath::getUpdatedPath() {
+ if (!mSkPathDirty && !mProperties.mTrimDirty) {
+ return mTrimmedSkPath;
+ }
+ Path::getUpdatedPath();
+ if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
+ mProperties.mTrimDirty = false;
+ applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
+ mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
+ return mTrimmedSkPath;
+ } else {
+ return mSkPath;
+ }
+}
+
+void FullPath::getStagingPath(SkPath* outPath) {
+ Path::getStagingPath(outPath);
+ SkPath inPath = *outPath;
+ applyTrim(outPath, inPath, mStagingProperties.getTrimPathStart(),
+ mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
+}
+
+void FullPath::dump() {
+ Path::dump();
+ ALOGD("stroke width, color, alpha: %f, %d, %f, fill color, alpha: %d, %f",
+ mProperties.getStrokeWidth(), mProperties.getStrokeColor(), mProperties.getStrokeAlpha(),
+ mProperties.getFillColor(), mProperties.getFillAlpha());
+}
+
+
+inline SkColor applyAlpha(SkColor color, float alpha) {
+ int alphaBytes = SkColorGetA(color);
+ return SkColorSetA(color, alphaBytes * alpha);
+}
+
+void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeScale,
+ const SkMatrix& matrix, bool useStagingData){
+ const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
+
+ // Draw path's fill, if fill color or gradient is valid
+ bool needsFill = false;
+ SkPaint paint;
+ if (properties.getFillGradient() != nullptr) {
+ paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
+ SkShader* newShader = properties.getFillGradient()->newWithLocalMatrix(matrix);
+ paint.setShader(newShader);
+ needsFill = true;
+ } else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
+ paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
+ needsFill = true;
+ }
+
+ if (needsFill) {
+ paint.setStyle(SkPaint::Style::kFill_Style);
+ paint.setAntiAlias(true);
+ SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
+ renderPath.setFillType(ft);
+ outCanvas->drawPath(renderPath, paint);
+ }
+
+ // Draw path's stroke, if stroke color or Gradient is valid
+ bool needsStroke = false;
+ if (properties.getStrokeGradient() != nullptr) {
+ paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
+ SkShader* newShader = properties.getStrokeGradient()->newWithLocalMatrix(matrix);
+ paint.setShader(newShader);
+ needsStroke = true;
+ } else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
+ paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
+ needsStroke = true;
+ }
+ if (needsStroke) {
+ paint.setStyle(SkPaint::Style::kStroke_Style);
+ paint.setAntiAlias(true);
+ paint.setStrokeJoin(SkPaint::Join(properties.getStrokeLineJoin()));
+ paint.setStrokeCap(SkPaint::Cap(properties.getStrokeLineCap()));
+ paint.setStrokeMiter(properties.getStrokeMiterLimit());
+ paint.setStrokeWidth(properties.getStrokeWidth() * strokeScale);
+ outCanvas->drawPath(renderPath, paint);
+ }
+}
+
+void FullPath::syncProperties() {
+ Path::syncProperties();
+
+ if (mStagingPropertiesDirty) {
+ mProperties.syncProperties(mStagingProperties);
+ } else {
+ // Update staging property with property values from animation.
+ mStagingProperties.syncProperties(mProperties);
+ }
+ mStagingPropertiesDirty = false;
+}
+
+REQUIRE_COMPATIBLE_LAYOUT(FullPath::FullPathProperties::PrimitiveFields);
+
+static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t");
+static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t");
+
+bool FullPath::FullPathProperties::copyProperties(int8_t* outProperties, int length) const {
+ int propertyDataSize = sizeof(FullPathProperties::PrimitiveFields);
+ if (length != propertyDataSize) {
+ LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
+ propertyDataSize, length);
+ return false;
+ }
+
+ PrimitiveFields* out = reinterpret_cast<PrimitiveFields*>(outProperties);
+ *out = mPrimitiveFields;
+ return true;
+}
+
+void FullPath::FullPathProperties::setColorPropertyValue(int propertyId, int32_t value) {
+ Property currentProperty = static_cast<Property>(propertyId);
+ if (currentProperty == Property::strokeColor) {
+ setStrokeColor(value);
+ } else if (currentProperty == Property::fillColor) {
+ setFillColor(value);
+ } else {
+ LOG_ALWAYS_FATAL("Error setting color property on FullPath: No valid property"
+ " with id: %d", propertyId);
+ }
+}
+
+void FullPath::FullPathProperties::setPropertyValue(int propertyId, float value) {
+ Property property = static_cast<Property>(propertyId);
+ switch (property) {
+ case Property::strokeWidth:
+ setStrokeWidth(value);
+ break;
+ case Property::strokeAlpha:
+ setStrokeAlpha(value);
+ break;
+ case Property::fillAlpha:
+ setFillAlpha(value);
+ break;
+ case Property::trimPathStart:
+ setTrimPathStart(value);
+ break;
+ case Property::trimPathEnd:
+ setTrimPathEnd(value);
+ break;
+ case Property::trimPathOffset:
+ setTrimPathOffset(value);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Invalid property id: %d for animation", propertyId);
+ break;
+ }
+}
+
+void ClipPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath,
+ float strokeScale, const SkMatrix& matrix, bool useStagingData){
+ outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op);
+}
+
+Group::Group(const Group& group) : Node(group) {
+ mStagingProperties.syncProperties(group.mStagingProperties);
+}
+
+void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
+ float scaleY, bool useStagingData) {
+ // TODO: Try apply the matrix to the canvas instead of passing it down the tree
+
+ // Calculate current group's matrix by preConcat the parent's and
+ // and the current one on the top of the stack.
+ // Basically the Mfinal = Mviewport * M0 * M1 * M2;
+ // Mi the local matrix at level i of the group tree.
+ SkMatrix stackedMatrix;
+ const GroupProperties& prop = useStagingData ? mStagingProperties : mProperties;
+ getLocalMatrix(&stackedMatrix, prop);
+ stackedMatrix.postConcat(currentMatrix);
+
+ // Save the current clip information, which is local to this group.
+ outCanvas->save();
+ // Draw the group tree in the same order as the XML file.
+ for (auto& child : mChildren) {
+ child->draw(outCanvas, stackedMatrix, scaleX, scaleY, useStagingData);
+ }
+ // Restore the previous clip information.
+ outCanvas->restore();
+}
+
+void Group::dump() {
+ ALOGD("Group %s has %zu children: ", mName.c_str(), mChildren.size());
+ ALOGD("Group translateX, Y : %f, %f, scaleX, Y: %f, %f", mProperties.getTranslateX(),
+ mProperties.getTranslateY(), mProperties.getScaleX(), mProperties.getScaleY());
+ for (size_t i = 0; i < mChildren.size(); i++) {
+ mChildren[i]->dump();
+ }
+}
+
+void Group::syncProperties() {
+ // Copy over the dirty staging properties
+ if (mStagingPropertiesDirty) {
+ mProperties.syncProperties(mStagingProperties);
+ } else {
+ mStagingProperties.syncProperties(mProperties);
+ }
+ mStagingPropertiesDirty = false;
+ for (auto& child : mChildren) {
+ child->syncProperties();
+ }
+}
+
+void Group::getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties) {
+ outMatrix->reset();
+ // TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of
+ // translating to pivot for rotating and scaling, then translating back.
+ outMatrix->postTranslate(-properties.getPivotX(), -properties.getPivotY());
+ outMatrix->postScale(properties.getScaleX(), properties.getScaleY());
+ outMatrix->postRotate(properties.getRotation(), 0, 0);
+ outMatrix->postTranslate(properties.getTranslateX() + properties.getPivotX(),
+ properties.getTranslateY() + properties.getPivotY());
+}
+
+void Group::addChild(Node* child) {
+ mChildren.emplace_back(child);
+ if (mPropertyChangedListener != nullptr) {
+ child->setPropertyChangedListener(mPropertyChangedListener);
+ }
+}
+
+bool Group::GroupProperties::copyProperties(float* outProperties, int length) const {
+ int propertyCount = static_cast<int>(Property::count);
+ if (length != propertyCount) {
+ LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
+ propertyCount, length);
+ return false;
+ }
+
+ PrimitiveFields* out = reinterpret_cast<PrimitiveFields*>(outProperties);
+ *out = mPrimitiveFields;
+ return true;
+}
+
+// TODO: Consider animating the properties as float pointers
+// Called on render thread
+float Group::GroupProperties::getPropertyValue(int propertyId) const {
+ Property currentProperty = static_cast<Property>(propertyId);
+ switch (currentProperty) {
+ case Property::rotate:
+ return getRotation();
+ case Property::pivotX:
+ return getPivotX();
+ case Property::pivotY:
+ return getPivotY();
+ case Property::scaleX:
+ return getScaleX();
+ case Property::scaleY:
+ return getScaleY();
+ case Property::translateX:
+ return getTranslateX();
+ case Property::translateY:
+ return getTranslateY();
+ default:
+ LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId);
+ return 0;
+ }
+}
+
+// Called on render thread
+void Group::GroupProperties::setPropertyValue(int propertyId, float value) {
+ Property currentProperty = static_cast<Property>(propertyId);
+ switch (currentProperty) {
+ case Property::rotate:
+ setRotation(value);
+ break;
+ case Property::pivotX:
+ setPivotX(value);
+ break;
+ case Property::pivotY:
+ setPivotY(value);
+ break;
+ case Property::scaleX:
+ setScaleX(value);
+ break;
+ case Property::scaleY:
+ setScaleY(value);
+ break;
+ case Property::translateX:
+ setTranslateX(value);
+ break;
+ case Property::translateY:
+ setTranslateY(value);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId);
+ }
+}
+
+bool Group::isValidProperty(int propertyId) {
+ return GroupProperties::isValidProperty(propertyId);
+}
+
+bool Group::GroupProperties::isValidProperty(int propertyId) {
+ return propertyId >= 0 && propertyId < static_cast<int>(Property::count);
+}
+
+void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter,
+ const SkRect& bounds, bool needsMirroring, bool canReuseCache) {
+ // The imageView can scale the canvas in different ways, in order to
+ // avoid blurry scaling, we have to draw into a bitmap with exact pixel
+ // size first. This bitmap size is determined by the bounds and the
+ // canvas scale.
+ SkMatrix canvasMatrix;
+ outCanvas->getMatrix(&canvasMatrix);
+ float canvasScaleX = 1.0f;
+ float canvasScaleY = 1.0f;
+ if (canvasMatrix.getSkewX() == 0 && canvasMatrix.getSkewY() == 0) {
+ // Only use the scale value when there's no skew or rotation in the canvas matrix.
+ // TODO: Add a cts test for drawing VD on a canvas with negative scaling factors.
+ canvasScaleX = fabs(canvasMatrix.getScaleX());
+ canvasScaleY = fabs(canvasMatrix.getScaleY());
+ }
+ int scaledWidth = (int) (bounds.width() * canvasScaleX);
+ int scaledHeight = (int) (bounds.height() * canvasScaleY);
+ scaledWidth = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledWidth);
+ scaledHeight = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledHeight);
+
+ if (scaledWidth <= 0 || scaledHeight <= 0) {
+ return;
+ }
+
+ mStagingProperties.setScaledSize(scaledWidth, scaledHeight);
+ int saveCount = outCanvas->save(SaveFlags::MatrixClip);
+ outCanvas->translate(bounds.fLeft, bounds.fTop);
+
+ // Handle RTL mirroring.
+ if (needsMirroring) {
+ outCanvas->translate(bounds.width(), 0);
+ outCanvas->scale(-1.0f, 1.0f);
+ }
+ mStagingProperties.setColorFilter(colorFilter);
+
+ // At this point, canvas has been translated to the right position.
+ // And we use this bound for the destination rect for the drawBitmap, so
+ // we offset to (0, 0);
+ SkRect tmpBounds = bounds;
+ tmpBounds.offsetTo(0, 0);
+ mStagingProperties.setBounds(tmpBounds);
+ outCanvas->drawVectorDrawable(this);
+ outCanvas->restoreToCount(saveCount);
+}
+
+void Tree::drawStaging(Canvas* outCanvas) {
+ bool redrawNeeded = allocateBitmapIfNeeded(&mStagingCache.bitmap,
+ mStagingProperties.getScaledWidth(), mStagingProperties.getScaledHeight());
+ // draw bitmap cache
+ if (redrawNeeded || mStagingCache.dirty) {
+ updateBitmapCache(&mStagingCache.bitmap, true);
+ mStagingCache.dirty = false;
+ }
+
+ SkPaint tmpPaint;
+ SkPaint* paint = updatePaint(&tmpPaint, &mStagingProperties);
+ outCanvas->drawBitmap(mStagingCache.bitmap, 0, 0,
+ mStagingCache.bitmap.width(), mStagingCache.bitmap.height(),
+ mStagingProperties.getBounds().left(), mStagingProperties.getBounds().top(),
+ mStagingProperties.getBounds().right(), mStagingProperties.getBounds().bottom(), paint);
+}
+
+SkPaint* Tree::getPaint() {
+ return updatePaint(&mPaint, &mProperties);
+}
+
+// Update the given paint with alpha and color filter. Return nullptr if no color filter is
+// specified and root alpha is 1. Otherwise, return updated paint.
+SkPaint* Tree::updatePaint(SkPaint* outPaint, TreeProperties* prop) {
+ if (prop->getRootAlpha() == 1.0f && prop->getColorFilter() == nullptr) {
+ return nullptr;
+ } else {
+ outPaint->setColorFilter(mStagingProperties.getColorFilter());
+ outPaint->setFilterQuality(kLow_SkFilterQuality);
+ outPaint->setAlpha(prop->getRootAlpha() * 255);
+ return outPaint;
+ }
+}
+
+const SkBitmap& Tree::getBitmapUpdateIfDirty() {
+ bool redrawNeeded = allocateBitmapIfNeeded(&mCache.bitmap, mProperties.getScaledWidth(),
+ mProperties.getScaledHeight());
+ if (redrawNeeded || mCache.dirty) {
+ updateBitmapCache(&mCache.bitmap, false);
+ mCache.dirty = false;
+ }
+ return mCache.bitmap;
+}
+
+void Tree::updateBitmapCache(SkBitmap* outCache, bool useStagingData) {
+ outCache->eraseColor(SK_ColorTRANSPARENT);
+ SkCanvas outCanvas(*outCache);
+ float viewportWidth = useStagingData ?
+ mStagingProperties.getViewportWidth() : mProperties.getViewportWidth();
+ float viewportHeight = useStagingData ?
+ mStagingProperties.getViewportHeight() : mProperties.getViewportHeight();
+ float scaleX = outCache->width() / viewportWidth;
+ float scaleY = outCache->height() / viewportHeight;
+ mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY, useStagingData);
+}
+
+bool Tree::allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height) {
+ if (!canReuseBitmap(*outCache, width, height)) {
+ SkImageInfo info = SkImageInfo::Make(width, height,
+ kN32_SkColorType, kPremul_SkAlphaType);
+ outCache->setInfo(info);
+ // TODO: Count the bitmap cache against app's java heap
+ outCache->allocPixels(info);
+ return true;
+ }
+ return false;
+}
+
+bool Tree::canReuseBitmap(const SkBitmap& bitmap, int width, int height) {
+ return width == bitmap.width() && height == bitmap.height();
+}
+
+void Tree::onPropertyChanged(TreeProperties* prop) {
+ if (prop == &mStagingProperties) {
+ mStagingCache.dirty = true;
+ } else {
+ mCache.dirty = true;
+ }
+}
+
+}; // namespace VectorDrawable
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
new file mode 100644
index 000000000000..1c6f48e7276b
--- /dev/null
+++ b/libs/hwui/VectorDrawable.h
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) 2015 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_VPATH_H
+#define ANDROID_HWUI_VPATH_H
+
+#include "hwui/Canvas.h"
+#include "DisplayList.h"
+
+#include <SkBitmap.h>
+#include <SkColor.h>
+#include <SkColorFilter.h>
+#include <SkCanvas.h>
+#include <SkMatrix.h>
+#include <SkPaint.h>
+#include <SkPath.h>
+#include <SkPathMeasure.h>
+#include <SkRect.h>
+#include <SkShader.h>
+
+#include <cutils/compiler.h>
+#include <stddef.h>
+#include <vector>
+#include <string>
+
+namespace android {
+namespace uirenderer {
+
+namespace VectorDrawable {
+#define VD_SET_PRIMITIVE_FIELD_WITH_FLAG(field, value, flag) (VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(field, (value)) ? ((flag) = true, true) : false)
+#define VD_SET_PROP(field, value) ((value) != (field) ? ((field) = (value), true) : false)
+#define VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(field, value) ({ bool retVal = VD_SET_PROP((mPrimitiveFields.field), (value));\
+ onPropertyChanged(); retVal;})
+#define UPDATE_SKPROP(field, value) ({bool retVal = ((field) != (value)); if ((field) != (value)) SkRefCnt_SafeAssign((field), (value)); retVal;})
+
+/* A VectorDrawable is composed of a tree of nodes.
+ * Each node can be a group node, or a path.
+ * A group node can have groups or paths as children, but a path node has
+ * no children.
+ * One example can be:
+ * Root Group
+ * / | \
+ * Group Path Group
+ * / \ |
+ * Path Path Path
+ *
+ * VectorDrawables are drawn into bitmap caches first, then the caches are drawn to the given
+ * canvas with root alpha applied. Two caches are maintained for VD, one in UI thread, the other in
+ * Render Thread. A generation id is used to keep track of changes in the vector drawable tree.
+ * Each cache has their own generation id to track whether they are up to date with the latest
+ * change in the tree.
+ *
+ * Any property change to the vector drawable coming from UI thread (such as bulk setters to update
+ * all the properties, and viewport change, etc.) are only modifying the staging properties. The
+ * staging properties will then be marked dirty and will be pushed over to render thread properties
+ * at sync point. If staging properties are not dirty at sync point, we sync backwards by updating
+ * staging properties with render thread properties to reflect the latest animation value.
+ *
+ */
+
+class PropertyChangedListener {
+public:
+ PropertyChangedListener(bool* dirty, bool* stagingDirty)
+ : mDirty(dirty), mStagingDirty(stagingDirty) {}
+ void onPropertyChanged() {
+ *mDirty = true;
+ }
+ void onStagingPropertyChanged() {
+ *mStagingDirty = true;
+ }
+private:
+ bool* mDirty;
+ bool* mStagingDirty;
+};
+
+class ANDROID_API Node {
+public:
+ class Properties {
+ public:
+ Properties(Node* node) : mNode(node) {}
+ inline void onPropertyChanged() {
+ mNode->onPropertyChanged(this);
+ }
+ private:
+ Node* mNode;
+ };
+ Node(const Node& node) {
+ mName = node.mName;
+ }
+ Node() {}
+ virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
+ float scaleX, float scaleY, bool useStagingData) = 0;
+ virtual void dump() = 0;
+ void setName(const char* name) {
+ mName = name;
+ }
+ virtual void setPropertyChangedListener(PropertyChangedListener* listener) {
+ mPropertyChangedListener = listener;
+ }
+ virtual void onPropertyChanged(Properties* properties) = 0;
+ virtual ~Node(){}
+ virtual void syncProperties() = 0;
+protected:
+ std::string mName;
+ PropertyChangedListener* mPropertyChangedListener = nullptr;
+};
+
+class ANDROID_API Path : public Node {
+public:
+ struct ANDROID_API Data {
+ std::vector<char> verbs;
+ std::vector<size_t> verbSizes;
+ std::vector<float> points;
+ bool operator==(const Data& data) const {
+ return verbs == data.verbs && verbSizes == data.verbSizes
+ && points == data.points;
+ }
+ };
+
+ class PathProperties : public Properties {
+ public:
+ PathProperties(Node* node) : Properties(node) {}
+ void syncProperties(const PathProperties& prop) {
+ mData = prop.mData;
+ onPropertyChanged();
+ }
+ void setData(const Data& data) {
+ // Updates the path data. Note that we don't generate a new Skia path right away
+ // because there are cases where the animation is changing the path data, but the view
+ // that hosts the VD has gone off screen, in which case we won't even draw. So we
+ // postpone the Skia path generation to the draw time.
+ if (data == mData) {
+ return;
+ }
+ mData = data;
+ onPropertyChanged();
+
+ }
+ const Data& getData() const {
+ return mData;
+ }
+ private:
+ Data mData;
+ };
+
+ Path(const Path& path);
+ Path(const char* path, size_t strLength);
+ Path() {}
+
+ void dump() override;
+ void draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix,
+ float scaleX, float scaleY, bool useStagingData) override;
+ static float getMatrixScale(const SkMatrix& groupStackedMatrix);
+ virtual void syncProperties() override;
+ virtual void onPropertyChanged(Properties* prop) override {
+ if (prop == &mStagingProperties) {
+ mStagingPropertiesDirty = true;
+ if (mPropertyChangedListener) {
+ mPropertyChangedListener->onStagingPropertyChanged();
+ }
+ } else if (prop == &mProperties){
+ mSkPathDirty = true;
+ if (mPropertyChangedListener) {
+ mPropertyChangedListener->onPropertyChanged();
+ }
+ }
+ }
+ PathProperties* mutateStagingProperties() { return &mStagingProperties; }
+ const PathProperties* stagingProperties() { return &mStagingProperties; }
+
+ // This should only be called from animations on RT
+ PathProperties* mutateProperties() { return &mProperties; }
+
+protected:
+ virtual const SkPath& getUpdatedPath();
+ virtual void getStagingPath(SkPath* outPath);
+ virtual void drawPath(SkCanvas *outCanvas, SkPath& renderPath,
+ float strokeScale, const SkMatrix& matrix, bool useStagingData) = 0;
+
+ // Internal data, render thread only.
+ bool mSkPathDirty = true;
+ SkPath mSkPath;
+
+private:
+ PathProperties mProperties = PathProperties(this);
+ PathProperties mStagingProperties = PathProperties(this);
+ bool mStagingPropertiesDirty = true;
+};
+
+class ANDROID_API FullPath: public Path {
+public:
+ class FullPathProperties : public Properties {
+ public:
+ struct PrimitiveFields {
+ float strokeWidth = 0;
+ SkColor strokeColor = SK_ColorTRANSPARENT;
+ float strokeAlpha = 1;
+ SkColor fillColor = SK_ColorTRANSPARENT;
+ float fillAlpha = 1;
+ float trimPathStart = 0;
+ float trimPathEnd = 1;
+ float trimPathOffset = 0;
+ int32_t strokeLineCap = SkPaint::Cap::kButt_Cap;
+ int32_t strokeLineJoin = SkPaint::Join::kMiter_Join;
+ float strokeMiterLimit = 4;
+ int fillType = 0; /* non-zero or kWinding_FillType in Skia */
+ };
+ FullPathProperties(Node* mNode) : Properties(mNode), mTrimDirty(false) {}
+ ~FullPathProperties() {
+ SkSafeUnref(fillGradient);
+ SkSafeUnref(strokeGradient);
+ }
+ void syncProperties(const FullPathProperties& prop) {
+ mPrimitiveFields = prop.mPrimitiveFields;
+ mTrimDirty = true;
+ UPDATE_SKPROP(fillGradient, prop.fillGradient);
+ UPDATE_SKPROP(strokeGradient, prop.strokeGradient);
+ onPropertyChanged();
+ }
+ void setFillGradient(SkShader* gradient) {
+ if(UPDATE_SKPROP(fillGradient, gradient)) {
+ onPropertyChanged();
+ }
+ }
+ void setStrokeGradient(SkShader* gradient) {
+ if(UPDATE_SKPROP(strokeGradient, gradient)) {
+ onPropertyChanged();
+ }
+ }
+ SkShader* getFillGradient() const {
+ return fillGradient;
+ }
+ SkShader* getStrokeGradient() const {
+ return strokeGradient;
+ }
+ float getStrokeWidth() const{
+ return mPrimitiveFields.strokeWidth;
+ }
+ void setStrokeWidth(float strokeWidth) {
+ VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeWidth, strokeWidth);
+ }
+ SkColor getStrokeColor() const{
+ return mPrimitiveFields.strokeColor;
+ }
+ void setStrokeColor(SkColor strokeColor) {
+ VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeColor, strokeColor);
+ }
+ float getStrokeAlpha() const{
+ return mPrimitiveFields.strokeAlpha;
+ }
+ void setStrokeAlpha(float strokeAlpha) {
+ VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeAlpha, strokeAlpha);
+ }
+ SkColor getFillColor() const {
+ return mPrimitiveFields.fillColor;
+ }
+ void setFillColor(SkColor fillColor) {
+ VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(fillColor, fillColor);
+ }
+ float getFillAlpha() const{
+ return mPrimitiveFields.fillAlpha;
+ }
+ void setFillAlpha(float fillAlpha) {
+ VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(fillAlpha, fillAlpha);
+ }
+ float getTrimPathStart() const{
+ return mPrimitiveFields.trimPathStart;
+ }
+ void setTrimPathStart(float trimPathStart) {
+ VD_SET_PRIMITIVE_FIELD_WITH_FLAG(trimPathStart, trimPathStart, mTrimDirty);
+ }
+ float getTrimPathEnd() const{
+ return mPrimitiveFields.trimPathEnd;
+ }
+ void setTrimPathEnd(float trimPathEnd) {
+ VD_SET_PRIMITIVE_FIELD_WITH_FLAG(trimPathEnd, trimPathEnd, mTrimDirty);
+ }
+ float getTrimPathOffset() const{
+ return mPrimitiveFields.trimPathOffset;
+ }
+ void setTrimPathOffset(float trimPathOffset) {
+ VD_SET_PRIMITIVE_FIELD_WITH_FLAG(trimPathOffset, trimPathOffset, mTrimDirty);
+ }
+
+ float getStrokeMiterLimit() const {
+ return mPrimitiveFields.strokeMiterLimit;
+ }
+ float getStrokeLineCap() const {
+ return mPrimitiveFields.strokeLineCap;
+ }
+ float getStrokeLineJoin() const {
+ return mPrimitiveFields.strokeLineJoin;
+ }
+ float getFillType() const {
+ return mPrimitiveFields.fillType;
+ }
+ bool copyProperties(int8_t* outProperties, int length) const;
+ void updateProperties(float strokeWidth, SkColor strokeColor, float strokeAlpha,
+ SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd,
+ float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin,
+ int fillType) {
+ mPrimitiveFields.strokeWidth = strokeWidth;
+ mPrimitiveFields.strokeColor = strokeColor;
+ mPrimitiveFields.strokeAlpha = strokeAlpha;
+ mPrimitiveFields.fillColor = fillColor;
+ mPrimitiveFields.fillAlpha = fillAlpha;
+ mPrimitiveFields.trimPathStart = trimPathStart;
+ mPrimitiveFields.trimPathEnd = trimPathEnd;
+ mPrimitiveFields.trimPathOffset = trimPathOffset;
+ mPrimitiveFields.strokeMiterLimit = strokeMiterLimit;
+ mPrimitiveFields.strokeLineCap = strokeLineCap;
+ mPrimitiveFields.strokeLineJoin = strokeLineJoin;
+ mPrimitiveFields.fillType = fillType;
+ mTrimDirty = true;
+ onPropertyChanged();
+ }
+ // Set property values during animation
+ void setColorPropertyValue(int propertyId, int32_t value);
+ void setPropertyValue(int propertyId, float value);
+ bool mTrimDirty;
+ private:
+ enum class Property {
+ strokeWidth = 0,
+ strokeColor,
+ strokeAlpha,
+ fillColor,
+ fillAlpha,
+ trimPathStart,
+ trimPathEnd,
+ trimPathOffset,
+ strokeLineCap,
+ strokeLineJoin,
+ strokeMiterLimit,
+ fillType,
+ count,
+ };
+ PrimitiveFields mPrimitiveFields;
+ SkShader* fillGradient = nullptr;
+ SkShader* strokeGradient = nullptr;
+ };
+
+ // Called from UI thread
+ FullPath(const FullPath& path); // for cloning
+ FullPath(const char* path, size_t strLength) : Path(path, strLength) {}
+ FullPath() : Path() {}
+ void dump() override;
+ FullPathProperties* mutateStagingProperties() { return &mStagingProperties; }
+ const FullPathProperties* stagingProperties() { return &mStagingProperties; }
+
+ // This should only be called from animations on RT
+ FullPathProperties* mutateProperties() { return &mProperties; }
+
+ virtual void syncProperties() override;
+ virtual void onPropertyChanged(Properties* properties) override {
+ Path::onPropertyChanged(properties);
+ if (properties == &mStagingProperties) {
+ mStagingPropertiesDirty = true;
+ if (mPropertyChangedListener) {
+ mPropertyChangedListener->onStagingPropertyChanged();
+ }
+ } else if (properties == &mProperties) {
+ if (mPropertyChangedListener) {
+ mPropertyChangedListener->onPropertyChanged();
+ }
+ }
+ }
+
+protected:
+ const SkPath& getUpdatedPath() override;
+ void getStagingPath(SkPath* outPath) override;
+ void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
+ float strokeScale, const SkMatrix& matrix, bool useStagingData) override;
+private:
+
+ FullPathProperties mProperties = FullPathProperties(this);
+ FullPathProperties mStagingProperties = FullPathProperties(this);
+ bool mStagingPropertiesDirty = true;
+
+ // Intermediate data for drawing, render thread only
+ SkPath mTrimmedSkPath;
+
+};
+
+class ANDROID_API ClipPath: public Path {
+public:
+ ClipPath(const ClipPath& path) : Path(path) {}
+ ClipPath(const char* path, size_t strLength) : Path(path, strLength) {}
+ ClipPath() : Path() {}
+
+protected:
+ void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
+ float strokeScale, const SkMatrix& matrix, bool useStagingData) override;
+};
+
+class ANDROID_API Group: public Node {
+public:
+ class GroupProperties : public Properties {
+ public:
+ GroupProperties(Node* mNode) : Properties(mNode) {}
+ struct PrimitiveFields {
+ float rotate = 0;
+ float pivotX = 0;
+ float pivotY = 0;
+ float scaleX = 1;
+ float scaleY = 1;
+ float translateX = 0;
+ float translateY = 0;
+ } mPrimitiveFields;
+ void syncProperties(const GroupProperties& prop) {
+ mPrimitiveFields = prop.mPrimitiveFields;
+ onPropertyChanged();
+ }
+ float getRotation() const {
+ return mPrimitiveFields.rotate;
+ }
+ void setRotation(float rotation) {
+ VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(rotate, rotation);
+ }
+ float getPivotX() const {
+ return mPrimitiveFields.pivotX;
+ }
+ void setPivotX(float pivotX) {
+ VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(pivotX, pivotX);
+ }
+ float getPivotY() const {
+ return mPrimitiveFields.pivotY;
+ }
+ void setPivotY(float pivotY) {
+ VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(pivotY, pivotY);
+ }
+ float getScaleX() const {
+ return mPrimitiveFields.scaleX;
+ }
+ void setScaleX(float scaleX) {
+ VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(scaleX, scaleX);
+ }
+ float getScaleY() const {
+ return mPrimitiveFields.scaleY;
+ }
+ void setScaleY(float scaleY) {
+ VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(scaleY, scaleY);
+ }
+ float getTranslateX() const {
+ return mPrimitiveFields.translateX;
+ }
+ void setTranslateX(float translateX) {
+ VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(translateX, translateX);
+ }
+ float getTranslateY() const {
+ return mPrimitiveFields.translateY;
+ }
+ void setTranslateY(float translateY) {
+ VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(translateY, translateY);
+ }
+ void updateProperties(float rotate, float pivotX, float pivotY,
+ float scaleX, float scaleY, float translateX, float translateY) {
+ mPrimitiveFields.rotate = rotate;
+ mPrimitiveFields.pivotX = pivotX;
+ mPrimitiveFields.pivotY = pivotY;
+ mPrimitiveFields.scaleX = scaleX;
+ mPrimitiveFields.scaleY = scaleY;
+ mPrimitiveFields.translateX = translateX;
+ mPrimitiveFields.translateY = translateY;
+ onPropertyChanged();
+ }
+ void setPropertyValue(int propertyId, float value);
+ float getPropertyValue(int propertyId) const;
+ bool copyProperties(float* outProperties, int length) const;
+ static bool isValidProperty(int propertyId);
+ private:
+ enum class Property {
+ rotate = 0,
+ pivotX,
+ pivotY,
+ scaleX,
+ scaleY,
+ translateX,
+ translateY,
+ // Count of the properties, must be at the end.
+ count,
+ };
+ };
+
+ Group(const Group& group);
+ Group() {}
+ void addChild(Node* child);
+ virtual void setPropertyChangedListener(PropertyChangedListener* listener) override {
+ Node::setPropertyChangedListener(listener);
+ for (auto& child : mChildren) {
+ child->setPropertyChangedListener(listener);
+ }
+ }
+ virtual void syncProperties() override;
+ GroupProperties* mutateStagingProperties() { return &mStagingProperties; }
+ const GroupProperties* stagingProperties() { return &mStagingProperties; }
+
+ // This should only be called from animations on RT
+ GroupProperties* mutateProperties() { return &mProperties; }
+
+ // Methods below could be called from either UI thread or Render Thread.
+ virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
+ float scaleX, float scaleY, bool useStagingData) override;
+ void getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties);
+ void dump() override;
+ static bool isValidProperty(int propertyId);
+
+ virtual void onPropertyChanged(Properties* properties) override {
+ if (properties == &mStagingProperties) {
+ mStagingPropertiesDirty = true;
+ if (mPropertyChangedListener) {
+ mPropertyChangedListener->onStagingPropertyChanged();
+ }
+ } else {
+ if (mPropertyChangedListener) {
+ mPropertyChangedListener->onPropertyChanged();
+ }
+ }
+ }
+
+private:
+ GroupProperties mProperties = GroupProperties(this);
+ GroupProperties mStagingProperties = GroupProperties(this);
+ bool mStagingPropertiesDirty = true;
+ std::vector< std::unique_ptr<Node> > mChildren;
+};
+
+class ANDROID_API Tree : public VirtualLightRefBase {
+public:
+ Tree(Group* rootNode) : mRootNode(rootNode) {
+ mRootNode->setPropertyChangedListener(&mPropertyChangedListener);
+ }
+ void draw(Canvas* outCanvas, SkColorFilter* colorFilter,
+ const SkRect& bounds, bool needsMirroring, bool canReuseCache);
+ void drawStaging(Canvas* canvas);
+
+ const SkBitmap& getBitmapUpdateIfDirty();
+ void setAllowCaching(bool allowCaching) {
+ mAllowCaching = allowCaching;
+ }
+ SkPaint* getPaint();
+ void syncProperties() {
+ if (mStagingProperties.mNonAnimatablePropertiesDirty) {
+ mProperties.syncNonAnimatableProperties(mStagingProperties);
+ mStagingProperties.mNonAnimatablePropertiesDirty = false;
+ }
+
+ if (mStagingProperties.mAnimatablePropertiesDirty) {
+ mProperties.syncAnimatableProperties(mStagingProperties);
+ } else {
+ mStagingProperties.syncAnimatableProperties(mProperties);
+ }
+ mStagingProperties.mAnimatablePropertiesDirty = false;
+ mRootNode->syncProperties();
+ }
+
+ class TreeProperties {
+ public:
+ TreeProperties(Tree* tree) : mTree(tree) {}
+ // Properties that can only be modified by UI thread, therefore sync should
+ // only go from UI to RT
+ struct NonAnimatableProperties {
+ float viewportWidth = 0;
+ float viewportHeight = 0;
+ SkRect bounds;
+ int scaledWidth = 0;
+ int scaledHeight = 0;
+ SkColorFilter* colorFilter = nullptr;
+ ~NonAnimatableProperties() {
+ SkSafeUnref(colorFilter);
+ }
+ } mNonAnimatableProperties;
+ bool mNonAnimatablePropertiesDirty = true;
+
+ float mRootAlpha = 1.0f;
+ bool mAnimatablePropertiesDirty = true;
+
+ void syncNonAnimatableProperties(const TreeProperties& prop) {
+ // Copy over the data that can only be changed in UI thread
+ if (mNonAnimatableProperties.colorFilter != prop.mNonAnimatableProperties.colorFilter) {
+ SkRefCnt_SafeAssign(mNonAnimatableProperties.colorFilter,
+ prop.mNonAnimatableProperties.colorFilter);
+ }
+ mNonAnimatableProperties = prop.mNonAnimatableProperties;
+ }
+
+ void setViewportSize(float width, float height) {
+ if (mNonAnimatableProperties.viewportWidth != width
+ || mNonAnimatableProperties.viewportHeight != height) {
+ mNonAnimatablePropertiesDirty = true;
+ mNonAnimatableProperties.viewportWidth = width;
+ mNonAnimatableProperties.viewportHeight = height;
+ mTree->onPropertyChanged(this);
+ }
+ }
+ void setBounds(const SkRect& bounds) {
+ if (mNonAnimatableProperties.bounds != bounds) {
+ mNonAnimatableProperties.bounds = bounds;
+ mNonAnimatablePropertiesDirty = true;
+ mTree->onPropertyChanged(this);
+ }
+ }
+
+ void setScaledSize(int width, int height) {
+ if (mNonAnimatableProperties.scaledWidth != width
+ || mNonAnimatableProperties.scaledHeight != height) {
+ mNonAnimatableProperties.scaledWidth = width;
+ mNonAnimatableProperties.scaledHeight = height;
+ mNonAnimatablePropertiesDirty = true;
+ mTree->onPropertyChanged(this);
+ }
+ }
+ void setColorFilter(SkColorFilter* filter) {
+ if (UPDATE_SKPROP(mNonAnimatableProperties.colorFilter, filter)) {
+ mNonAnimatablePropertiesDirty = true;
+ mTree->onPropertyChanged(this);
+ }
+ }
+ SkColorFilter* getColorFilter() const{
+ return mNonAnimatableProperties.colorFilter;
+ }
+
+ float getViewportWidth() const {
+ return mNonAnimatableProperties.viewportWidth;
+ }
+ float getViewportHeight() const {
+ return mNonAnimatableProperties.viewportHeight;
+ }
+ float getScaledWidth() const {
+ return mNonAnimatableProperties.scaledWidth;
+ }
+ float getScaledHeight() const {
+ return mNonAnimatableProperties.scaledHeight;
+ }
+ void syncAnimatableProperties(const TreeProperties& prop) {
+ mRootAlpha = prop.mRootAlpha;
+ }
+ bool setRootAlpha(float rootAlpha) {
+ if (rootAlpha != mRootAlpha) {
+ mAnimatablePropertiesDirty = true;
+ mRootAlpha = rootAlpha;
+ mTree->onPropertyChanged(this);
+ return true;
+ }
+ return false;
+ }
+ float getRootAlpha() const { return mRootAlpha;}
+ const SkRect& getBounds() const {
+ return mNonAnimatableProperties.bounds;
+ }
+ Tree* mTree;
+ };
+ void onPropertyChanged(TreeProperties* prop);
+ TreeProperties* mutateStagingProperties() { return &mStagingProperties; }
+ const TreeProperties* stagingProperties() { return &mStagingProperties; }
+ PushStagingFunctor* getFunctor() { return &mFunctor;}
+
+ // This should only be called from animations on RT
+ TreeProperties* mutateProperties() { return &mProperties; }
+
+private:
+ class VectorDrawableFunctor : public PushStagingFunctor {
+ public:
+ VectorDrawableFunctor(Tree* tree) : mTree(tree) {}
+ virtual void operator ()() {
+ mTree->syncProperties();
+ }
+ private:
+ Tree* mTree;
+ };
+
+ SkPaint* updatePaint(SkPaint* outPaint, TreeProperties* prop);
+ bool allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height);
+ bool canReuseBitmap(const SkBitmap&, int width, int height);
+ void updateBitmapCache(SkBitmap* outCache, bool useStagingData);
+ // Cap the bitmap size, such that it won't hurt the performance too much
+ // and it won't crash due to a very large scale.
+ // The drawable will look blurry above this size.
+ const static int MAX_CACHED_BITMAP_SIZE;
+
+ bool mAllowCaching = true;
+ std::unique_ptr<Group> mRootNode;
+
+ TreeProperties mProperties = TreeProperties(this);
+ TreeProperties mStagingProperties = TreeProperties(this);
+
+ VectorDrawableFunctor mFunctor = VectorDrawableFunctor(this);
+
+ SkPaint mPaint;
+ struct Cache {
+ SkBitmap bitmap;
+ bool dirty = true;
+ };
+
+ Cache mStagingCache;
+ Cache mCache;
+
+ PropertyChangedListener mPropertyChangedListener
+ = PropertyChangedListener(&mCache.dirty, &mStagingCache.dirty);
+};
+
+} // namespace VectorDrawable
+
+typedef VectorDrawable::Path::Data PathData;
+} // namespace uirenderer
+} // namespace android
+
+#endif // ANDROID_HWUI_VPATH_H
diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h
index 11d0c4bef84f..c1bf980658b2 100644
--- a/libs/hwui/Vertex.h
+++ b/libs/hwui/Vertex.h
@@ -37,7 +37,6 @@ struct Vertex {
*/
static float GeometryFudgeFactor() { return 0.0656f; }
-
float x, y;
static inline void set(Vertex* vertex, float x, float y) {
diff --git a/libs/hwui/VertexBuffer.h b/libs/hwui/VertexBuffer.h
index 9be4d8487505..bdb5b7b381bf 100644
--- a/libs/hwui/VertexBuffer.h
+++ b/libs/hwui/VertexBuffer.h
@@ -17,7 +17,7 @@
#ifndef ANDROID_HWUI_VERTEX_BUFFER_H
#define ANDROID_HWUI_VERTEX_BUFFER_H
-#include "utils/MathUtils.h"
+#include <algorithm>
namespace android {
namespace uirenderer {
@@ -118,7 +118,7 @@ public:
TYPE* end = current + vertexCount;
mBounds.set(current->x, current->y, current->x, current->y);
for (; current < end; current++) {
- mBounds.expandToCoverVertex(current->x, current->y);
+ mBounds.expandToCover(current->x, current->y);
}
}
@@ -129,10 +129,10 @@ public:
unsigned int getSize() const { return mByteCount; }
unsigned int getIndexCount() const { return mIndexCount; }
void updateIndexCount(unsigned int newCount) {
- mIndexCount = MathUtils::min(newCount, mAllocatedIndexCount);
+ mIndexCount = std::min(newCount, mAllocatedIndexCount);
}
void updateVertexCount(unsigned int newCount) {
- mVertexCount = MathUtils::min(newCount, mAllocatedVertexCount);
+ mVertexCount = std::min(newCount, mAllocatedVertexCount);
}
MeshFeatureFlags getMeshFeatureFlags() const { return mMeshFeatureFlags; }
void setMeshFeatureFlags(int flags) {
diff --git a/libs/hwui/tests/nullegl.cpp b/libs/hwui/debug/nullegl.cpp
index b6cc2f247627..b6cc2f247627 100644
--- a/libs/hwui/tests/nullegl.cpp
+++ b/libs/hwui/debug/nullegl.cpp
diff --git a/libs/hwui/tests/nullgles.cpp b/libs/hwui/debug/nullgles.cpp
index 8ca7598a91a0..8689f9814f7b 100644
--- a/libs/hwui/tests/nullgles.cpp
+++ b/libs/hwui/debug/nullgles.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "unwrap_gles.h"
+
#include <GLES3/gl3.h>
#include <GLES2/gl2ext.h>
@@ -131,6 +133,15 @@ void glGetIntegerv(GLenum pname, GLint *data) {
}
}
+GLenum glCheckFramebufferStatus(GLenum target) {
+ switch (target) {
+ case GL_FRAMEBUFFER:
+ return GL_FRAMEBUFFER_COMPLETE;
+ default:
+ return 0; // error case
+ }
+}
+
const char* getString(GLenum name) {
switch (name) {
case GL_VENDOR:
@@ -261,8 +272,6 @@ void glInsertEventMarkerEXT(GLsizei length, const GLchar *marker) {}
void glPushGroupMarkerEXT(GLsizei length, const GLchar *marker) {}
void glPopGroupMarkerEXT(void) {}
void glDiscardFramebufferEXT(GLenum target, GLsizei numAttachments, const GLenum *attachments) {}
-void glStartTilingQCOM(GLuint x, GLuint y, GLuint width, GLuint height, GLbitfield preserveMask) {}
-void glEndTilingQCOM(GLbitfield preserveMask) {}
void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) {}
// GLES3
diff --git a/libs/hwui/debug/unwrap_gles.h b/libs/hwui/debug/unwrap_gles.h
new file mode 100644
index 000000000000..7716a735a63b
--- /dev/null
+++ b/libs/hwui/debug/unwrap_gles.h
@@ -0,0 +1,918 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifdef HWUI_GLES_WRAP_ENABLED
+#undef HWUI_GLES_WRAP_ENABLED
+
+#undef glActiveShaderProgram
+#undef glActiveShaderProgramEXT
+#undef glActiveTexture
+#undef glAlphaFunc
+#undef glAlphaFuncQCOM
+#undef glAlphaFuncx
+#undef glAlphaFuncxOES
+#undef glApplyFramebufferAttachmentCMAAINTEL
+#undef glAttachShader
+#undef glBeginConditionalRenderNV
+#undef glBeginPerfMonitorAMD
+#undef glBeginPerfQueryINTEL
+#undef glBeginQuery
+#undef glBeginQueryEXT
+#undef glBeginTransformFeedback
+#undef glBindAttribLocation
+#undef glBindBuffer
+#undef glBindBufferBase
+#undef glBindBufferRange
+#undef glBindFragDataLocationEXT
+#undef glBindFragDataLocationIndexedEXT
+#undef glBindFramebuffer
+#undef glBindFramebufferOES
+#undef glBindImageTexture
+#undef glBindProgramPipeline
+#undef glBindProgramPipelineEXT
+#undef glBindRenderbuffer
+#undef glBindRenderbufferOES
+#undef glBindSampler
+#undef glBindTexture
+#undef glBindTransformFeedback
+#undef glBindVertexArray
+#undef glBindVertexArrayOES
+#undef glBindVertexBuffer
+#undef glBlendBarrier
+#undef glBlendBarrierKHR
+#undef glBlendBarrierNV
+#undef glBlendColor
+#undef glBlendEquation
+#undef glBlendEquationOES
+#undef glBlendEquationSeparate
+#undef glBlendEquationSeparateOES
+#undef glBlendEquationSeparatei
+#undef glBlendEquationSeparateiEXT
+#undef glBlendEquationSeparateiOES
+#undef glBlendEquationi
+#undef glBlendEquationiEXT
+#undef glBlendEquationiOES
+#undef glBlendFunc
+#undef glBlendFuncSeparate
+#undef glBlendFuncSeparateOES
+#undef glBlendFuncSeparatei
+#undef glBlendFuncSeparateiEXT
+#undef glBlendFuncSeparateiOES
+#undef glBlendFunci
+#undef glBlendFunciEXT
+#undef glBlendFunciOES
+#undef glBlendParameteriNV
+#undef glBlitFramebuffer
+#undef glBlitFramebufferANGLE
+#undef glBlitFramebufferNV
+#undef glBufferData
+#undef glBufferStorageEXT
+#undef glBufferSubData
+#undef glCheckFramebufferStatus
+#undef glCheckFramebufferStatusOES
+#undef glClear
+#undef glClearBufferfi
+#undef glClearBufferfv
+#undef glClearBufferiv
+#undef glClearBufferuiv
+#undef glClearColor
+#undef glClearColorx
+#undef glClearColorxOES
+#undef glClearDepthf
+#undef glClearDepthfOES
+#undef glClearDepthx
+#undef glClearDepthxOES
+#undef glClearStencil
+#undef glClientActiveTexture
+#undef glClientWaitSync
+#undef glClientWaitSyncAPPLE
+#undef glClipPlanef
+#undef glClipPlanefIMG
+#undef glClipPlanefOES
+#undef glClipPlanex
+#undef glClipPlanexIMG
+#undef glClipPlanexOES
+#undef glColor4f
+#undef glColor4ub
+#undef glColor4x
+#undef glColor4xOES
+#undef glColorMask
+#undef glColorMaski
+#undef glColorMaskiEXT
+#undef glColorMaskiOES
+#undef glColorPointer
+#undef glCompileShader
+#undef glCompressedTexImage2D
+#undef glCompressedTexImage3D
+#undef glCompressedTexImage3DOES
+#undef glCompressedTexSubImage2D
+#undef glCompressedTexSubImage3D
+#undef glCompressedTexSubImage3DOES
+#undef glCopyBufferSubData
+#undef glCopyBufferSubDataNV
+#undef glCopyImageSubData
+#undef glCopyImageSubDataEXT
+#undef glCopyImageSubDataOES
+#undef glCopyPathNV
+#undef glCopyTexImage2D
+#undef glCopyTexSubImage2D
+#undef glCopyTexSubImage3D
+#undef glCopyTexSubImage3DOES
+#undef glCopyTextureLevelsAPPLE
+#undef glCoverFillPathInstancedNV
+#undef glCoverFillPathNV
+#undef glCoverStrokePathInstancedNV
+#undef glCoverStrokePathNV
+#undef glCoverageMaskNV
+#undef glCoverageModulationNV
+#undef glCoverageModulationTableNV
+#undef glCoverageOperationNV
+#undef glCreatePerfQueryINTEL
+#undef glCreateProgram
+#undef glCreateShader
+#undef glCreateShaderProgramv
+#undef glCreateShaderProgramvEXT
+#undef glCullFace
+#undef glCurrentPaletteMatrixOES
+#undef glDebugMessageCallback
+#undef glDebugMessageCallbackKHR
+#undef glDebugMessageControl
+#undef glDebugMessageControlKHR
+#undef glDebugMessageInsert
+#undef glDebugMessageInsertKHR
+#undef glDeleteBuffers
+#undef glDeleteFencesNV
+#undef glDeleteFramebuffers
+#undef glDeleteFramebuffersOES
+#undef glDeletePathsNV
+#undef glDeletePerfMonitorsAMD
+#undef glDeletePerfQueryINTEL
+#undef glDeleteProgram
+#undef glDeleteProgramPipelines
+#undef glDeleteProgramPipelinesEXT
+#undef glDeleteQueries
+#undef glDeleteQueriesEXT
+#undef glDeleteRenderbuffers
+#undef glDeleteRenderbuffersOES
+#undef glDeleteSamplers
+#undef glDeleteShader
+#undef glDeleteSync
+#undef glDeleteSyncAPPLE
+#undef glDeleteTextures
+#undef glDeleteTransformFeedbacks
+#undef glDeleteVertexArrays
+#undef glDeleteVertexArraysOES
+#undef glDepthFunc
+#undef glDepthMask
+#undef glDepthRangeArrayfvNV
+#undef glDepthRangeIndexedfNV
+#undef glDepthRangef
+#undef glDepthRangefOES
+#undef glDepthRangex
+#undef glDepthRangexOES
+#undef glDetachShader
+#undef glDisable
+#undef glDisableClientState
+#undef glDisableDriverControlQCOM
+#undef glDisableVertexAttribArray
+#undef glDisablei
+#undef glDisableiEXT
+#undef glDisableiNV
+#undef glDisableiOES
+#undef glDiscardFramebufferEXT
+#undef glDispatchCompute
+#undef glDispatchComputeIndirect
+#undef glDrawArrays
+#undef glDrawArraysIndirect
+#undef glDrawArraysInstanced
+#undef glDrawArraysInstancedANGLE
+#undef glDrawArraysInstancedBaseInstanceEXT
+#undef glDrawArraysInstancedEXT
+#undef glDrawArraysInstancedNV
+#undef glDrawBuffers
+#undef glDrawBuffersEXT
+#undef glDrawBuffersIndexedEXT
+#undef glDrawBuffersNV
+#undef glDrawElements
+#undef glDrawElementsBaseVertex
+#undef glDrawElementsBaseVertexEXT
+#undef glDrawElementsBaseVertexOES
+#undef glDrawElementsIndirect
+#undef glDrawElementsInstanced
+#undef glDrawElementsInstancedANGLE
+#undef glDrawElementsInstancedBaseInstanceEXT
+#undef glDrawElementsInstancedBaseVertex
+#undef glDrawElementsInstancedBaseVertexBaseInstanceEXT
+#undef glDrawElementsInstancedBaseVertexEXT
+#undef glDrawElementsInstancedBaseVertexOES
+#undef glDrawElementsInstancedEXT
+#undef glDrawElementsInstancedNV
+#undef glDrawRangeElements
+#undef glDrawRangeElementsBaseVertex
+#undef glDrawRangeElementsBaseVertexEXT
+#undef glDrawRangeElementsBaseVertexOES
+#undef glDrawTexfOES
+#undef glDrawTexfvOES
+#undef glDrawTexiOES
+#undef glDrawTexivOES
+#undef glDrawTexsOES
+#undef glDrawTexsvOES
+#undef glDrawTexxOES
+#undef glDrawTexxvOES
+#undef glEGLImageTargetRenderbufferStorageOES
+#undef glEGLImageTargetTexture2DOES
+#undef glEnable
+#undef glEnableClientState
+#undef glEnableDriverControlQCOM
+#undef glEnableVertexAttribArray
+#undef glEnablei
+#undef glEnableiEXT
+#undef glEnableiNV
+#undef glEnableiOES
+#undef glEndConditionalRenderNV
+#undef glEndPerfMonitorAMD
+#undef glEndPerfQueryINTEL
+#undef glEndQuery
+#undef glEndQueryEXT
+#undef glEndTilingQCOM
+#undef glEndTransformFeedback
+#undef glExtGetBufferPointervQCOM
+#undef glExtGetBuffersQCOM
+#undef glExtGetFramebuffersQCOM
+#undef glExtGetProgramBinarySourceQCOM
+#undef glExtGetProgramsQCOM
+#undef glExtGetRenderbuffersQCOM
+#undef glExtGetShadersQCOM
+#undef glExtGetTexLevelParameterivQCOM
+#undef glExtGetTexSubImageQCOM
+#undef glExtGetTexturesQCOM
+#undef glExtIsProgramBinaryQCOM
+#undef glExtTexObjectStateOverrideiQCOM
+#undef glFenceSync
+#undef glFenceSyncAPPLE
+#undef glFinish
+#undef glFinishFenceNV
+#undef glFlush
+#undef glFlushMappedBufferRange
+#undef glFlushMappedBufferRangeEXT
+#undef glFogf
+#undef glFogfv
+#undef glFogx
+#undef glFogxOES
+#undef glFogxv
+#undef glFogxvOES
+#undef glFragmentCoverageColorNV
+#undef glFramebufferParameteri
+#undef glFramebufferRenderbuffer
+#undef glFramebufferRenderbufferOES
+#undef glFramebufferSampleLocationsfvNV
+#undef glFramebufferTexture
+#undef glFramebufferTexture2D
+#undef glFramebufferTexture2DMultisampleEXT
+#undef glFramebufferTexture2DMultisampleIMG
+#undef glFramebufferTexture2DOES
+#undef glFramebufferTexture3DOES
+#undef glFramebufferTextureEXT
+#undef glFramebufferTextureLayer
+#undef glFramebufferTextureMultisampleMultiviewOVR
+#undef glFramebufferTextureMultiviewOVR
+#undef glFramebufferTextureOES
+#undef glFrontFace
+#undef glFrustumf
+#undef glFrustumfOES
+#undef glFrustumx
+#undef glFrustumxOES
+#undef glGenBuffers
+#undef glGenFencesNV
+#undef glGenFramebuffers
+#undef glGenFramebuffersOES
+#undef glGenPathsNV
+#undef glGenPerfMonitorsAMD
+#undef glGenProgramPipelines
+#undef glGenProgramPipelinesEXT
+#undef glGenQueries
+#undef glGenQueriesEXT
+#undef glGenRenderbuffers
+#undef glGenRenderbuffersOES
+#undef glGenSamplers
+#undef glGenTextures
+#undef glGenTransformFeedbacks
+#undef glGenVertexArrays
+#undef glGenVertexArraysOES
+#undef glGenerateMipmap
+#undef glGenerateMipmapOES
+#undef glGetActiveAttrib
+#undef glGetActiveUniform
+#undef glGetActiveUniformBlockName
+#undef glGetActiveUniformBlockiv
+#undef glGetActiveUniformsiv
+#undef glGetAttachedShaders
+#undef glGetAttribLocation
+#undef glGetBooleani_v
+#undef glGetBooleanv
+#undef glGetBufferParameteri64v
+#undef glGetBufferParameteriv
+#undef glGetBufferPointerv
+#undef glGetBufferPointervOES
+#undef glGetClipPlanef
+#undef glGetClipPlanefOES
+#undef glGetClipPlanex
+#undef glGetClipPlanexOES
+#undef glGetCoverageModulationTableNV
+#undef glGetDebugMessageLog
+#undef glGetDebugMessageLogKHR
+#undef glGetDriverControlStringQCOM
+#undef glGetDriverControlsQCOM
+#undef glGetError
+#undef glGetFenceivNV
+#undef glGetFirstPerfQueryIdINTEL
+#undef glGetFixedv
+#undef glGetFixedvOES
+#undef glGetFloati_vNV
+#undef glGetFloatv
+#undef glGetFragDataIndexEXT
+#undef glGetFragDataLocation
+#undef glGetFramebufferAttachmentParameteriv
+#undef glGetFramebufferAttachmentParameterivOES
+#undef glGetFramebufferParameteriv
+#undef glGetGraphicsResetStatus
+#undef glGetGraphicsResetStatusEXT
+#undef glGetGraphicsResetStatusKHR
+#undef glGetImageHandleNV
+#undef glGetInteger64i_v
+#undef glGetInteger64v
+#undef glGetInteger64vAPPLE
+#undef glGetIntegeri_v
+#undef glGetIntegeri_vEXT
+#undef glGetIntegerv
+#undef glGetInternalformatSampleivNV
+#undef glGetInternalformativ
+#undef glGetLightfv
+#undef glGetLightxv
+#undef glGetLightxvOES
+#undef glGetMaterialfv
+#undef glGetMaterialxv
+#undef glGetMaterialxvOES
+#undef glGetMultisamplefv
+#undef glGetNextPerfQueryIdINTEL
+#undef glGetObjectLabel
+#undef glGetObjectLabelEXT
+#undef glGetObjectLabelKHR
+#undef glGetObjectPtrLabel
+#undef glGetObjectPtrLabelKHR
+#undef glGetPathCommandsNV
+#undef glGetPathCoordsNV
+#undef glGetPathDashArrayNV
+#undef glGetPathLengthNV
+#undef glGetPathMetricRangeNV
+#undef glGetPathMetricsNV
+#undef glGetPathParameterfvNV
+#undef glGetPathParameterivNV
+#undef glGetPathSpacingNV
+#undef glGetPerfCounterInfoINTEL
+#undef glGetPerfMonitorCounterDataAMD
+#undef glGetPerfMonitorCounterInfoAMD
+#undef glGetPerfMonitorCounterStringAMD
+#undef glGetPerfMonitorCountersAMD
+#undef glGetPerfMonitorGroupStringAMD
+#undef glGetPerfMonitorGroupsAMD
+#undef glGetPerfQueryDataINTEL
+#undef glGetPerfQueryIdByNameINTEL
+#undef glGetPerfQueryInfoINTEL
+#undef glGetPointerv
+#undef glGetPointervKHR
+#undef glGetProgramBinary
+#undef glGetProgramBinaryOES
+#undef glGetProgramInfoLog
+#undef glGetProgramInterfaceiv
+#undef glGetProgramPipelineInfoLog
+#undef glGetProgramPipelineInfoLogEXT
+#undef glGetProgramPipelineiv
+#undef glGetProgramPipelineivEXT
+#undef glGetProgramResourceIndex
+#undef glGetProgramResourceLocation
+#undef glGetProgramResourceLocationIndexEXT
+#undef glGetProgramResourceName
+#undef glGetProgramResourcefvNV
+#undef glGetProgramResourceiv
+#undef glGetProgramiv
+#undef glGetQueryObjecti64vEXT
+#undef glGetQueryObjectivEXT
+#undef glGetQueryObjectui64vEXT
+#undef glGetQueryObjectuiv
+#undef glGetQueryObjectuivEXT
+#undef glGetQueryiv
+#undef glGetQueryivEXT
+#undef glGetRenderbufferParameteriv
+#undef glGetRenderbufferParameterivOES
+#undef glGetSamplerParameterIiv
+#undef glGetSamplerParameterIivEXT
+#undef glGetSamplerParameterIivOES
+#undef glGetSamplerParameterIuiv
+#undef glGetSamplerParameterIuivEXT
+#undef glGetSamplerParameterIuivOES
+#undef glGetSamplerParameterfv
+#undef glGetSamplerParameteriv
+#undef glGetShaderInfoLog
+#undef glGetShaderPrecisionFormat
+#undef glGetShaderSource
+#undef glGetShaderiv
+#undef glGetString
+#undef glGetStringi
+#undef glGetSynciv
+#undef glGetSyncivAPPLE
+#undef glGetTexEnvfv
+#undef glGetTexEnviv
+#undef glGetTexEnvxv
+#undef glGetTexEnvxvOES
+#undef glGetTexGenfvOES
+#undef glGetTexGenivOES
+#undef glGetTexGenxvOES
+#undef glGetTexLevelParameterfv
+#undef glGetTexLevelParameteriv
+#undef glGetTexParameterIiv
+#undef glGetTexParameterIivEXT
+#undef glGetTexParameterIivOES
+#undef glGetTexParameterIuiv
+#undef glGetTexParameterIuivEXT
+#undef glGetTexParameterIuivOES
+#undef glGetTexParameterfv
+#undef glGetTexParameteriv
+#undef glGetTexParameterxv
+#undef glGetTexParameterxvOES
+#undef glGetTextureHandleNV
+#undef glGetTextureSamplerHandleNV
+#undef glGetTransformFeedbackVarying
+#undef glGetTranslatedShaderSourceANGLE
+#undef glGetUniformBlockIndex
+#undef glGetUniformIndices
+#undef glGetUniformLocation
+#undef glGetUniformfv
+#undef glGetUniformiv
+#undef glGetUniformuiv
+#undef glGetVertexAttribIiv
+#undef glGetVertexAttribIuiv
+#undef glGetVertexAttribPointerv
+#undef glGetVertexAttribfv
+#undef glGetVertexAttribiv
+#undef glGetnUniformfv
+#undef glGetnUniformfvEXT
+#undef glGetnUniformfvKHR
+#undef glGetnUniformiv
+#undef glGetnUniformivEXT
+#undef glGetnUniformivKHR
+#undef glGetnUniformuiv
+#undef glGetnUniformuivKHR
+#undef glHint
+#undef glInsertEventMarkerEXT
+#undef glInterpolatePathsNV
+#undef glInvalidateFramebuffer
+#undef glInvalidateSubFramebuffer
+#undef glIsBuffer
+#undef glIsEnabled
+#undef glIsEnabledi
+#undef glIsEnablediEXT
+#undef glIsEnablediNV
+#undef glIsEnablediOES
+#undef glIsFenceNV
+#undef glIsFramebuffer
+#undef glIsFramebufferOES
+#undef glIsImageHandleResidentNV
+#undef glIsPathNV
+#undef glIsPointInFillPathNV
+#undef glIsPointInStrokePathNV
+#undef glIsProgram
+#undef glIsProgramPipeline
+#undef glIsProgramPipelineEXT
+#undef glIsQuery
+#undef glIsQueryEXT
+#undef glIsRenderbuffer
+#undef glIsRenderbufferOES
+#undef glIsSampler
+#undef glIsShader
+#undef glIsSync
+#undef glIsSyncAPPLE
+#undef glIsTexture
+#undef glIsTextureHandleResidentNV
+#undef glIsTransformFeedback
+#undef glIsVertexArray
+#undef glIsVertexArrayOES
+#undef glLabelObjectEXT
+#undef glLightModelf
+#undef glLightModelfv
+#undef glLightModelx
+#undef glLightModelxOES
+#undef glLightModelxv
+#undef glLightModelxvOES
+#undef glLightf
+#undef glLightfv
+#undef glLightx
+#undef glLightxOES
+#undef glLightxv
+#undef glLightxvOES
+#undef glLineWidth
+#undef glLineWidthx
+#undef glLineWidthxOES
+#undef glLinkProgram
+#undef glLoadIdentity
+#undef glLoadMatrixf
+#undef glLoadMatrixx
+#undef glLoadMatrixxOES
+#undef glLoadPaletteFromModelViewMatrixOES
+#undef glLogicOp
+#undef glMakeImageHandleNonResidentNV
+#undef glMakeImageHandleResidentNV
+#undef glMakeTextureHandleNonResidentNV
+#undef glMakeTextureHandleResidentNV
+#undef glMapBufferOES
+#undef glMapBufferRange
+#undef glMapBufferRangeEXT
+#undef glMaterialf
+#undef glMaterialfv
+#undef glMaterialx
+#undef glMaterialxOES
+#undef glMaterialxv
+#undef glMaterialxvOES
+#undef glMatrixIndexPointerOES
+#undef glMatrixLoad3x2fNV
+#undef glMatrixLoad3x3fNV
+#undef glMatrixLoadTranspose3x3fNV
+#undef glMatrixMode
+#undef glMatrixMult3x2fNV
+#undef glMatrixMult3x3fNV
+#undef glMatrixMultTranspose3x3fNV
+#undef glMemoryBarrier
+#undef glMemoryBarrierByRegion
+#undef glMinSampleShading
+#undef glMinSampleShadingOES
+#undef glMultMatrixf
+#undef glMultMatrixx
+#undef glMultMatrixxOES
+#undef glMultiDrawArraysEXT
+#undef glMultiDrawArraysIndirectEXT
+#undef glMultiDrawElementsBaseVertexEXT
+#undef glMultiDrawElementsBaseVertexOES
+#undef glMultiDrawElementsEXT
+#undef glMultiDrawElementsIndirectEXT
+#undef glMultiTexCoord4f
+#undef glMultiTexCoord4x
+#undef glMultiTexCoord4xOES
+#undef glNamedFramebufferSampleLocationsfvNV
+#undef glNormal3f
+#undef glNormal3x
+#undef glNormal3xOES
+#undef glNormalPointer
+#undef glObjectLabel
+#undef glObjectLabelKHR
+#undef glObjectPtrLabel
+#undef glObjectPtrLabelKHR
+#undef glOrthof
+#undef glOrthofOES
+#undef glOrthox
+#undef glOrthoxOES
+#undef glPatchParameteri
+#undef glPatchParameteriEXT
+#undef glPatchParameteriOES
+#undef glPathCommandsNV
+#undef glPathCoordsNV
+#undef glPathCoverDepthFuncNV
+#undef glPathDashArrayNV
+#undef glPathGlyphIndexArrayNV
+#undef glPathGlyphIndexRangeNV
+#undef glPathGlyphRangeNV
+#undef glPathGlyphsNV
+#undef glPathMemoryGlyphIndexArrayNV
+#undef glPathParameterfNV
+#undef glPathParameterfvNV
+#undef glPathParameteriNV
+#undef glPathParameterivNV
+#undef glPathStencilDepthOffsetNV
+#undef glPathStencilFuncNV
+#undef glPathStringNV
+#undef glPathSubCommandsNV
+#undef glPathSubCoordsNV
+#undef glPauseTransformFeedback
+#undef glPixelStorei
+#undef glPointAlongPathNV
+#undef glPointParameterf
+#undef glPointParameterfv
+#undef glPointParameterx
+#undef glPointParameterxOES
+#undef glPointParameterxv
+#undef glPointParameterxvOES
+#undef glPointSize
+#undef glPointSizePointerOES
+#undef glPointSizex
+#undef glPointSizexOES
+#undef glPolygonModeNV
+#undef glPolygonOffset
+#undef glPolygonOffsetx
+#undef glPolygonOffsetxOES
+#undef glPopDebugGroup
+#undef glPopDebugGroupKHR
+#undef glPopGroupMarkerEXT
+#undef glPopMatrix
+#undef glPrimitiveBoundingBox
+#undef glPrimitiveBoundingBoxEXT
+#undef glPrimitiveBoundingBoxOES
+#undef glProgramBinary
+#undef glProgramBinaryOES
+#undef glProgramParameteri
+#undef glProgramParameteriEXT
+#undef glProgramPathFragmentInputGenNV
+#undef glProgramUniform1f
+#undef glProgramUniform1fEXT
+#undef glProgramUniform1fv
+#undef glProgramUniform1fvEXT
+#undef glProgramUniform1i
+#undef glProgramUniform1iEXT
+#undef glProgramUniform1iv
+#undef glProgramUniform1ivEXT
+#undef glProgramUniform1ui
+#undef glProgramUniform1uiEXT
+#undef glProgramUniform1uiv
+#undef glProgramUniform1uivEXT
+#undef glProgramUniform2f
+#undef glProgramUniform2fEXT
+#undef glProgramUniform2fv
+#undef glProgramUniform2fvEXT
+#undef glProgramUniform2i
+#undef glProgramUniform2iEXT
+#undef glProgramUniform2iv
+#undef glProgramUniform2ivEXT
+#undef glProgramUniform2ui
+#undef glProgramUniform2uiEXT
+#undef glProgramUniform2uiv
+#undef glProgramUniform2uivEXT
+#undef glProgramUniform3f
+#undef glProgramUniform3fEXT
+#undef glProgramUniform3fv
+#undef glProgramUniform3fvEXT
+#undef glProgramUniform3i
+#undef glProgramUniform3iEXT
+#undef glProgramUniform3iv
+#undef glProgramUniform3ivEXT
+#undef glProgramUniform3ui
+#undef glProgramUniform3uiEXT
+#undef glProgramUniform3uiv
+#undef glProgramUniform3uivEXT
+#undef glProgramUniform4f
+#undef glProgramUniform4fEXT
+#undef glProgramUniform4fv
+#undef glProgramUniform4fvEXT
+#undef glProgramUniform4i
+#undef glProgramUniform4iEXT
+#undef glProgramUniform4iv
+#undef glProgramUniform4ivEXT
+#undef glProgramUniform4ui
+#undef glProgramUniform4uiEXT
+#undef glProgramUniform4uiv
+#undef glProgramUniform4uivEXT
+#undef glProgramUniformHandleui64NV
+#undef glProgramUniformHandleui64vNV
+#undef glProgramUniformMatrix2fv
+#undef glProgramUniformMatrix2fvEXT
+#undef glProgramUniformMatrix2x3fv
+#undef glProgramUniformMatrix2x3fvEXT
+#undef glProgramUniformMatrix2x4fv
+#undef glProgramUniformMatrix2x4fvEXT
+#undef glProgramUniformMatrix3fv
+#undef glProgramUniformMatrix3fvEXT
+#undef glProgramUniformMatrix3x2fv
+#undef glProgramUniformMatrix3x2fvEXT
+#undef glProgramUniformMatrix3x4fv
+#undef glProgramUniformMatrix3x4fvEXT
+#undef glProgramUniformMatrix4fv
+#undef glProgramUniformMatrix4fvEXT
+#undef glProgramUniformMatrix4x2fv
+#undef glProgramUniformMatrix4x2fvEXT
+#undef glProgramUniformMatrix4x3fv
+#undef glProgramUniformMatrix4x3fvEXT
+#undef glPushDebugGroup
+#undef glPushDebugGroupKHR
+#undef glPushGroupMarkerEXT
+#undef glPushMatrix
+#undef glQueryCounterEXT
+#undef glQueryMatrixxOES
+#undef glRasterSamplesEXT
+#undef glReadBuffer
+#undef glReadBufferIndexedEXT
+#undef glReadBufferNV
+#undef glReadPixels
+#undef glReadnPixels
+#undef glReadnPixelsEXT
+#undef glReadnPixelsKHR
+#undef glReleaseShaderCompiler
+#undef glRenderbufferStorage
+#undef glRenderbufferStorageMultisample
+#undef glRenderbufferStorageMultisampleANGLE
+#undef glRenderbufferStorageMultisampleAPPLE
+#undef glRenderbufferStorageMultisampleEXT
+#undef glRenderbufferStorageMultisampleIMG
+#undef glRenderbufferStorageMultisampleNV
+#undef glRenderbufferStorageOES
+#undef glResolveDepthValuesNV
+#undef glResolveMultisampleFramebufferAPPLE
+#undef glResumeTransformFeedback
+#undef glRotatef
+#undef glRotatex
+#undef glRotatexOES
+#undef glSampleCoverage
+#undef glSampleCoveragex
+#undef glSampleCoveragexOES
+#undef glSampleMaski
+#undef glSamplerParameterIiv
+#undef glSamplerParameterIivEXT
+#undef glSamplerParameterIivOES
+#undef glSamplerParameterIuiv
+#undef glSamplerParameterIuivEXT
+#undef glSamplerParameterIuivOES
+#undef glSamplerParameterf
+#undef glSamplerParameterfv
+#undef glSamplerParameteri
+#undef glSamplerParameteriv
+#undef glScalef
+#undef glScalex
+#undef glScalexOES
+#undef glScissor
+#undef glScissorArrayvNV
+#undef glScissorIndexedNV
+#undef glScissorIndexedvNV
+#undef glSelectPerfMonitorCountersAMD
+#undef glSetFenceNV
+#undef glShadeModel
+#undef glShaderBinary
+#undef glShaderSource
+#undef glStartTilingQCOM
+#undef glStencilFillPathInstancedNV
+#undef glStencilFillPathNV
+#undef glStencilFunc
+#undef glStencilFuncSeparate
+#undef glStencilMask
+#undef glStencilMaskSeparate
+#undef glStencilOp
+#undef glStencilOpSeparate
+#undef glStencilStrokePathInstancedNV
+#undef glStencilStrokePathNV
+#undef glStencilThenCoverFillPathInstancedNV
+#undef glStencilThenCoverFillPathNV
+#undef glStencilThenCoverStrokePathInstancedNV
+#undef glStencilThenCoverStrokePathNV
+#undef glSubpixelPrecisionBiasNV
+#undef glTestFenceNV
+#undef glTexBuffer
+#undef glTexBufferEXT
+#undef glTexBufferOES
+#undef glTexBufferRange
+#undef glTexBufferRangeEXT
+#undef glTexBufferRangeOES
+#undef glTexCoordPointer
+#undef glTexEnvf
+#undef glTexEnvfv
+#undef glTexEnvi
+#undef glTexEnviv
+#undef glTexEnvx
+#undef glTexEnvxOES
+#undef glTexEnvxv
+#undef glTexEnvxvOES
+#undef glTexGenfOES
+#undef glTexGenfvOES
+#undef glTexGeniOES
+#undef glTexGenivOES
+#undef glTexGenxOES
+#undef glTexGenxvOES
+#undef glTexImage2D
+#undef glTexImage3D
+#undef glTexImage3DOES
+#undef glTexPageCommitmentEXT
+#undef glTexParameterIiv
+#undef glTexParameterIivEXT
+#undef glTexParameterIivOES
+#undef glTexParameterIuiv
+#undef glTexParameterIuivEXT
+#undef glTexParameterIuivOES
+#undef glTexParameterf
+#undef glTexParameterfv
+#undef glTexParameteri
+#undef glTexParameteriv
+#undef glTexParameterx
+#undef glTexParameterxOES
+#undef glTexParameterxv
+#undef glTexParameterxvOES
+#undef glTexStorage1DEXT
+#undef glTexStorage2D
+#undef glTexStorage2DEXT
+#undef glTexStorage2DMultisample
+#undef glTexStorage3D
+#undef glTexStorage3DEXT
+#undef glTexStorage3DMultisample
+#undef glTexStorage3DMultisampleOES
+#undef glTexSubImage2D
+#undef glTexSubImage3D
+#undef glTexSubImage3DOES
+#undef glTextureStorage1DEXT
+#undef glTextureStorage2DEXT
+#undef glTextureStorage3DEXT
+#undef glTextureViewEXT
+#undef glTextureViewOES
+#undef glTransformFeedbackVaryings
+#undef glTransformPathNV
+#undef glTranslatef
+#undef glTranslatex
+#undef glTranslatexOES
+#undef glUniform1f
+#undef glUniform1fv
+#undef glUniform1i
+#undef glUniform1iv
+#undef glUniform1ui
+#undef glUniform1uiv
+#undef glUniform2f
+#undef glUniform2fv
+#undef glUniform2i
+#undef glUniform2iv
+#undef glUniform2ui
+#undef glUniform2uiv
+#undef glUniform3f
+#undef glUniform3fv
+#undef glUniform3i
+#undef glUniform3iv
+#undef glUniform3ui
+#undef glUniform3uiv
+#undef glUniform4f
+#undef glUniform4fv
+#undef glUniform4i
+#undef glUniform4iv
+#undef glUniform4ui
+#undef glUniform4uiv
+#undef glUniformBlockBinding
+#undef glUniformHandleui64NV
+#undef glUniformHandleui64vNV
+#undef glUniformMatrix2fv
+#undef glUniformMatrix2x3fv
+#undef glUniformMatrix2x3fvNV
+#undef glUniformMatrix2x4fv
+#undef glUniformMatrix2x4fvNV
+#undef glUniformMatrix3fv
+#undef glUniformMatrix3x2fv
+#undef glUniformMatrix3x2fvNV
+#undef glUniformMatrix3x4fv
+#undef glUniformMatrix3x4fvNV
+#undef glUniformMatrix4fv
+#undef glUniformMatrix4x2fv
+#undef glUniformMatrix4x2fvNV
+#undef glUniformMatrix4x3fv
+#undef glUniformMatrix4x3fvNV
+#undef glUnmapBuffer
+#undef glUnmapBufferOES
+#undef glUseProgram
+#undef glUseProgramStages
+#undef glUseProgramStagesEXT
+#undef glValidateProgram
+#undef glValidateProgramPipeline
+#undef glValidateProgramPipelineEXT
+#undef glVertexAttrib1f
+#undef glVertexAttrib1fv
+#undef glVertexAttrib2f
+#undef glVertexAttrib2fv
+#undef glVertexAttrib3f
+#undef glVertexAttrib3fv
+#undef glVertexAttrib4f
+#undef glVertexAttrib4fv
+#undef glVertexAttribBinding
+#undef glVertexAttribDivisor
+#undef glVertexAttribDivisorANGLE
+#undef glVertexAttribDivisorEXT
+#undef glVertexAttribDivisorNV
+#undef glVertexAttribFormat
+#undef glVertexAttribI4i
+#undef glVertexAttribI4iv
+#undef glVertexAttribI4ui
+#undef glVertexAttribI4uiv
+#undef glVertexAttribIFormat
+#undef glVertexAttribIPointer
+#undef glVertexAttribPointer
+#undef glVertexBindingDivisor
+#undef glVertexPointer
+#undef glViewport
+#undef glViewportArrayvNV
+#undef glViewportIndexedfNV
+#undef glViewportIndexedfvNV
+#undef glWaitSync
+#undef glWaitSyncAPPLE
+#undef glWeightPathsNV
+#undef glWeightPointerOES
+
+#endif // HWUI_GLES_WRAP_ENABLED
diff --git a/libs/hwui/debug/wrap_gles.cpp b/libs/hwui/debug/wrap_gles.cpp
new file mode 100644
index 000000000000..c4f2e3537fe8
--- /dev/null
+++ b/libs/hwui/debug/wrap_gles.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 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 "unwrap_gles.h"
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES/gl.h>
+#include <GLES/glext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <GLES3/gl3.h>
+#include <GLES3/gl31.h>
+#include <GLES3/gl32.h>
+
+#include <cutils/log.h>
+
+void assertNoGlErrors(const char* apicall) {
+ GLenum status = GL_NO_ERROR;
+ GLenum lastError = GL_NO_ERROR;
+ const char* lastErrorName = nullptr;
+ while ((status = glGetError()) != GL_NO_ERROR) {
+ lastError = status;
+ switch (status) {
+ case GL_INVALID_ENUM:
+ ALOGE("GL error: GL_INVALID_ENUM");
+ lastErrorName = "GL_INVALID_ENUM";
+ break;
+ case GL_INVALID_VALUE:
+ ALOGE("GL error: GL_INVALID_VALUE");
+ lastErrorName = "GL_INVALID_VALUE";
+ break;
+ case GL_INVALID_OPERATION:
+ ALOGE("GL error: GL_INVALID_OPERATION");
+ lastErrorName = "GL_INVALID_OPERATION";
+ break;
+ case GL_OUT_OF_MEMORY:
+ ALOGE("GL error: Out of memory!");
+ lastErrorName = "GL_OUT_OF_MEMORY";
+ break;
+ default:
+ ALOGE("GL error: 0x%x", status);
+ lastErrorName = "UNKNOWN";
+ }
+ }
+ LOG_ALWAYS_FATAL_IF(lastError != GL_NO_ERROR,
+ "%s error! %s (0x%x)", apicall, lastErrorName, lastError);
+}
+
+#define API_ENTRY(x) wrap_##x
+#define CALL_GL_API(x, ...) x(__VA_ARGS__); assertNoGlErrors(#x)
+#define CALL_GL_API_RETURN(x, ...) auto ret = x(__VA_ARGS__);\
+ assertNoGlErrors(#x);\
+ return ret
+
+extern "C" {
+#include <gl2_api.in>
+#include <gl2ext_api.in>
+
+// libGLESv2 handles these specially, so they are not in gl2_api.in
+
+void API_ENTRY(glGetBooleanv)(GLenum pname, GLboolean *data) {
+ CALL_GL_API(glGetBooleanv, pname, data);
+}
+void API_ENTRY(glGetFloatv)(GLenum pname, GLfloat *data) {
+ CALL_GL_API(glGetFloatv, pname, data);
+}
+void API_ENTRY(glGetIntegerv)(GLenum pname, GLint *data) {
+ CALL_GL_API(glGetIntegerv, pname, data);
+}
+const GLubyte * API_ENTRY(glGetString)(GLenum name) {
+ CALL_GL_API_RETURN(glGetString, name);
+}
+const GLubyte * API_ENTRY(glGetStringi)(GLenum name, GLuint index) {
+ CALL_GL_API_RETURN(glGetStringi, name, index);
+}
+void API_ENTRY(glGetInteger64v)(GLenum pname, GLint64 *data) {
+ CALL_GL_API(glGetInteger64v, pname, data);
+}
+}
diff --git a/libs/hwui/debug/wrap_gles.h b/libs/hwui/debug/wrap_gles.h
new file mode 100644
index 000000000000..4a3537442e73
--- /dev/null
+++ b/libs/hwui/debug/wrap_gles.h
@@ -0,0 +1,918 @@
+/*
+ * Copyright (C) 2016 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 HWUI_GLES_WRAP_ENABLED
+#define HWUI_GLES_WRAP_ENABLED
+
+#define glActiveShaderProgram wrap_glActiveShaderProgram
+#define glActiveShaderProgramEXT wrap_glActiveShaderProgramEXT
+#define glActiveTexture wrap_glActiveTexture
+#define glAlphaFunc wrap_glAlphaFunc
+#define glAlphaFuncQCOM wrap_glAlphaFuncQCOM
+#define glAlphaFuncx wrap_glAlphaFuncx
+#define glAlphaFuncxOES wrap_glAlphaFuncxOES
+#define glApplyFramebufferAttachmentCMAAINTEL wrap_glApplyFramebufferAttachmentCMAAINTEL
+#define glAttachShader wrap_glAttachShader
+#define glBeginConditionalRenderNV wrap_glBeginConditionalRenderNV
+#define glBeginPerfMonitorAMD wrap_glBeginPerfMonitorAMD
+#define glBeginPerfQueryINTEL wrap_glBeginPerfQueryINTEL
+#define glBeginQuery wrap_glBeginQuery
+#define glBeginQueryEXT wrap_glBeginQueryEXT
+#define glBeginTransformFeedback wrap_glBeginTransformFeedback
+#define glBindAttribLocation wrap_glBindAttribLocation
+#define glBindBuffer wrap_glBindBuffer
+#define glBindBufferBase wrap_glBindBufferBase
+#define glBindBufferRange wrap_glBindBufferRange
+#define glBindFragDataLocationEXT wrap_glBindFragDataLocationEXT
+#define glBindFragDataLocationIndexedEXT wrap_glBindFragDataLocationIndexedEXT
+#define glBindFramebuffer wrap_glBindFramebuffer
+#define glBindFramebufferOES wrap_glBindFramebufferOES
+#define glBindImageTexture wrap_glBindImageTexture
+#define glBindProgramPipeline wrap_glBindProgramPipeline
+#define glBindProgramPipelineEXT wrap_glBindProgramPipelineEXT
+#define glBindRenderbuffer wrap_glBindRenderbuffer
+#define glBindRenderbufferOES wrap_glBindRenderbufferOES
+#define glBindSampler wrap_glBindSampler
+#define glBindTexture wrap_glBindTexture
+#define glBindTransformFeedback wrap_glBindTransformFeedback
+#define glBindVertexArray wrap_glBindVertexArray
+#define glBindVertexArrayOES wrap_glBindVertexArrayOES
+#define glBindVertexBuffer wrap_glBindVertexBuffer
+#define glBlendBarrier wrap_glBlendBarrier
+#define glBlendBarrierKHR wrap_glBlendBarrierKHR
+#define glBlendBarrierNV wrap_glBlendBarrierNV
+#define glBlendColor wrap_glBlendColor
+#define glBlendEquation wrap_glBlendEquation
+#define glBlendEquationOES wrap_glBlendEquationOES
+#define glBlendEquationSeparate wrap_glBlendEquationSeparate
+#define glBlendEquationSeparateOES wrap_glBlendEquationSeparateOES
+#define glBlendEquationSeparatei wrap_glBlendEquationSeparatei
+#define glBlendEquationSeparateiEXT wrap_glBlendEquationSeparateiEXT
+#define glBlendEquationSeparateiOES wrap_glBlendEquationSeparateiOES
+#define glBlendEquationi wrap_glBlendEquationi
+#define glBlendEquationiEXT wrap_glBlendEquationiEXT
+#define glBlendEquationiOES wrap_glBlendEquationiOES
+#define glBlendFunc wrap_glBlendFunc
+#define glBlendFuncSeparate wrap_glBlendFuncSeparate
+#define glBlendFuncSeparateOES wrap_glBlendFuncSeparateOES
+#define glBlendFuncSeparatei wrap_glBlendFuncSeparatei
+#define glBlendFuncSeparateiEXT wrap_glBlendFuncSeparateiEXT
+#define glBlendFuncSeparateiOES wrap_glBlendFuncSeparateiOES
+#define glBlendFunci wrap_glBlendFunci
+#define glBlendFunciEXT wrap_glBlendFunciEXT
+#define glBlendFunciOES wrap_glBlendFunciOES
+#define glBlendParameteriNV wrap_glBlendParameteriNV
+#define glBlitFramebuffer wrap_glBlitFramebuffer
+#define glBlitFramebufferANGLE wrap_glBlitFramebufferANGLE
+#define glBlitFramebufferNV wrap_glBlitFramebufferNV
+#define glBufferData wrap_glBufferData
+#define glBufferStorageEXT wrap_glBufferStorageEXT
+#define glBufferSubData wrap_glBufferSubData
+#define glCheckFramebufferStatus wrap_glCheckFramebufferStatus
+#define glCheckFramebufferStatusOES wrap_glCheckFramebufferStatusOES
+#define glClear wrap_glClear
+#define glClearBufferfi wrap_glClearBufferfi
+#define glClearBufferfv wrap_glClearBufferfv
+#define glClearBufferiv wrap_glClearBufferiv
+#define glClearBufferuiv wrap_glClearBufferuiv
+#define glClearColor wrap_glClearColor
+#define glClearColorx wrap_glClearColorx
+#define glClearColorxOES wrap_glClearColorxOES
+#define glClearDepthf wrap_glClearDepthf
+#define glClearDepthfOES wrap_glClearDepthfOES
+#define glClearDepthx wrap_glClearDepthx
+#define glClearDepthxOES wrap_glClearDepthxOES
+#define glClearStencil wrap_glClearStencil
+#define glClientActiveTexture wrap_glClientActiveTexture
+#define glClientWaitSync wrap_glClientWaitSync
+#define glClientWaitSyncAPPLE wrap_glClientWaitSyncAPPLE
+#define glClipPlanef wrap_glClipPlanef
+#define glClipPlanefIMG wrap_glClipPlanefIMG
+#define glClipPlanefOES wrap_glClipPlanefOES
+#define glClipPlanex wrap_glClipPlanex
+#define glClipPlanexIMG wrap_glClipPlanexIMG
+#define glClipPlanexOES wrap_glClipPlanexOES
+#define glColor4f wrap_glColor4f
+#define glColor4ub wrap_glColor4ub
+#define glColor4x wrap_glColor4x
+#define glColor4xOES wrap_glColor4xOES
+#define glColorMask wrap_glColorMask
+#define glColorMaski wrap_glColorMaski
+#define glColorMaskiEXT wrap_glColorMaskiEXT
+#define glColorMaskiOES wrap_glColorMaskiOES
+#define glColorPointer wrap_glColorPointer
+#define glCompileShader wrap_glCompileShader
+#define glCompressedTexImage2D wrap_glCompressedTexImage2D
+#define glCompressedTexImage3D wrap_glCompressedTexImage3D
+#define glCompressedTexImage3DOES wrap_glCompressedTexImage3DOES
+#define glCompressedTexSubImage2D wrap_glCompressedTexSubImage2D
+#define glCompressedTexSubImage3D wrap_glCompressedTexSubImage3D
+#define glCompressedTexSubImage3DOES wrap_glCompressedTexSubImage3DOES
+#define glCopyBufferSubData wrap_glCopyBufferSubData
+#define glCopyBufferSubDataNV wrap_glCopyBufferSubDataNV
+#define glCopyImageSubData wrap_glCopyImageSubData
+#define glCopyImageSubDataEXT wrap_glCopyImageSubDataEXT
+#define glCopyImageSubDataOES wrap_glCopyImageSubDataOES
+#define glCopyPathNV wrap_glCopyPathNV
+#define glCopyTexImage2D wrap_glCopyTexImage2D
+#define glCopyTexSubImage2D wrap_glCopyTexSubImage2D
+#define glCopyTexSubImage3D wrap_glCopyTexSubImage3D
+#define glCopyTexSubImage3DOES wrap_glCopyTexSubImage3DOES
+#define glCopyTextureLevelsAPPLE wrap_glCopyTextureLevelsAPPLE
+#define glCoverFillPathInstancedNV wrap_glCoverFillPathInstancedNV
+#define glCoverFillPathNV wrap_glCoverFillPathNV
+#define glCoverStrokePathInstancedNV wrap_glCoverStrokePathInstancedNV
+#define glCoverStrokePathNV wrap_glCoverStrokePathNV
+#define glCoverageMaskNV wrap_glCoverageMaskNV
+#define glCoverageModulationNV wrap_glCoverageModulationNV
+#define glCoverageModulationTableNV wrap_glCoverageModulationTableNV
+#define glCoverageOperationNV wrap_glCoverageOperationNV
+#define glCreatePerfQueryINTEL wrap_glCreatePerfQueryINTEL
+#define glCreateProgram wrap_glCreateProgram
+#define glCreateShader wrap_glCreateShader
+#define glCreateShaderProgramv wrap_glCreateShaderProgramv
+#define glCreateShaderProgramvEXT wrap_glCreateShaderProgramvEXT
+#define glCullFace wrap_glCullFace
+#define glCurrentPaletteMatrixOES wrap_glCurrentPaletteMatrixOES
+#define glDebugMessageCallback wrap_glDebugMessageCallback
+#define glDebugMessageCallbackKHR wrap_glDebugMessageCallbackKHR
+#define glDebugMessageControl wrap_glDebugMessageControl
+#define glDebugMessageControlKHR wrap_glDebugMessageControlKHR
+#define glDebugMessageInsert wrap_glDebugMessageInsert
+#define glDebugMessageInsertKHR wrap_glDebugMessageInsertKHR
+#define glDeleteBuffers wrap_glDeleteBuffers
+#define glDeleteFencesNV wrap_glDeleteFencesNV
+#define glDeleteFramebuffers wrap_glDeleteFramebuffers
+#define glDeleteFramebuffersOES wrap_glDeleteFramebuffersOES
+#define glDeletePathsNV wrap_glDeletePathsNV
+#define glDeletePerfMonitorsAMD wrap_glDeletePerfMonitorsAMD
+#define glDeletePerfQueryINTEL wrap_glDeletePerfQueryINTEL
+#define glDeleteProgram wrap_glDeleteProgram
+#define glDeleteProgramPipelines wrap_glDeleteProgramPipelines
+#define glDeleteProgramPipelinesEXT wrap_glDeleteProgramPipelinesEXT
+#define glDeleteQueries wrap_glDeleteQueries
+#define glDeleteQueriesEXT wrap_glDeleteQueriesEXT
+#define glDeleteRenderbuffers wrap_glDeleteRenderbuffers
+#define glDeleteRenderbuffersOES wrap_glDeleteRenderbuffersOES
+#define glDeleteSamplers wrap_glDeleteSamplers
+#define glDeleteShader wrap_glDeleteShader
+#define glDeleteSync wrap_glDeleteSync
+#define glDeleteSyncAPPLE wrap_glDeleteSyncAPPLE
+#define glDeleteTextures wrap_glDeleteTextures
+#define glDeleteTransformFeedbacks wrap_glDeleteTransformFeedbacks
+#define glDeleteVertexArrays wrap_glDeleteVertexArrays
+#define glDeleteVertexArraysOES wrap_glDeleteVertexArraysOES
+#define glDepthFunc wrap_glDepthFunc
+#define glDepthMask wrap_glDepthMask
+#define glDepthRangeArrayfvNV wrap_glDepthRangeArrayfvNV
+#define glDepthRangeIndexedfNV wrap_glDepthRangeIndexedfNV
+#define glDepthRangef wrap_glDepthRangef
+#define glDepthRangefOES wrap_glDepthRangefOES
+#define glDepthRangex wrap_glDepthRangex
+#define glDepthRangexOES wrap_glDepthRangexOES
+#define glDetachShader wrap_glDetachShader
+#define glDisable wrap_glDisable
+#define glDisableClientState wrap_glDisableClientState
+#define glDisableDriverControlQCOM wrap_glDisableDriverControlQCOM
+#define glDisableVertexAttribArray wrap_glDisableVertexAttribArray
+#define glDisablei wrap_glDisablei
+#define glDisableiEXT wrap_glDisableiEXT
+#define glDisableiNV wrap_glDisableiNV
+#define glDisableiOES wrap_glDisableiOES
+#define glDiscardFramebufferEXT wrap_glDiscardFramebufferEXT
+#define glDispatchCompute wrap_glDispatchCompute
+#define glDispatchComputeIndirect wrap_glDispatchComputeIndirect
+#define glDrawArrays wrap_glDrawArrays
+#define glDrawArraysIndirect wrap_glDrawArraysIndirect
+#define glDrawArraysInstanced wrap_glDrawArraysInstanced
+#define glDrawArraysInstancedANGLE wrap_glDrawArraysInstancedANGLE
+#define glDrawArraysInstancedBaseInstanceEXT wrap_glDrawArraysInstancedBaseInstanceEXT
+#define glDrawArraysInstancedEXT wrap_glDrawArraysInstancedEXT
+#define glDrawArraysInstancedNV wrap_glDrawArraysInstancedNV
+#define glDrawBuffers wrap_glDrawBuffers
+#define glDrawBuffersEXT wrap_glDrawBuffersEXT
+#define glDrawBuffersIndexedEXT wrap_glDrawBuffersIndexedEXT
+#define glDrawBuffersNV wrap_glDrawBuffersNV
+#define glDrawElements wrap_glDrawElements
+#define glDrawElementsBaseVertex wrap_glDrawElementsBaseVertex
+#define glDrawElementsBaseVertexEXT wrap_glDrawElementsBaseVertexEXT
+#define glDrawElementsBaseVertexOES wrap_glDrawElementsBaseVertexOES
+#define glDrawElementsIndirect wrap_glDrawElementsIndirect
+#define glDrawElementsInstanced wrap_glDrawElementsInstanced
+#define glDrawElementsInstancedANGLE wrap_glDrawElementsInstancedANGLE
+#define glDrawElementsInstancedBaseInstanceEXT wrap_glDrawElementsInstancedBaseInstanceEXT
+#define glDrawElementsInstancedBaseVertex wrap_glDrawElementsInstancedBaseVertex
+#define glDrawElementsInstancedBaseVertexBaseInstanceEXT wrap_glDrawElementsInstancedBaseVertexBaseInstanceEXT
+#define glDrawElementsInstancedBaseVertexEXT wrap_glDrawElementsInstancedBaseVertexEXT
+#define glDrawElementsInstancedBaseVertexOES wrap_glDrawElementsInstancedBaseVertexOES
+#define glDrawElementsInstancedEXT wrap_glDrawElementsInstancedEXT
+#define glDrawElementsInstancedNV wrap_glDrawElementsInstancedNV
+#define glDrawRangeElements wrap_glDrawRangeElements
+#define glDrawRangeElementsBaseVertex wrap_glDrawRangeElementsBaseVertex
+#define glDrawRangeElementsBaseVertexEXT wrap_glDrawRangeElementsBaseVertexEXT
+#define glDrawRangeElementsBaseVertexOES wrap_glDrawRangeElementsBaseVertexOES
+#define glDrawTexfOES wrap_glDrawTexfOES
+#define glDrawTexfvOES wrap_glDrawTexfvOES
+#define glDrawTexiOES wrap_glDrawTexiOES
+#define glDrawTexivOES wrap_glDrawTexivOES
+#define glDrawTexsOES wrap_glDrawTexsOES
+#define glDrawTexsvOES wrap_glDrawTexsvOES
+#define glDrawTexxOES wrap_glDrawTexxOES
+#define glDrawTexxvOES wrap_glDrawTexxvOES
+#define glEGLImageTargetRenderbufferStorageOES wrap_glEGLImageTargetRenderbufferStorageOES
+#define glEGLImageTargetTexture2DOES wrap_glEGLImageTargetTexture2DOES
+#define glEnable wrap_glEnable
+#define glEnableClientState wrap_glEnableClientState
+#define glEnableDriverControlQCOM wrap_glEnableDriverControlQCOM
+#define glEnableVertexAttribArray wrap_glEnableVertexAttribArray
+#define glEnablei wrap_glEnablei
+#define glEnableiEXT wrap_glEnableiEXT
+#define glEnableiNV wrap_glEnableiNV
+#define glEnableiOES wrap_glEnableiOES
+#define glEndConditionalRenderNV wrap_glEndConditionalRenderNV
+#define glEndPerfMonitorAMD wrap_glEndPerfMonitorAMD
+#define glEndPerfQueryINTEL wrap_glEndPerfQueryINTEL
+#define glEndQuery wrap_glEndQuery
+#define glEndQueryEXT wrap_glEndQueryEXT
+#define glEndTilingQCOM wrap_glEndTilingQCOM
+#define glEndTransformFeedback wrap_glEndTransformFeedback
+#define glExtGetBufferPointervQCOM wrap_glExtGetBufferPointervQCOM
+#define glExtGetBuffersQCOM wrap_glExtGetBuffersQCOM
+#define glExtGetFramebuffersQCOM wrap_glExtGetFramebuffersQCOM
+#define glExtGetProgramBinarySourceQCOM wrap_glExtGetProgramBinarySourceQCOM
+#define glExtGetProgramsQCOM wrap_glExtGetProgramsQCOM
+#define glExtGetRenderbuffersQCOM wrap_glExtGetRenderbuffersQCOM
+#define glExtGetShadersQCOM wrap_glExtGetShadersQCOM
+#define glExtGetTexLevelParameterivQCOM wrap_glExtGetTexLevelParameterivQCOM
+#define glExtGetTexSubImageQCOM wrap_glExtGetTexSubImageQCOM
+#define glExtGetTexturesQCOM wrap_glExtGetTexturesQCOM
+#define glExtIsProgramBinaryQCOM wrap_glExtIsProgramBinaryQCOM
+#define glExtTexObjectStateOverrideiQCOM wrap_glExtTexObjectStateOverrideiQCOM
+#define glFenceSync wrap_glFenceSync
+#define glFenceSyncAPPLE wrap_glFenceSyncAPPLE
+#define glFinish wrap_glFinish
+#define glFinishFenceNV wrap_glFinishFenceNV
+#define glFlush wrap_glFlush
+#define glFlushMappedBufferRange wrap_glFlushMappedBufferRange
+#define glFlushMappedBufferRangeEXT wrap_glFlushMappedBufferRangeEXT
+#define glFogf wrap_glFogf
+#define glFogfv wrap_glFogfv
+#define glFogx wrap_glFogx
+#define glFogxOES wrap_glFogxOES
+#define glFogxv wrap_glFogxv
+#define glFogxvOES wrap_glFogxvOES
+#define glFragmentCoverageColorNV wrap_glFragmentCoverageColorNV
+#define glFramebufferParameteri wrap_glFramebufferParameteri
+#define glFramebufferRenderbuffer wrap_glFramebufferRenderbuffer
+#define glFramebufferRenderbufferOES wrap_glFramebufferRenderbufferOES
+#define glFramebufferSampleLocationsfvNV wrap_glFramebufferSampleLocationsfvNV
+#define glFramebufferTexture wrap_glFramebufferTexture
+#define glFramebufferTexture2D wrap_glFramebufferTexture2D
+#define glFramebufferTexture2DMultisampleEXT wrap_glFramebufferTexture2DMultisampleEXT
+#define glFramebufferTexture2DMultisampleIMG wrap_glFramebufferTexture2DMultisampleIMG
+#define glFramebufferTexture2DOES wrap_glFramebufferTexture2DOES
+#define glFramebufferTexture3DOES wrap_glFramebufferTexture3DOES
+#define glFramebufferTextureEXT wrap_glFramebufferTextureEXT
+#define glFramebufferTextureLayer wrap_glFramebufferTextureLayer
+#define glFramebufferTextureMultisampleMultiviewOVR wrap_glFramebufferTextureMultisampleMultiviewOVR
+#define glFramebufferTextureMultiviewOVR wrap_glFramebufferTextureMultiviewOVR
+#define glFramebufferTextureOES wrap_glFramebufferTextureOES
+#define glFrontFace wrap_glFrontFace
+#define glFrustumf wrap_glFrustumf
+#define glFrustumfOES wrap_glFrustumfOES
+#define glFrustumx wrap_glFrustumx
+#define glFrustumxOES wrap_glFrustumxOES
+#define glGenBuffers wrap_glGenBuffers
+#define glGenFencesNV wrap_glGenFencesNV
+#define glGenFramebuffers wrap_glGenFramebuffers
+#define glGenFramebuffersOES wrap_glGenFramebuffersOES
+#define glGenPathsNV wrap_glGenPathsNV
+#define glGenPerfMonitorsAMD wrap_glGenPerfMonitorsAMD
+#define glGenProgramPipelines wrap_glGenProgramPipelines
+#define glGenProgramPipelinesEXT wrap_glGenProgramPipelinesEXT
+#define glGenQueries wrap_glGenQueries
+#define glGenQueriesEXT wrap_glGenQueriesEXT
+#define glGenRenderbuffers wrap_glGenRenderbuffers
+#define glGenRenderbuffersOES wrap_glGenRenderbuffersOES
+#define glGenSamplers wrap_glGenSamplers
+#define glGenTextures wrap_glGenTextures
+#define glGenTransformFeedbacks wrap_glGenTransformFeedbacks
+#define glGenVertexArrays wrap_glGenVertexArrays
+#define glGenVertexArraysOES wrap_glGenVertexArraysOES
+#define glGenerateMipmap wrap_glGenerateMipmap
+#define glGenerateMipmapOES wrap_glGenerateMipmapOES
+#define glGetActiveAttrib wrap_glGetActiveAttrib
+#define glGetActiveUniform wrap_glGetActiveUniform
+#define glGetActiveUniformBlockName wrap_glGetActiveUniformBlockName
+#define glGetActiveUniformBlockiv wrap_glGetActiveUniformBlockiv
+#define glGetActiveUniformsiv wrap_glGetActiveUniformsiv
+#define glGetAttachedShaders wrap_glGetAttachedShaders
+#define glGetAttribLocation wrap_glGetAttribLocation
+#define glGetBooleani_v wrap_glGetBooleani_v
+#define glGetBooleanv wrap_glGetBooleanv
+#define glGetBufferParameteri64v wrap_glGetBufferParameteri64v
+#define glGetBufferParameteriv wrap_glGetBufferParameteriv
+#define glGetBufferPointerv wrap_glGetBufferPointerv
+#define glGetBufferPointervOES wrap_glGetBufferPointervOES
+#define glGetClipPlanef wrap_glGetClipPlanef
+#define glGetClipPlanefOES wrap_glGetClipPlanefOES
+#define glGetClipPlanex wrap_glGetClipPlanex
+#define glGetClipPlanexOES wrap_glGetClipPlanexOES
+#define glGetCoverageModulationTableNV wrap_glGetCoverageModulationTableNV
+#define glGetDebugMessageLog wrap_glGetDebugMessageLog
+#define glGetDebugMessageLogKHR wrap_glGetDebugMessageLogKHR
+#define glGetDriverControlStringQCOM wrap_glGetDriverControlStringQCOM
+#define glGetDriverControlsQCOM wrap_glGetDriverControlsQCOM
+#define glGetError wrap_glGetError
+#define glGetFenceivNV wrap_glGetFenceivNV
+#define glGetFirstPerfQueryIdINTEL wrap_glGetFirstPerfQueryIdINTEL
+#define glGetFixedv wrap_glGetFixedv
+#define glGetFixedvOES wrap_glGetFixedvOES
+#define glGetFloati_vNV wrap_glGetFloati_vNV
+#define glGetFloatv wrap_glGetFloatv
+#define glGetFragDataIndexEXT wrap_glGetFragDataIndexEXT
+#define glGetFragDataLocation wrap_glGetFragDataLocation
+#define glGetFramebufferAttachmentParameteriv wrap_glGetFramebufferAttachmentParameteriv
+#define glGetFramebufferAttachmentParameterivOES wrap_glGetFramebufferAttachmentParameterivOES
+#define glGetFramebufferParameteriv wrap_glGetFramebufferParameteriv
+#define glGetGraphicsResetStatus wrap_glGetGraphicsResetStatus
+#define glGetGraphicsResetStatusEXT wrap_glGetGraphicsResetStatusEXT
+#define glGetGraphicsResetStatusKHR wrap_glGetGraphicsResetStatusKHR
+#define glGetImageHandleNV wrap_glGetImageHandleNV
+#define glGetInteger64i_v wrap_glGetInteger64i_v
+#define glGetInteger64v wrap_glGetInteger64v
+#define glGetInteger64vAPPLE wrap_glGetInteger64vAPPLE
+#define glGetIntegeri_v wrap_glGetIntegeri_v
+#define glGetIntegeri_vEXT wrap_glGetIntegeri_vEXT
+#define glGetIntegerv wrap_glGetIntegerv
+#define glGetInternalformatSampleivNV wrap_glGetInternalformatSampleivNV
+#define glGetInternalformativ wrap_glGetInternalformativ
+#define glGetLightfv wrap_glGetLightfv
+#define glGetLightxv wrap_glGetLightxv
+#define glGetLightxvOES wrap_glGetLightxvOES
+#define glGetMaterialfv wrap_glGetMaterialfv
+#define glGetMaterialxv wrap_glGetMaterialxv
+#define glGetMaterialxvOES wrap_glGetMaterialxvOES
+#define glGetMultisamplefv wrap_glGetMultisamplefv
+#define glGetNextPerfQueryIdINTEL wrap_glGetNextPerfQueryIdINTEL
+#define glGetObjectLabel wrap_glGetObjectLabel
+#define glGetObjectLabelEXT wrap_glGetObjectLabelEXT
+#define glGetObjectLabelKHR wrap_glGetObjectLabelKHR
+#define glGetObjectPtrLabel wrap_glGetObjectPtrLabel
+#define glGetObjectPtrLabelKHR wrap_glGetObjectPtrLabelKHR
+#define glGetPathCommandsNV wrap_glGetPathCommandsNV
+#define glGetPathCoordsNV wrap_glGetPathCoordsNV
+#define glGetPathDashArrayNV wrap_glGetPathDashArrayNV
+#define glGetPathLengthNV wrap_glGetPathLengthNV
+#define glGetPathMetricRangeNV wrap_glGetPathMetricRangeNV
+#define glGetPathMetricsNV wrap_glGetPathMetricsNV
+#define glGetPathParameterfvNV wrap_glGetPathParameterfvNV
+#define glGetPathParameterivNV wrap_glGetPathParameterivNV
+#define glGetPathSpacingNV wrap_glGetPathSpacingNV
+#define glGetPerfCounterInfoINTEL wrap_glGetPerfCounterInfoINTEL
+#define glGetPerfMonitorCounterDataAMD wrap_glGetPerfMonitorCounterDataAMD
+#define glGetPerfMonitorCounterInfoAMD wrap_glGetPerfMonitorCounterInfoAMD
+#define glGetPerfMonitorCounterStringAMD wrap_glGetPerfMonitorCounterStringAMD
+#define glGetPerfMonitorCountersAMD wrap_glGetPerfMonitorCountersAMD
+#define glGetPerfMonitorGroupStringAMD wrap_glGetPerfMonitorGroupStringAMD
+#define glGetPerfMonitorGroupsAMD wrap_glGetPerfMonitorGroupsAMD
+#define glGetPerfQueryDataINTEL wrap_glGetPerfQueryDataINTEL
+#define glGetPerfQueryIdByNameINTEL wrap_glGetPerfQueryIdByNameINTEL
+#define glGetPerfQueryInfoINTEL wrap_glGetPerfQueryInfoINTEL
+#define glGetPointerv wrap_glGetPointerv
+#define glGetPointervKHR wrap_glGetPointervKHR
+#define glGetProgramBinary wrap_glGetProgramBinary
+#define glGetProgramBinaryOES wrap_glGetProgramBinaryOES
+#define glGetProgramInfoLog wrap_glGetProgramInfoLog
+#define glGetProgramInterfaceiv wrap_glGetProgramInterfaceiv
+#define glGetProgramPipelineInfoLog wrap_glGetProgramPipelineInfoLog
+#define glGetProgramPipelineInfoLogEXT wrap_glGetProgramPipelineInfoLogEXT
+#define glGetProgramPipelineiv wrap_glGetProgramPipelineiv
+#define glGetProgramPipelineivEXT wrap_glGetProgramPipelineivEXT
+#define glGetProgramResourceIndex wrap_glGetProgramResourceIndex
+#define glGetProgramResourceLocation wrap_glGetProgramResourceLocation
+#define glGetProgramResourceLocationIndexEXT wrap_glGetProgramResourceLocationIndexEXT
+#define glGetProgramResourceName wrap_glGetProgramResourceName
+#define glGetProgramResourcefvNV wrap_glGetProgramResourcefvNV
+#define glGetProgramResourceiv wrap_glGetProgramResourceiv
+#define glGetProgramiv wrap_glGetProgramiv
+#define glGetQueryObjecti64vEXT wrap_glGetQueryObjecti64vEXT
+#define glGetQueryObjectivEXT wrap_glGetQueryObjectivEXT
+#define glGetQueryObjectui64vEXT wrap_glGetQueryObjectui64vEXT
+#define glGetQueryObjectuiv wrap_glGetQueryObjectuiv
+#define glGetQueryObjectuivEXT wrap_glGetQueryObjectuivEXT
+#define glGetQueryiv wrap_glGetQueryiv
+#define glGetQueryivEXT wrap_glGetQueryivEXT
+#define glGetRenderbufferParameteriv wrap_glGetRenderbufferParameteriv
+#define glGetRenderbufferParameterivOES wrap_glGetRenderbufferParameterivOES
+#define glGetSamplerParameterIiv wrap_glGetSamplerParameterIiv
+#define glGetSamplerParameterIivEXT wrap_glGetSamplerParameterIivEXT
+#define glGetSamplerParameterIivOES wrap_glGetSamplerParameterIivOES
+#define glGetSamplerParameterIuiv wrap_glGetSamplerParameterIuiv
+#define glGetSamplerParameterIuivEXT wrap_glGetSamplerParameterIuivEXT
+#define glGetSamplerParameterIuivOES wrap_glGetSamplerParameterIuivOES
+#define glGetSamplerParameterfv wrap_glGetSamplerParameterfv
+#define glGetSamplerParameteriv wrap_glGetSamplerParameteriv
+#define glGetShaderInfoLog wrap_glGetShaderInfoLog
+#define glGetShaderPrecisionFormat wrap_glGetShaderPrecisionFormat
+#define glGetShaderSource wrap_glGetShaderSource
+#define glGetShaderiv wrap_glGetShaderiv
+#define glGetString wrap_glGetString
+#define glGetStringi wrap_glGetStringi
+#define glGetSynciv wrap_glGetSynciv
+#define glGetSyncivAPPLE wrap_glGetSyncivAPPLE
+#define glGetTexEnvfv wrap_glGetTexEnvfv
+#define glGetTexEnviv wrap_glGetTexEnviv
+#define glGetTexEnvxv wrap_glGetTexEnvxv
+#define glGetTexEnvxvOES wrap_glGetTexEnvxvOES
+#define glGetTexGenfvOES wrap_glGetTexGenfvOES
+#define glGetTexGenivOES wrap_glGetTexGenivOES
+#define glGetTexGenxvOES wrap_glGetTexGenxvOES
+#define glGetTexLevelParameterfv wrap_glGetTexLevelParameterfv
+#define glGetTexLevelParameteriv wrap_glGetTexLevelParameteriv
+#define glGetTexParameterIiv wrap_glGetTexParameterIiv
+#define glGetTexParameterIivEXT wrap_glGetTexParameterIivEXT
+#define glGetTexParameterIivOES wrap_glGetTexParameterIivOES
+#define glGetTexParameterIuiv wrap_glGetTexParameterIuiv
+#define glGetTexParameterIuivEXT wrap_glGetTexParameterIuivEXT
+#define glGetTexParameterIuivOES wrap_glGetTexParameterIuivOES
+#define glGetTexParameterfv wrap_glGetTexParameterfv
+#define glGetTexParameteriv wrap_glGetTexParameteriv
+#define glGetTexParameterxv wrap_glGetTexParameterxv
+#define glGetTexParameterxvOES wrap_glGetTexParameterxvOES
+#define glGetTextureHandleNV wrap_glGetTextureHandleNV
+#define glGetTextureSamplerHandleNV wrap_glGetTextureSamplerHandleNV
+#define glGetTransformFeedbackVarying wrap_glGetTransformFeedbackVarying
+#define glGetTranslatedShaderSourceANGLE wrap_glGetTranslatedShaderSourceANGLE
+#define glGetUniformBlockIndex wrap_glGetUniformBlockIndex
+#define glGetUniformIndices wrap_glGetUniformIndices
+#define glGetUniformLocation wrap_glGetUniformLocation
+#define glGetUniformfv wrap_glGetUniformfv
+#define glGetUniformiv wrap_glGetUniformiv
+#define glGetUniformuiv wrap_glGetUniformuiv
+#define glGetVertexAttribIiv wrap_glGetVertexAttribIiv
+#define glGetVertexAttribIuiv wrap_glGetVertexAttribIuiv
+#define glGetVertexAttribPointerv wrap_glGetVertexAttribPointerv
+#define glGetVertexAttribfv wrap_glGetVertexAttribfv
+#define glGetVertexAttribiv wrap_glGetVertexAttribiv
+#define glGetnUniformfv wrap_glGetnUniformfv
+#define glGetnUniformfvEXT wrap_glGetnUniformfvEXT
+#define glGetnUniformfvKHR wrap_glGetnUniformfvKHR
+#define glGetnUniformiv wrap_glGetnUniformiv
+#define glGetnUniformivEXT wrap_glGetnUniformivEXT
+#define glGetnUniformivKHR wrap_glGetnUniformivKHR
+#define glGetnUniformuiv wrap_glGetnUniformuiv
+#define glGetnUniformuivKHR wrap_glGetnUniformuivKHR
+#define glHint wrap_glHint
+#define glInsertEventMarkerEXT wrap_glInsertEventMarkerEXT
+#define glInterpolatePathsNV wrap_glInterpolatePathsNV
+#define glInvalidateFramebuffer wrap_glInvalidateFramebuffer
+#define glInvalidateSubFramebuffer wrap_glInvalidateSubFramebuffer
+#define glIsBuffer wrap_glIsBuffer
+#define glIsEnabled wrap_glIsEnabled
+#define glIsEnabledi wrap_glIsEnabledi
+#define glIsEnablediEXT wrap_glIsEnablediEXT
+#define glIsEnablediNV wrap_glIsEnablediNV
+#define glIsEnablediOES wrap_glIsEnablediOES
+#define glIsFenceNV wrap_glIsFenceNV
+#define glIsFramebuffer wrap_glIsFramebuffer
+#define glIsFramebufferOES wrap_glIsFramebufferOES
+#define glIsImageHandleResidentNV wrap_glIsImageHandleResidentNV
+#define glIsPathNV wrap_glIsPathNV
+#define glIsPointInFillPathNV wrap_glIsPointInFillPathNV
+#define glIsPointInStrokePathNV wrap_glIsPointInStrokePathNV
+#define glIsProgram wrap_glIsProgram
+#define glIsProgramPipeline wrap_glIsProgramPipeline
+#define glIsProgramPipelineEXT wrap_glIsProgramPipelineEXT
+#define glIsQuery wrap_glIsQuery
+#define glIsQueryEXT wrap_glIsQueryEXT
+#define glIsRenderbuffer wrap_glIsRenderbuffer
+#define glIsRenderbufferOES wrap_glIsRenderbufferOES
+#define glIsSampler wrap_glIsSampler
+#define glIsShader wrap_glIsShader
+#define glIsSync wrap_glIsSync
+#define glIsSyncAPPLE wrap_glIsSyncAPPLE
+#define glIsTexture wrap_glIsTexture
+#define glIsTextureHandleResidentNV wrap_glIsTextureHandleResidentNV
+#define glIsTransformFeedback wrap_glIsTransformFeedback
+#define glIsVertexArray wrap_glIsVertexArray
+#define glIsVertexArrayOES wrap_glIsVertexArrayOES
+#define glLabelObjectEXT wrap_glLabelObjectEXT
+#define glLightModelf wrap_glLightModelf
+#define glLightModelfv wrap_glLightModelfv
+#define glLightModelx wrap_glLightModelx
+#define glLightModelxOES wrap_glLightModelxOES
+#define glLightModelxv wrap_glLightModelxv
+#define glLightModelxvOES wrap_glLightModelxvOES
+#define glLightf wrap_glLightf
+#define glLightfv wrap_glLightfv
+#define glLightx wrap_glLightx
+#define glLightxOES wrap_glLightxOES
+#define glLightxv wrap_glLightxv
+#define glLightxvOES wrap_glLightxvOES
+#define glLineWidth wrap_glLineWidth
+#define glLineWidthx wrap_glLineWidthx
+#define glLineWidthxOES wrap_glLineWidthxOES
+#define glLinkProgram wrap_glLinkProgram
+#define glLoadIdentity wrap_glLoadIdentity
+#define glLoadMatrixf wrap_glLoadMatrixf
+#define glLoadMatrixx wrap_glLoadMatrixx
+#define glLoadMatrixxOES wrap_glLoadMatrixxOES
+#define glLoadPaletteFromModelViewMatrixOES wrap_glLoadPaletteFromModelViewMatrixOES
+#define glLogicOp wrap_glLogicOp
+#define glMakeImageHandleNonResidentNV wrap_glMakeImageHandleNonResidentNV
+#define glMakeImageHandleResidentNV wrap_glMakeImageHandleResidentNV
+#define glMakeTextureHandleNonResidentNV wrap_glMakeTextureHandleNonResidentNV
+#define glMakeTextureHandleResidentNV wrap_glMakeTextureHandleResidentNV
+#define glMapBufferOES wrap_glMapBufferOES
+#define glMapBufferRange wrap_glMapBufferRange
+#define glMapBufferRangeEXT wrap_glMapBufferRangeEXT
+#define glMaterialf wrap_glMaterialf
+#define glMaterialfv wrap_glMaterialfv
+#define glMaterialx wrap_glMaterialx
+#define glMaterialxOES wrap_glMaterialxOES
+#define glMaterialxv wrap_glMaterialxv
+#define glMaterialxvOES wrap_glMaterialxvOES
+#define glMatrixIndexPointerOES wrap_glMatrixIndexPointerOES
+#define glMatrixLoad3x2fNV wrap_glMatrixLoad3x2fNV
+#define glMatrixLoad3x3fNV wrap_glMatrixLoad3x3fNV
+#define glMatrixLoadTranspose3x3fNV wrap_glMatrixLoadTranspose3x3fNV
+#define glMatrixMode wrap_glMatrixMode
+#define glMatrixMult3x2fNV wrap_glMatrixMult3x2fNV
+#define glMatrixMult3x3fNV wrap_glMatrixMult3x3fNV
+#define glMatrixMultTranspose3x3fNV wrap_glMatrixMultTranspose3x3fNV
+#define glMemoryBarrier wrap_glMemoryBarrier
+#define glMemoryBarrierByRegion wrap_glMemoryBarrierByRegion
+#define glMinSampleShading wrap_glMinSampleShading
+#define glMinSampleShadingOES wrap_glMinSampleShadingOES
+#define glMultMatrixf wrap_glMultMatrixf
+#define glMultMatrixx wrap_glMultMatrixx
+#define glMultMatrixxOES wrap_glMultMatrixxOES
+#define glMultiDrawArraysEXT wrap_glMultiDrawArraysEXT
+#define glMultiDrawArraysIndirectEXT wrap_glMultiDrawArraysIndirectEXT
+#define glMultiDrawElementsBaseVertexEXT wrap_glMultiDrawElementsBaseVertexEXT
+#define glMultiDrawElementsBaseVertexOES wrap_glMultiDrawElementsBaseVertexOES
+#define glMultiDrawElementsEXT wrap_glMultiDrawElementsEXT
+#define glMultiDrawElementsIndirectEXT wrap_glMultiDrawElementsIndirectEXT
+#define glMultiTexCoord4f wrap_glMultiTexCoord4f
+#define glMultiTexCoord4x wrap_glMultiTexCoord4x
+#define glMultiTexCoord4xOES wrap_glMultiTexCoord4xOES
+#define glNamedFramebufferSampleLocationsfvNV wrap_glNamedFramebufferSampleLocationsfvNV
+#define glNormal3f wrap_glNormal3f
+#define glNormal3x wrap_glNormal3x
+#define glNormal3xOES wrap_glNormal3xOES
+#define glNormalPointer wrap_glNormalPointer
+#define glObjectLabel wrap_glObjectLabel
+#define glObjectLabelKHR wrap_glObjectLabelKHR
+#define glObjectPtrLabel wrap_glObjectPtrLabel
+#define glObjectPtrLabelKHR wrap_glObjectPtrLabelKHR
+#define glOrthof wrap_glOrthof
+#define glOrthofOES wrap_glOrthofOES
+#define glOrthox wrap_glOrthox
+#define glOrthoxOES wrap_glOrthoxOES
+#define glPatchParameteri wrap_glPatchParameteri
+#define glPatchParameteriEXT wrap_glPatchParameteriEXT
+#define glPatchParameteriOES wrap_glPatchParameteriOES
+#define glPathCommandsNV wrap_glPathCommandsNV
+#define glPathCoordsNV wrap_glPathCoordsNV
+#define glPathCoverDepthFuncNV wrap_glPathCoverDepthFuncNV
+#define glPathDashArrayNV wrap_glPathDashArrayNV
+#define glPathGlyphIndexArrayNV wrap_glPathGlyphIndexArrayNV
+#define glPathGlyphIndexRangeNV wrap_glPathGlyphIndexRangeNV
+#define glPathGlyphRangeNV wrap_glPathGlyphRangeNV
+#define glPathGlyphsNV wrap_glPathGlyphsNV
+#define glPathMemoryGlyphIndexArrayNV wrap_glPathMemoryGlyphIndexArrayNV
+#define glPathParameterfNV wrap_glPathParameterfNV
+#define glPathParameterfvNV wrap_glPathParameterfvNV
+#define glPathParameteriNV wrap_glPathParameteriNV
+#define glPathParameterivNV wrap_glPathParameterivNV
+#define glPathStencilDepthOffsetNV wrap_glPathStencilDepthOffsetNV
+#define glPathStencilFuncNV wrap_glPathStencilFuncNV
+#define glPathStringNV wrap_glPathStringNV
+#define glPathSubCommandsNV wrap_glPathSubCommandsNV
+#define glPathSubCoordsNV wrap_glPathSubCoordsNV
+#define glPauseTransformFeedback wrap_glPauseTransformFeedback
+#define glPixelStorei wrap_glPixelStorei
+#define glPointAlongPathNV wrap_glPointAlongPathNV
+#define glPointParameterf wrap_glPointParameterf
+#define glPointParameterfv wrap_glPointParameterfv
+#define glPointParameterx wrap_glPointParameterx
+#define glPointParameterxOES wrap_glPointParameterxOES
+#define glPointParameterxv wrap_glPointParameterxv
+#define glPointParameterxvOES wrap_glPointParameterxvOES
+#define glPointSize wrap_glPointSize
+#define glPointSizePointerOES wrap_glPointSizePointerOES
+#define glPointSizex wrap_glPointSizex
+#define glPointSizexOES wrap_glPointSizexOES
+#define glPolygonModeNV wrap_glPolygonModeNV
+#define glPolygonOffset wrap_glPolygonOffset
+#define glPolygonOffsetx wrap_glPolygonOffsetx
+#define glPolygonOffsetxOES wrap_glPolygonOffsetxOES
+#define glPopDebugGroup wrap_glPopDebugGroup
+#define glPopDebugGroupKHR wrap_glPopDebugGroupKHR
+#define glPopGroupMarkerEXT wrap_glPopGroupMarkerEXT
+#define glPopMatrix wrap_glPopMatrix
+#define glPrimitiveBoundingBox wrap_glPrimitiveBoundingBox
+#define glPrimitiveBoundingBoxEXT wrap_glPrimitiveBoundingBoxEXT
+#define glPrimitiveBoundingBoxOES wrap_glPrimitiveBoundingBoxOES
+#define glProgramBinary wrap_glProgramBinary
+#define glProgramBinaryOES wrap_glProgramBinaryOES
+#define glProgramParameteri wrap_glProgramParameteri
+#define glProgramParameteriEXT wrap_glProgramParameteriEXT
+#define glProgramPathFragmentInputGenNV wrap_glProgramPathFragmentInputGenNV
+#define glProgramUniform1f wrap_glProgramUniform1f
+#define glProgramUniform1fEXT wrap_glProgramUniform1fEXT
+#define glProgramUniform1fv wrap_glProgramUniform1fv
+#define glProgramUniform1fvEXT wrap_glProgramUniform1fvEXT
+#define glProgramUniform1i wrap_glProgramUniform1i
+#define glProgramUniform1iEXT wrap_glProgramUniform1iEXT
+#define glProgramUniform1iv wrap_glProgramUniform1iv
+#define glProgramUniform1ivEXT wrap_glProgramUniform1ivEXT
+#define glProgramUniform1ui wrap_glProgramUniform1ui
+#define glProgramUniform1uiEXT wrap_glProgramUniform1uiEXT
+#define glProgramUniform1uiv wrap_glProgramUniform1uiv
+#define glProgramUniform1uivEXT wrap_glProgramUniform1uivEXT
+#define glProgramUniform2f wrap_glProgramUniform2f
+#define glProgramUniform2fEXT wrap_glProgramUniform2fEXT
+#define glProgramUniform2fv wrap_glProgramUniform2fv
+#define glProgramUniform2fvEXT wrap_glProgramUniform2fvEXT
+#define glProgramUniform2i wrap_glProgramUniform2i
+#define glProgramUniform2iEXT wrap_glProgramUniform2iEXT
+#define glProgramUniform2iv wrap_glProgramUniform2iv
+#define glProgramUniform2ivEXT wrap_glProgramUniform2ivEXT
+#define glProgramUniform2ui wrap_glProgramUniform2ui
+#define glProgramUniform2uiEXT wrap_glProgramUniform2uiEXT
+#define glProgramUniform2uiv wrap_glProgramUniform2uiv
+#define glProgramUniform2uivEXT wrap_glProgramUniform2uivEXT
+#define glProgramUniform3f wrap_glProgramUniform3f
+#define glProgramUniform3fEXT wrap_glProgramUniform3fEXT
+#define glProgramUniform3fv wrap_glProgramUniform3fv
+#define glProgramUniform3fvEXT wrap_glProgramUniform3fvEXT
+#define glProgramUniform3i wrap_glProgramUniform3i
+#define glProgramUniform3iEXT wrap_glProgramUniform3iEXT
+#define glProgramUniform3iv wrap_glProgramUniform3iv
+#define glProgramUniform3ivEXT wrap_glProgramUniform3ivEXT
+#define glProgramUniform3ui wrap_glProgramUniform3ui
+#define glProgramUniform3uiEXT wrap_glProgramUniform3uiEXT
+#define glProgramUniform3uiv wrap_glProgramUniform3uiv
+#define glProgramUniform3uivEXT wrap_glProgramUniform3uivEXT
+#define glProgramUniform4f wrap_glProgramUniform4f
+#define glProgramUniform4fEXT wrap_glProgramUniform4fEXT
+#define glProgramUniform4fv wrap_glProgramUniform4fv
+#define glProgramUniform4fvEXT wrap_glProgramUniform4fvEXT
+#define glProgramUniform4i wrap_glProgramUniform4i
+#define glProgramUniform4iEXT wrap_glProgramUniform4iEXT
+#define glProgramUniform4iv wrap_glProgramUniform4iv
+#define glProgramUniform4ivEXT wrap_glProgramUniform4ivEXT
+#define glProgramUniform4ui wrap_glProgramUniform4ui
+#define glProgramUniform4uiEXT wrap_glProgramUniform4uiEXT
+#define glProgramUniform4uiv wrap_glProgramUniform4uiv
+#define glProgramUniform4uivEXT wrap_glProgramUniform4uivEXT
+#define glProgramUniformHandleui64NV wrap_glProgramUniformHandleui64NV
+#define glProgramUniformHandleui64vNV wrap_glProgramUniformHandleui64vNV
+#define glProgramUniformMatrix2fv wrap_glProgramUniformMatrix2fv
+#define glProgramUniformMatrix2fvEXT wrap_glProgramUniformMatrix2fvEXT
+#define glProgramUniformMatrix2x3fv wrap_glProgramUniformMatrix2x3fv
+#define glProgramUniformMatrix2x3fvEXT wrap_glProgramUniformMatrix2x3fvEXT
+#define glProgramUniformMatrix2x4fv wrap_glProgramUniformMatrix2x4fv
+#define glProgramUniformMatrix2x4fvEXT wrap_glProgramUniformMatrix2x4fvEXT
+#define glProgramUniformMatrix3fv wrap_glProgramUniformMatrix3fv
+#define glProgramUniformMatrix3fvEXT wrap_glProgramUniformMatrix3fvEXT
+#define glProgramUniformMatrix3x2fv wrap_glProgramUniformMatrix3x2fv
+#define glProgramUniformMatrix3x2fvEXT wrap_glProgramUniformMatrix3x2fvEXT
+#define glProgramUniformMatrix3x4fv wrap_glProgramUniformMatrix3x4fv
+#define glProgramUniformMatrix3x4fvEXT wrap_glProgramUniformMatrix3x4fvEXT
+#define glProgramUniformMatrix4fv wrap_glProgramUniformMatrix4fv
+#define glProgramUniformMatrix4fvEXT wrap_glProgramUniformMatrix4fvEXT
+#define glProgramUniformMatrix4x2fv wrap_glProgramUniformMatrix4x2fv
+#define glProgramUniformMatrix4x2fvEXT wrap_glProgramUniformMatrix4x2fvEXT
+#define glProgramUniformMatrix4x3fv wrap_glProgramUniformMatrix4x3fv
+#define glProgramUniformMatrix4x3fvEXT wrap_glProgramUniformMatrix4x3fvEXT
+#define glPushDebugGroup wrap_glPushDebugGroup
+#define glPushDebugGroupKHR wrap_glPushDebugGroupKHR
+#define glPushGroupMarkerEXT wrap_glPushGroupMarkerEXT
+#define glPushMatrix wrap_glPushMatrix
+#define glQueryCounterEXT wrap_glQueryCounterEXT
+#define glQueryMatrixxOES wrap_glQueryMatrixxOES
+#define glRasterSamplesEXT wrap_glRasterSamplesEXT
+#define glReadBuffer wrap_glReadBuffer
+#define glReadBufferIndexedEXT wrap_glReadBufferIndexedEXT
+#define glReadBufferNV wrap_glReadBufferNV
+#define glReadPixels wrap_glReadPixels
+#define glReadnPixels wrap_glReadnPixels
+#define glReadnPixelsEXT wrap_glReadnPixelsEXT
+#define glReadnPixelsKHR wrap_glReadnPixelsKHR
+#define glReleaseShaderCompiler wrap_glReleaseShaderCompiler
+#define glRenderbufferStorage wrap_glRenderbufferStorage
+#define glRenderbufferStorageMultisample wrap_glRenderbufferStorageMultisample
+#define glRenderbufferStorageMultisampleANGLE wrap_glRenderbufferStorageMultisampleANGLE
+#define glRenderbufferStorageMultisampleAPPLE wrap_glRenderbufferStorageMultisampleAPPLE
+#define glRenderbufferStorageMultisampleEXT wrap_glRenderbufferStorageMultisampleEXT
+#define glRenderbufferStorageMultisampleIMG wrap_glRenderbufferStorageMultisampleIMG
+#define glRenderbufferStorageMultisampleNV wrap_glRenderbufferStorageMultisampleNV
+#define glRenderbufferStorageOES wrap_glRenderbufferStorageOES
+#define glResolveDepthValuesNV wrap_glResolveDepthValuesNV
+#define glResolveMultisampleFramebufferAPPLE wrap_glResolveMultisampleFramebufferAPPLE
+#define glResumeTransformFeedback wrap_glResumeTransformFeedback
+#define glRotatef wrap_glRotatef
+#define glRotatex wrap_glRotatex
+#define glRotatexOES wrap_glRotatexOES
+#define glSampleCoverage wrap_glSampleCoverage
+#define glSampleCoveragex wrap_glSampleCoveragex
+#define glSampleCoveragexOES wrap_glSampleCoveragexOES
+#define glSampleMaski wrap_glSampleMaski
+#define glSamplerParameterIiv wrap_glSamplerParameterIiv
+#define glSamplerParameterIivEXT wrap_glSamplerParameterIivEXT
+#define glSamplerParameterIivOES wrap_glSamplerParameterIivOES
+#define glSamplerParameterIuiv wrap_glSamplerParameterIuiv
+#define glSamplerParameterIuivEXT wrap_glSamplerParameterIuivEXT
+#define glSamplerParameterIuivOES wrap_glSamplerParameterIuivOES
+#define glSamplerParameterf wrap_glSamplerParameterf
+#define glSamplerParameterfv wrap_glSamplerParameterfv
+#define glSamplerParameteri wrap_glSamplerParameteri
+#define glSamplerParameteriv wrap_glSamplerParameteriv
+#define glScalef wrap_glScalef
+#define glScalex wrap_glScalex
+#define glScalexOES wrap_glScalexOES
+#define glScissor wrap_glScissor
+#define glScissorArrayvNV wrap_glScissorArrayvNV
+#define glScissorIndexedNV wrap_glScissorIndexedNV
+#define glScissorIndexedvNV wrap_glScissorIndexedvNV
+#define glSelectPerfMonitorCountersAMD wrap_glSelectPerfMonitorCountersAMD
+#define glSetFenceNV wrap_glSetFenceNV
+#define glShadeModel wrap_glShadeModel
+#define glShaderBinary wrap_glShaderBinary
+#define glShaderSource wrap_glShaderSource
+#define glStartTilingQCOM wrap_glStartTilingQCOM
+#define glStencilFillPathInstancedNV wrap_glStencilFillPathInstancedNV
+#define glStencilFillPathNV wrap_glStencilFillPathNV
+#define glStencilFunc wrap_glStencilFunc
+#define glStencilFuncSeparate wrap_glStencilFuncSeparate
+#define glStencilMask wrap_glStencilMask
+#define glStencilMaskSeparate wrap_glStencilMaskSeparate
+#define glStencilOp wrap_glStencilOp
+#define glStencilOpSeparate wrap_glStencilOpSeparate
+#define glStencilStrokePathInstancedNV wrap_glStencilStrokePathInstancedNV
+#define glStencilStrokePathNV wrap_glStencilStrokePathNV
+#define glStencilThenCoverFillPathInstancedNV wrap_glStencilThenCoverFillPathInstancedNV
+#define glStencilThenCoverFillPathNV wrap_glStencilThenCoverFillPathNV
+#define glStencilThenCoverStrokePathInstancedNV wrap_glStencilThenCoverStrokePathInstancedNV
+#define glStencilThenCoverStrokePathNV wrap_glStencilThenCoverStrokePathNV
+#define glSubpixelPrecisionBiasNV wrap_glSubpixelPrecisionBiasNV
+#define glTestFenceNV wrap_glTestFenceNV
+#define glTexBuffer wrap_glTexBuffer
+#define glTexBufferEXT wrap_glTexBufferEXT
+#define glTexBufferOES wrap_glTexBufferOES
+#define glTexBufferRange wrap_glTexBufferRange
+#define glTexBufferRangeEXT wrap_glTexBufferRangeEXT
+#define glTexBufferRangeOES wrap_glTexBufferRangeOES
+#define glTexCoordPointer wrap_glTexCoordPointer
+#define glTexEnvf wrap_glTexEnvf
+#define glTexEnvfv wrap_glTexEnvfv
+#define glTexEnvi wrap_glTexEnvi
+#define glTexEnviv wrap_glTexEnviv
+#define glTexEnvx wrap_glTexEnvx
+#define glTexEnvxOES wrap_glTexEnvxOES
+#define glTexEnvxv wrap_glTexEnvxv
+#define glTexEnvxvOES wrap_glTexEnvxvOES
+#define glTexGenfOES wrap_glTexGenfOES
+#define glTexGenfvOES wrap_glTexGenfvOES
+#define glTexGeniOES wrap_glTexGeniOES
+#define glTexGenivOES wrap_glTexGenivOES
+#define glTexGenxOES wrap_glTexGenxOES
+#define glTexGenxvOES wrap_glTexGenxvOES
+#define glTexImage2D wrap_glTexImage2D
+#define glTexImage3D wrap_glTexImage3D
+#define glTexImage3DOES wrap_glTexImage3DOES
+#define glTexPageCommitmentEXT wrap_glTexPageCommitmentEXT
+#define glTexParameterIiv wrap_glTexParameterIiv
+#define glTexParameterIivEXT wrap_glTexParameterIivEXT
+#define glTexParameterIivOES wrap_glTexParameterIivOES
+#define glTexParameterIuiv wrap_glTexParameterIuiv
+#define glTexParameterIuivEXT wrap_glTexParameterIuivEXT
+#define glTexParameterIuivOES wrap_glTexParameterIuivOES
+#define glTexParameterf wrap_glTexParameterf
+#define glTexParameterfv wrap_glTexParameterfv
+#define glTexParameteri wrap_glTexParameteri
+#define glTexParameteriv wrap_glTexParameteriv
+#define glTexParameterx wrap_glTexParameterx
+#define glTexParameterxOES wrap_glTexParameterxOES
+#define glTexParameterxv wrap_glTexParameterxv
+#define glTexParameterxvOES wrap_glTexParameterxvOES
+#define glTexStorage1DEXT wrap_glTexStorage1DEXT
+#define glTexStorage2D wrap_glTexStorage2D
+#define glTexStorage2DEXT wrap_glTexStorage2DEXT
+#define glTexStorage2DMultisample wrap_glTexStorage2DMultisample
+#define glTexStorage3D wrap_glTexStorage3D
+#define glTexStorage3DEXT wrap_glTexStorage3DEXT
+#define glTexStorage3DMultisample wrap_glTexStorage3DMultisample
+#define glTexStorage3DMultisampleOES wrap_glTexStorage3DMultisampleOES
+#define glTexSubImage2D wrap_glTexSubImage2D
+#define glTexSubImage3D wrap_glTexSubImage3D
+#define glTexSubImage3DOES wrap_glTexSubImage3DOES
+#define glTextureStorage1DEXT wrap_glTextureStorage1DEXT
+#define glTextureStorage2DEXT wrap_glTextureStorage2DEXT
+#define glTextureStorage3DEXT wrap_glTextureStorage3DEXT
+#define glTextureViewEXT wrap_glTextureViewEXT
+#define glTextureViewOES wrap_glTextureViewOES
+#define glTransformFeedbackVaryings wrap_glTransformFeedbackVaryings
+#define glTransformPathNV wrap_glTransformPathNV
+#define glTranslatef wrap_glTranslatef
+#define glTranslatex wrap_glTranslatex
+#define glTranslatexOES wrap_glTranslatexOES
+#define glUniform1f wrap_glUniform1f
+#define glUniform1fv wrap_glUniform1fv
+#define glUniform1i wrap_glUniform1i
+#define glUniform1iv wrap_glUniform1iv
+#define glUniform1ui wrap_glUniform1ui
+#define glUniform1uiv wrap_glUniform1uiv
+#define glUniform2f wrap_glUniform2f
+#define glUniform2fv wrap_glUniform2fv
+#define glUniform2i wrap_glUniform2i
+#define glUniform2iv wrap_glUniform2iv
+#define glUniform2ui wrap_glUniform2ui
+#define glUniform2uiv wrap_glUniform2uiv
+#define glUniform3f wrap_glUniform3f
+#define glUniform3fv wrap_glUniform3fv
+#define glUniform3i wrap_glUniform3i
+#define glUniform3iv wrap_glUniform3iv
+#define glUniform3ui wrap_glUniform3ui
+#define glUniform3uiv wrap_glUniform3uiv
+#define glUniform4f wrap_glUniform4f
+#define glUniform4fv wrap_glUniform4fv
+#define glUniform4i wrap_glUniform4i
+#define glUniform4iv wrap_glUniform4iv
+#define glUniform4ui wrap_glUniform4ui
+#define glUniform4uiv wrap_glUniform4uiv
+#define glUniformBlockBinding wrap_glUniformBlockBinding
+#define glUniformHandleui64NV wrap_glUniformHandleui64NV
+#define glUniformHandleui64vNV wrap_glUniformHandleui64vNV
+#define glUniformMatrix2fv wrap_glUniformMatrix2fv
+#define glUniformMatrix2x3fv wrap_glUniformMatrix2x3fv
+#define glUniformMatrix2x3fvNV wrap_glUniformMatrix2x3fvNV
+#define glUniformMatrix2x4fv wrap_glUniformMatrix2x4fv
+#define glUniformMatrix2x4fvNV wrap_glUniformMatrix2x4fvNV
+#define glUniformMatrix3fv wrap_glUniformMatrix3fv
+#define glUniformMatrix3x2fv wrap_glUniformMatrix3x2fv
+#define glUniformMatrix3x2fvNV wrap_glUniformMatrix3x2fvNV
+#define glUniformMatrix3x4fv wrap_glUniformMatrix3x4fv
+#define glUniformMatrix3x4fvNV wrap_glUniformMatrix3x4fvNV
+#define glUniformMatrix4fv wrap_glUniformMatrix4fv
+#define glUniformMatrix4x2fv wrap_glUniformMatrix4x2fv
+#define glUniformMatrix4x2fvNV wrap_glUniformMatrix4x2fvNV
+#define glUniformMatrix4x3fv wrap_glUniformMatrix4x3fv
+#define glUniformMatrix4x3fvNV wrap_glUniformMatrix4x3fvNV
+#define glUnmapBuffer wrap_glUnmapBuffer
+#define glUnmapBufferOES wrap_glUnmapBufferOES
+#define glUseProgram wrap_glUseProgram
+#define glUseProgramStages wrap_glUseProgramStages
+#define glUseProgramStagesEXT wrap_glUseProgramStagesEXT
+#define glValidateProgram wrap_glValidateProgram
+#define glValidateProgramPipeline wrap_glValidateProgramPipeline
+#define glValidateProgramPipelineEXT wrap_glValidateProgramPipelineEXT
+#define glVertexAttrib1f wrap_glVertexAttrib1f
+#define glVertexAttrib1fv wrap_glVertexAttrib1fv
+#define glVertexAttrib2f wrap_glVertexAttrib2f
+#define glVertexAttrib2fv wrap_glVertexAttrib2fv
+#define glVertexAttrib3f wrap_glVertexAttrib3f
+#define glVertexAttrib3fv wrap_glVertexAttrib3fv
+#define glVertexAttrib4f wrap_glVertexAttrib4f
+#define glVertexAttrib4fv wrap_glVertexAttrib4fv
+#define glVertexAttribBinding wrap_glVertexAttribBinding
+#define glVertexAttribDivisor wrap_glVertexAttribDivisor
+#define glVertexAttribDivisorANGLE wrap_glVertexAttribDivisorANGLE
+#define glVertexAttribDivisorEXT wrap_glVertexAttribDivisorEXT
+#define glVertexAttribDivisorNV wrap_glVertexAttribDivisorNV
+#define glVertexAttribFormat wrap_glVertexAttribFormat
+#define glVertexAttribI4i wrap_glVertexAttribI4i
+#define glVertexAttribI4iv wrap_glVertexAttribI4iv
+#define glVertexAttribI4ui wrap_glVertexAttribI4ui
+#define glVertexAttribI4uiv wrap_glVertexAttribI4uiv
+#define glVertexAttribIFormat wrap_glVertexAttribIFormat
+#define glVertexAttribIPointer wrap_glVertexAttribIPointer
+#define glVertexAttribPointer wrap_glVertexAttribPointer
+#define glVertexBindingDivisor wrap_glVertexBindingDivisor
+#define glVertexPointer wrap_glVertexPointer
+#define glViewport wrap_glViewport
+#define glViewportArrayvNV wrap_glViewportArrayvNV
+#define glViewportIndexedfNV wrap_glViewportIndexedfNV
+#define glViewportIndexedfvNV wrap_glViewportIndexedfvNV
+#define glWaitSync wrap_glWaitSync
+#define glWaitSyncAPPLE wrap_glWaitSyncAPPLE
+#define glWeightPathsNV wrap_glWeightPathsNV
+#define glWeightPointerOES wrap_glWeightPointerOES
+
+#endif // HWUI_GLES_WRAP_ENABLED
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index d2685daa1711..8ba4761c1b2e 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -111,11 +111,11 @@ CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove)
CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount)
: mTexture(Caches::getInstance())
+ , mWidth(width)
+ , mHeight(height)
, mFormat(format)
, mMaxQuadCount(maxQuadCount)
, mCaches(Caches::getInstance()) {
- mTexture.width = width;
- mTexture.height = height;
mTexture.blend = true;
mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
@@ -160,10 +160,7 @@ void CacheTexture::releasePixelBuffer() {
delete mPixelBuffer;
mPixelBuffer = nullptr;
}
- if (mTexture.id) {
- mCaches.textureState().deleteTexture(mTexture.id);
- mTexture.id = 0;
- }
+ mTexture.deleteTexture();
mDirty = false;
mCurrentQuad = 0;
}
@@ -183,22 +180,9 @@ void CacheTexture::allocatePixelBuffer() {
mPixelBuffer = PixelBuffer::create(mFormat, getWidth(), getHeight());
}
- if (!mTexture.id) {
- glGenTextures(1, &mTexture.id);
-
- mCaches.textureState().bindTexture(mTexture.id);
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
- // Initialize texture dimensions
- glTexImage2D(GL_TEXTURE_2D, 0, mFormat, getWidth(), getHeight(), 0,
- mFormat, GL_UNSIGNED_BYTE, nullptr);
-
- const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST;
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
-
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- }
+ mTexture.resize(mWidth, mHeight, mFormat);
+ mTexture.setFilter(getLinearFiltering() ? GL_LINEAR : GL_NEAREST);
+ mTexture.setWrap(GL_CLAMP_TO_EDGE);
}
bool CacheTexture::upload() {
diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h
index 6dabc768ce6b..4dfb41dafcc7 100644
--- a/libs/hwui/font/CacheTexture.h
+++ b/libs/hwui/font/CacheTexture.h
@@ -23,7 +23,7 @@
#include "Vertex.h"
#include <GLES3/gl3.h>
-#include <SkScalerContext.h>
+#include <SkGlyph.h>
#include <utils/Log.h>
@@ -92,11 +92,11 @@ public:
bool fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY);
inline uint16_t getWidth() const {
- return mTexture.width;
+ return mWidth;
}
inline uint16_t getHeight() const {
- return mTexture.height;
+ return mHeight;
}
inline GLenum getFormat() const {
@@ -122,7 +122,7 @@ public:
GLuint getTextureId() {
allocatePixelBuffer();
- return mTexture.id;
+ return mTexture.id();
}
inline bool isDirty() const {
@@ -183,6 +183,7 @@ private:
PixelBuffer* mPixelBuffer = nullptr;
Texture mTexture;
+ uint32_t mWidth, mHeight;
GLenum mFormat;
bool mLinearFiltering = false;
bool mDirty = false;
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index 5de64a4b1654..8e04c8715f62 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -14,15 +14,12 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-#define ATRACE_TAG ATRACE_TAG_VIEW
-
#include <cutils/compiler.h>
#include <utils/JenkinsHash.h>
#include <utils/Trace.h>
-#include <SkDeviceProperties.h>
+#include <SkSurfaceProps.h>
#include <SkGlyph.h>
#include <SkGlyphCache.h>
#include <SkUtils.h>
@@ -64,8 +61,6 @@ Font::FontDescription::FontDescription(const SkPaint* paint, const SkMatrix& ras
}
Font::~Font() {
- mState->removeFont(this);
-
for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
delete mCachedGlyphs.valueAt(i);
}
@@ -284,8 +279,8 @@ CachedGlyphInfo* Font::getCachedGlyph(const SkPaint* paint, glyph_t textUnit, bo
if (cachedGlyph) {
// Is the glyph still in texture cache?
if (!cachedGlyph->mIsValid) {
- SkDeviceProperties deviceProperties(kUnknown_SkPixelGeometry, 1.0f);
- SkAutoGlyphCache autoCache(*paint, &deviceProperties, &mDescription.mLookupTransform);
+ SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+ SkAutoGlyphCacheNoGamma autoCache(*paint, &surfaceProps, &mDescription.mLookupTransform);
const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), textUnit);
updateGlyphCache(paint, skiaGlyph, autoCache.getCache(), cachedGlyph, precaching);
}
@@ -296,20 +291,18 @@ CachedGlyphInfo* Font::getCachedGlyph(const SkPaint* paint, glyph_t textUnit, bo
return cachedGlyph;
}
-void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+void Font::render(const SkPaint* paint, const glyph_t* glyphs,
int numGlyphs, int x, int y, const float* positions) {
- render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, nullptr,
+ render(paint, glyphs, numGlyphs, x, y, FRAMEBUFFER, nullptr,
0, 0, nullptr, positions);
}
-void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
- int numGlyphs, const SkPath* path, float hOffset, float vOffset) {
- if (numGlyphs == 0 || text == nullptr || len == 0) {
+void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs,
+ const SkPath* path, float hOffset, float vOffset) {
+ if (numGlyphs == 0 || glyphs == nullptr) {
return;
}
- text += start;
-
int glyphsCount = 0;
SkFixed prevRsbDelta = 0;
@@ -322,7 +315,7 @@ void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32
float pathLength = SkScalarToFloat(measure.getLength());
if (paint->getTextAlign() != SkPaint::kLeft_Align) {
- float textWidth = SkScalarToFloat(paint->measureText(text, len));
+ float textWidth = SkScalarToFloat(paint->measureText(glyphs, numGlyphs * 2));
float pathOffset = pathLength;
if (paint->getTextAlign() == SkPaint::kCenter_Align) {
textWidth *= 0.5f;
@@ -332,7 +325,7 @@ void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32
}
while (glyphsCount < numGlyphs && penX < pathLength) {
- glyph_t glyph = GET_GLYPH(text);
+ glyph_t glyph = *(glyphs++);
if (IS_END_OF_STRING(glyph)) {
break;
@@ -352,26 +345,24 @@ void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32
}
}
-void Font::measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::measure(const SkPaint* paint, const glyph_t* glyphs,
int numGlyphs, Rect *bounds, const float* positions) {
if (bounds == nullptr) {
ALOGE("No return rectangle provided to measure text");
return;
}
bounds->set(1e6, -1e6, -1e6, 1e6);
- render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
+ render(paint, glyphs, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
}
-void Font::precache(const SkPaint* paint, const char* text, int numGlyphs) {
- ATRACE_NAME("Precache Glyphs");
-
- if (numGlyphs == 0 || text == nullptr) {
+void Font::precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs) {
+ if (numGlyphs == 0 || glyphs == nullptr) {
return;
}
int glyphsCount = 0;
while (glyphsCount < numGlyphs) {
- glyph_t glyph = GET_GLYPH(text);
+ glyph_t glyph = *(glyphs++);
// Reached the end of the string
if (IS_END_OF_STRING(glyph)) {
@@ -383,10 +374,10 @@ void Font::precache(const SkPaint* paint, const char* text, int numGlyphs) {
}
}
-void Font::render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::render(const SkPaint* paint, const glyph_t* glyphs,
int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) {
- if (numGlyphs == 0 || text == nullptr || len == 0) {
+ if (numGlyphs == 0 || glyphs == nullptr) {
return;
}
@@ -400,11 +391,10 @@ void Font::render(const SkPaint* paint, const char* text, uint32_t start, uint32
};
RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform];
- text += start;
int glyphsCount = 0;
while (glyphsCount < numGlyphs) {
- glyph_t glyph = GET_GLYPH(text);
+ glyph_t glyph = *(glyphs++);
// Reached the end of the string
if (IS_END_OF_STRING(glyph)) {
@@ -475,8 +465,8 @@ CachedGlyphInfo* Font::cacheGlyph(const SkPaint* paint, glyph_t glyph, bool prec
CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
mCachedGlyphs.add(glyph, newGlyph);
- SkDeviceProperties deviceProperties(kUnknown_SkPixelGeometry, 1.0f);
- SkAutoGlyphCache autoCache(*paint, &deviceProperties, &mDescription.mLookupTransform);
+ SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+ SkAutoGlyphCacheNoGamma autoCache(*paint, &surfaceProps, &mDescription.mLookupTransform);
const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), glyph);
newGlyph->mIsValid = false;
newGlyph->mGlyphIndex = skiaGlyph.fID;
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index 3119d734bc2b..288f73361bbc 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -23,7 +23,6 @@
#include <SkScalar.h>
#include <SkGlyphCache.h>
-#include <SkScalerContext.h>
#include <SkPaint.h>
#include <SkPathMeasure.h>
@@ -82,10 +81,10 @@ public:
~Font();
- void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ void render(const SkPaint* paint, const glyph_t* glyphs,
int numGlyphs, int x, int y, const float* positions);
- void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ void render(const SkPaint* paint, const glyph_t* glyphs,
int numGlyphs, const SkPath* path, float hOffset, float vOffset);
const Font::FontDescription& getDescription() const {
@@ -111,13 +110,13 @@ private:
MEASURE,
};
- void precache(const SkPaint* paint, const char* text, int numGlyphs);
+ void precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs);
- void render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+ void render(const SkPaint* paint, const glyph_t* glyphs,
int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions);
- void measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ void measure(const SkPaint* paint, const glyph_t* glyphs,
int numGlyphs, Rect *bounds, const float* positions);
void invalidateTextureCache(CacheTexture* cacheTexture = nullptr);
diff --git a/libs/hwui/font/FontUtil.h b/libs/hwui/font/FontUtil.h
index 4e5debe33c4a..aa77d98c9343 100644
--- a/libs/hwui/font/FontUtil.h
+++ b/libs/hwui/font/FontUtil.h
@@ -40,26 +40,9 @@
#define CACHE_BLOCK_ROUNDING_SIZE 4
-#if RENDER_TEXT_AS_GLYPHS
- typedef uint16_t glyph_t;
- #define TO_GLYPH(g) g
- #define GET_METRICS(cache, glyph) cache->getGlyphIDMetrics(glyph)
- #define GET_GLYPH(text) nextGlyph((const uint16_t**) &text)
- #define IS_END_OF_STRING(glyph) false
-
- static inline glyph_t nextGlyph(const uint16_t** srcPtr) {
- const uint16_t* src = *srcPtr;
- glyph_t g = *src++;
- *srcPtr = src;
- return g;
- }
-#else
- typedef SkUnichar glyph_t;
- #define TO_GLYPH(g) ((SkUnichar) g)
- #define GET_METRICS(cache, glyph) cache->getUnicharMetrics(glyph)
- #define GET_GLYPH(text) SkUTF16_NextUnichar((const uint16_t**) &text)
- #define IS_END_OF_STRING(glyph) glyph < 0
-#endif
+typedef uint16_t glyph_t;
+#define GET_METRICS(cache, glyph) cache->getGlyphIDMetrics(glyph)
+#define IS_END_OF_STRING(glyph) false
#define AUTO_KERN(prev, next) (((next) - (prev) + 32) >> 6 << 16)
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
new file mode 100644
index 000000000000..7bfa15a26d56
--- /dev/null
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2015 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 "Canvas.h"
+
+#include "DisplayListCanvas.h"
+#include "RecordingCanvas.h"
+#include "MinikinUtils.h"
+#include "Paint.h"
+#include "Typeface.h"
+
+#include <SkDrawFilter.h>
+
+namespace android {
+
+Canvas* Canvas::create_recording_canvas(int width, int height) {
+#if HWUI_NEW_OPS
+ return new uirenderer::RecordingCanvas(width, height);
+#else
+ return new uirenderer::DisplayListCanvas(width, height);
+#endif
+}
+
+void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
+ uint32_t flags;
+ SkDrawFilter* drawFilter = getDrawFilter();
+ if (drawFilter) {
+ SkPaint paintCopy(paint);
+ drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
+ flags = paintCopy.getFlags();
+ } else {
+ flags = paint.getFlags();
+ }
+ if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
+ // Same values used by Skia
+ const float kStdStrikeThru_Offset = (-6.0f / 21.0f);
+ const float kStdUnderline_Offset = (1.0f / 9.0f);
+ const float kStdUnderline_Thickness = (1.0f / 18.0f);
+
+ SkScalar left = x;
+ SkScalar right = x + length;
+ float textSize = paint.getTextSize();
+ float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
+ if (flags & SkPaint::kUnderlineText_Flag) {
+ SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
+ SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
+ drawRect(left, top, right, bottom, paint);
+ }
+ if (flags & SkPaint::kStrikeThruText_Flag) {
+ SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
+ SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
+ drawRect(left, top, right, bottom, paint);
+ }
+ }
+}
+
+static void simplifyPaint(int color, SkPaint* paint) {
+ paint->setColor(color);
+ paint->setShader(nullptr);
+ paint->setColorFilter(nullptr);
+ paint->setLooper(nullptr);
+ paint->setStrokeWidth(4 + 0.04 * paint->getTextSize());
+ paint->setStrokeJoin(SkPaint::kRound_Join);
+ paint->setLooper(nullptr);
+}
+
+class DrawTextFunctor {
+public:
+ DrawTextFunctor(const Layout& layout, Canvas* canvas, uint16_t* glyphs, float* pos,
+ const SkPaint& paint, float x, float y, MinikinRect& bounds, float totalAdvance)
+ : layout(layout)
+ , canvas(canvas)
+ , glyphs(glyphs)
+ , pos(pos)
+ , paint(paint)
+ , x(x)
+ , y(y)
+ , bounds(bounds)
+ , totalAdvance(totalAdvance) {
+ }
+
+ void operator()(size_t start, size_t end) {
+ if (canvas->drawTextAbsolutePos()) {
+ for (size_t i = start; i < end; i++) {
+ glyphs[i] = layout.getGlyphId(i);
+ pos[2 * i] = x + layout.getX(i);
+ pos[2 * i + 1] = y + layout.getY(i);
+ }
+ } else {
+ for (size_t i = start; i < end; i++) {
+ glyphs[i] = layout.getGlyphId(i);
+ pos[2 * i] = layout.getX(i);
+ pos[2 * i + 1] = 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
+ SkPaint outlinePaint(paint);
+ simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
+ outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
+ canvas->drawGlyphs(glyphs + start, pos + (2 * start), glyphCount, outlinePaint, x, y,
+ bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance);
+
+ // inner
+ SkPaint innerPaint(paint);
+ simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
+ innerPaint.setStyle(SkPaint::kFill_Style);
+ canvas->drawGlyphs(glyphs + start, pos + (2 * start), glyphCount, innerPaint, x, y,
+ bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance);
+ } else {
+ // standard draw path
+ canvas->drawGlyphs(glyphs + start, pos + (2 * start), glyphCount, paint, x, y,
+ bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance);
+ }
+ }
+private:
+ const Layout& layout;
+ Canvas* canvas;
+ uint16_t* glyphs;
+ float* pos;
+ const SkPaint& paint;
+ float x;
+ float y;
+ MinikinRect& bounds;
+ float totalAdvance;
+};
+
+void Canvas::drawText(const uint16_t* text, int start, int count, int contextCount,
+ float x, float y, int bidiFlags, const Paint& origPaint, Typeface* typeface) {
+ // minikin may modify the original paint
+ Paint paint(origPaint);
+
+ Layout layout;
+ MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, start, count, contextCount);
+
+ size_t nGlyphs = layout.nGlyphs();
+ std::unique_ptr<uint16_t[]> glyphs(new uint16_t[nGlyphs]);
+ std::unique_ptr<float[]> pos(new float[nGlyphs * 2]);
+
+ x += MinikinUtils::xOffsetForTextAlign(&paint, layout);
+
+ MinikinRect bounds;
+ layout.getBounds(&bounds);
+ if (!drawTextAbsolutePos()) {
+ bounds.offset(x, y);
+ }
+
+ // Set align to left for drawing, as we don't want individual
+ // glyphs centered or right-aligned; the offset above takes
+ // care of all alignment.
+ paint.setTextAlign(Paint::kLeft_Align);
+
+ DrawTextFunctor f(layout, this, glyphs.get(), pos.get(),
+ paint, x, y, bounds, layout.getAdvance());
+ MinikinUtils::forFontRun(layout, &paint, f);
+}
+
+class DrawTextOnPathFunctor {
+public:
+ DrawTextOnPathFunctor(const Layout& layout, Canvas* canvas, float hOffset,
+ float vOffset, const Paint& paint, const SkPath& path)
+ : layout(layout)
+ , canvas(canvas)
+ , hOffset(hOffset)
+ , vOffset(vOffset)
+ , paint(paint)
+ , path(path) {
+ }
+
+ void operator()(size_t start, size_t end) {
+ uint16_t glyphs[1];
+ for (size_t i = start; i < end; i++) {
+ glyphs[0] = layout.getGlyphId(i);
+ float x = hOffset + layout.getX(i);
+ float y = vOffset + layout.getY(i);
+ canvas->drawGlyphsOnPath(glyphs, 1, path, x, y, paint);
+ }
+ }
+private:
+ const Layout& layout;
+ Canvas* canvas;
+ float hOffset;
+ float vOffset;
+ const Paint& paint;
+ const SkPath& path;
+};
+
+void Canvas::drawTextOnPath(const uint16_t* text, int count, int bidiFlags, const SkPath& path,
+ float hOffset, float vOffset, const Paint& paint, Typeface* typeface) {
+ Paint paintCopy(paint);
+ Layout layout;
+ MinikinUtils::doLayout(&layout, &paintCopy, bidiFlags, typeface, text, 0, count, count);
+ hOffset += MinikinUtils::hOffsetForTextAlign(&paintCopy, layout, path);
+
+ // Set align to left for drawing, as we don't want individual
+ // glyphs centered or right-aligned; the offset above takes
+ // care of all alignment.
+ paintCopy.setTextAlign(Paint::kLeft_Align);
+
+ DrawTextOnPathFunctor f(layout, this, hOffset, vOffset, paintCopy, path);
+ MinikinUtils::forFontRun(layout, &paintCopy, f);
+}
+
+} // namespace android
diff --git a/libs/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 160d9a8a87dd..55af33e80256 100644
--- a/libs/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -18,6 +18,10 @@
#define ANDROID_GRAPHICS_CANVAS_H
#include <cutils/compiler.h>
+#include <utils/Functor.h>
+
+#include "GlFunctorLifecycleListener.h"
+#include "utils/NinePatch.h"
#include <SkBitmap.h>
#include <SkCanvas.h>
@@ -25,12 +29,49 @@
namespace android {
+namespace uirenderer {
+ class CanvasPropertyPaint;
+ class CanvasPropertyPrimitive;
+ class DeferredLayerUpdater;
+ class DisplayList;
+ class RenderNode;
+}
+
+namespace SaveFlags {
+
+// These must match the corresponding Canvas API constants.
+enum {
+ Matrix = 0x01,
+ Clip = 0x02,
+ HasAlphaLayer = 0x04,
+ ClipToLayer = 0x10,
+
+ // Helper constant
+ MatrixClip = Matrix | Clip,
+};
+typedef uint32_t Flags;
+
+} // namespace SaveFlags
+
+namespace uirenderer {
+class SkiaCanvasProxy;
+namespace VectorDrawable {
+class Tree;
+};
+};
+typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
+
+class Paint;
+struct Typeface;
+
class ANDROID_API Canvas {
public:
virtual ~Canvas() {};
static Canvas* create_canvas(const SkBitmap& bitmap);
+ static Canvas* create_recording_canvas(int width, int height);
+
/**
* Create a new Canvas object which delegates to an SkCanvas.
*
@@ -56,6 +97,7 @@ public:
*/
virtual SkCanvas* asSkCanvas() = 0;
+
virtual void setBitmap(const SkBitmap& bitmap) = 0;
virtual bool isOpaque() = 0;
@@ -63,27 +105,48 @@ public:
virtual int height() = 0;
// ----------------------------------------------------------------------------
+// View System operations (not exposed in public Canvas API)
+// ----------------------------------------------------------------------------
+
+ virtual void resetRecording(int width, int height) = 0;
+ virtual uirenderer::DisplayList* finishRecording() = 0;
+ virtual void insertReorderBarrier(bool enableReorder) = 0;
+
+ virtual void setHighContrastText(bool highContrastText) = 0;
+ virtual bool isHighContrastText() = 0;
+
+ virtual void drawRoundRect(uirenderer::CanvasPropertyPrimitive* left,
+ uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
+ uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
+ uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* paint) = 0;
+ virtual void drawCircle(uirenderer::CanvasPropertyPrimitive* x,
+ uirenderer::CanvasPropertyPrimitive* y, uirenderer::CanvasPropertyPrimitive* radius,
+ uirenderer::CanvasPropertyPaint* paint) = 0;
+
+ virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) = 0;
+ virtual void drawRenderNode(uirenderer::RenderNode* renderNode) = 0;
+ virtual void callDrawGLFunction(Functor* functor,
+ uirenderer::GlFunctorLifecycleListener* listener) = 0;
+
+// ----------------------------------------------------------------------------
// Canvas state operations
// ----------------------------------------------------------------------------
+
// Save (layer)
virtual int getSaveCount() const = 0;
- virtual int save(SkCanvas::SaveFlags flags) = 0;
+ virtual int save(SaveFlags::Flags flags) = 0;
virtual void restore() = 0;
virtual void restoreToCount(int saveCount) = 0;
virtual int saveLayer(float left, float top, float right, float bottom,
- const SkPaint* paint, SkCanvas::SaveFlags flags) = 0;
+ const SkPaint* paint, SaveFlags::Flags flags) = 0;
virtual int saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, SkCanvas::SaveFlags flags) = 0;
+ int alpha, SaveFlags::Flags flags) = 0;
// Matrix
virtual void getMatrix(SkMatrix* outMatrix) const = 0;
virtual void setMatrix(const SkMatrix& matrix) = 0;
- /// Like setMatrix(), but to be translated into local / view-relative coordinates
- /// rather than executed in global / device coordinates at rendering time.
- virtual void setLocalMatrix(const SkMatrix& matrix) = 0;
-
virtual void concat(const SkMatrix& matrix) = 0;
virtual void rotate(float degrees) = 0;
virtual void scale(float sx, float sy) = 0;
@@ -112,12 +175,13 @@ public:
// Geometry
virtual void drawPoint(float x, float y, const SkPaint& paint) = 0;
- virtual void drawPoints(const float* points, int count, const SkPaint& paint) = 0;
+ virtual void drawPoints(const float* points, int floatCount, const SkPaint& paint) = 0;
virtual void drawLine(float startX, float startY, float stopX, float stopY,
const SkPaint& paint) = 0;
- virtual void drawLines(const float* points, int count, const SkPaint& paint) = 0;
+ virtual void drawLines(const float* points, int floatCount, const SkPaint& paint) = 0;
virtual void drawRect(float left, float top, float right, float bottom,
const SkPaint& paint) = 0;
+ virtual void drawRegion(const SkRegion& region, const SkPaint& paint) = 0;
virtual void drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint& paint) = 0;
virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) = 0;
@@ -140,32 +204,52 @@ public:
float dstRight, float dstBottom, const SkPaint* paint) = 0;
virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
const float* vertices, const int* colors, const SkPaint* paint) = 0;
+ virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ const SkPaint* paint) = 0;
+
+ /**
+ * Specifies if the positions passed to ::drawText are absolute or relative
+ * to the (x,y) value provided.
+ *
+ * If true the (x,y) values are ignored. Otherwise, those (x,y) values need
+ * to be added to each glyph's position to get its absolute position.
+ */
+ virtual bool drawTextAbsolutePos() const = 0;
+
+ /**
+ * Draws a VectorDrawable onto the canvas.
+ */
+ virtual void drawVectorDrawable(VectorDrawableRoot* tree);
+
+ /**
+ * Converts utf16 text to glyphs, calculating position and boundary,
+ * and delegating the final draw to virtual drawGlyphs method.
+ */
+ void drawText(const uint16_t* text, int start, int count, int contextCount,
+ float x, float y, int bidiFlags, const Paint& origPaint, Typeface* typeface);
+
+ void drawTextOnPath(const uint16_t* text, int count, int bidiFlags, const SkPath& path,
+ float hOffset, float vOffset, const Paint& paint, Typeface* typeface);
+
+protected:
+ void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
- // Text
/**
* drawText: count is of glyphs
- * totalAdvance is ignored in software renderering, used by hardware renderer for
- * text decorations (underlines, strikethroughs).
+ * totalAdvance: used to define width of text decorations (underlines, strikethroughs).
*/
- virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
+ virtual void drawGlyphs(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) = 0;
- /** drawPosText: count is of UTF16 characters, posCount is floats (2 * glyphs) */
- virtual void drawPosText(const uint16_t* text, const float* positions, int count,
- int posCount, const SkPaint& paint) = 0;
/** drawTextOnPath: count is of glyphs */
- virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
+ virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path,
float hOffset, float vOffset, const SkPaint& paint) = 0;
- /**
- * Specifies if the positions passed to ::drawText are absolute or relative
- * to the (x,y) value provided.
- *
- * If true the (x,y) values are ignored. Otherwise, those (x,y) values need
- * to be added to each glyph's position to get its absolute position.
- */
- virtual bool drawTextAbsolutePos() const = 0;
+ friend class DrawTextFunctor;
+ friend class DrawTextOnPathFunctor;
+ friend class uirenderer::SkiaCanvasProxy;
};
}; // namespace android
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
new file mode 100644
index 000000000000..a455f576e38d
--- /dev/null
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2013 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 "MinikinSkia.h"
+
+#include <SkPaint.h>
+#include <SkTypeface.h>
+#include <cutils/log.h>
+
+namespace android {
+
+MinikinFontSkia::MinikinFontSkia(SkTypeface* typeface, const void* fontData, size_t fontSize,
+ int ttcIndex) :
+ MinikinFont(typeface->uniqueID()), mTypeface(typeface), mFontData(fontData),
+ mFontSize(fontSize), mTtcIndex(ttcIndex) {
+}
+
+MinikinFontSkia::~MinikinFontSkia() {
+ SkSafeUnref(mTypeface);
+}
+
+static void MinikinFontSkia_SetSkiaPaint(const MinikinFont* font, SkPaint* skPaint, const MinikinPaint& paint) {
+ skPaint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ skPaint->setTextSize(paint.size);
+ skPaint->setTextScaleX(paint.scaleX);
+ skPaint->setTextSkewX(paint.skewX);
+ MinikinFontSkia::unpackPaintFlags(skPaint, paint.paintFlags);
+ // Apply font fakery on top of user-supplied flags.
+ MinikinFontSkia::populateSkPaint(skPaint, font, paint.fakery);
+}
+
+float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id,
+ const MinikinPaint &paint) const {
+ SkPaint skPaint;
+ uint16_t glyph16 = glyph_id;
+ SkScalar skWidth;
+ MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint);
+ skPaint.getTextWidths(&glyph16, sizeof(glyph16), &skWidth, NULL);
+#ifdef VERBOSE
+ ALOGD("width for typeface %d glyph %d = %f", mTypeface->uniqueID(), glyph_id, skWidth);
+#endif
+ return skWidth;
+}
+
+void MinikinFontSkia::GetBounds(MinikinRect* bounds, uint32_t glyph_id,
+ const MinikinPaint& paint) const {
+ SkPaint skPaint;
+ uint16_t glyph16 = glyph_id;
+ SkRect skBounds;
+ MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint);
+ skPaint.getTextWidths(&glyph16, sizeof(glyph16), NULL, &skBounds);
+ bounds->mLeft = skBounds.fLeft;
+ bounds->mTop = skBounds.fTop;
+ bounds->mRight = skBounds.fRight;
+ bounds->mBottom = skBounds.fBottom;
+}
+
+const void* MinikinFontSkia::GetTable(uint32_t tag, size_t* size, MinikinDestroyFunc* destroy) {
+ // we don't have a buffer to the font data, copy to own buffer
+ const size_t tableSize = mTypeface->getTableSize(tag);
+ *size = tableSize;
+ if (tableSize == 0) {
+ return nullptr;
+ }
+ void* buf = malloc(tableSize);
+ if (buf == nullptr) {
+ return nullptr;
+ }
+ mTypeface->getTableData(tag, 0, tableSize, buf);
+ *destroy = free;
+ return buf;
+}
+
+SkTypeface *MinikinFontSkia::GetSkTypeface() const {
+ return mTypeface;
+}
+
+const void* MinikinFontSkia::GetFontData() const {
+ return mFontData;
+}
+
+size_t MinikinFontSkia::GetFontSize() const {
+ return mFontSize;
+}
+
+int MinikinFontSkia::GetFontIndex() const {
+ return mTtcIndex;
+}
+
+uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) {
+ uint32_t flags = paint->getFlags();
+ SkPaint::Hinting hinting = paint->getHinting();
+ // select only flags that might affect text layout
+ flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag |
+ SkPaint::kSubpixelText_Flag | SkPaint::kDevKernText_Flag |
+ SkPaint::kEmbeddedBitmapText_Flag | SkPaint::kAutoHinting_Flag |
+ SkPaint::kVerticalText_Flag);
+ flags |= (hinting << 16);
+ return flags;
+}
+
+void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) {
+ paint->setFlags(paintFlags & SkPaint::kAllFlags);
+ paint->setHinting(static_cast<SkPaint::Hinting>(paintFlags >> 16));
+}
+
+void MinikinFontSkia::populateSkPaint(SkPaint* paint, const MinikinFont* font, FontFakery fakery) {
+ paint->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->GetSkTypeface());
+ paint->setFakeBoldText(paint->isFakeBoldText() || fakery.isFakeBold());
+ if (fakery.isFakeItalic()) {
+ paint->setTextSkewX(paint->getTextSkewX() - 0.25f);
+ }
+}
+
+}
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
new file mode 100644
index 000000000000..a7c9fb0b09d4
--- /dev/null
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 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_GRAPHICS_MINIKIN_SKIA_H_
+#define _ANDROID_GRAPHICS_MINIKIN_SKIA_H_
+
+#include <cutils/compiler.h>
+#include <minikin/MinikinFont.h>
+
+class SkPaint;
+class SkTypeface;
+
+namespace android {
+
+class ANDROID_API MinikinFontSkia : public MinikinFont {
+public:
+ // Note: this takes ownership of the reference (will unref on dtor)
+ explicit MinikinFontSkia(SkTypeface *typeface, const void* fontData, size_t fontSize,
+ int ttcIndex);
+
+ ~MinikinFontSkia();
+
+ float GetHorizontalAdvance(uint32_t glyph_id,
+ const MinikinPaint &paint) const;
+
+ void GetBounds(MinikinRect* bounds, uint32_t glyph_id,
+ const MinikinPaint &paint) const;
+
+ const void* GetTable(uint32_t tag, size_t* size, MinikinDestroyFunc* destroy);
+
+ SkTypeface* GetSkTypeface() const;
+
+ // Access to underlying raw font bytes
+ const void* GetFontData() const;
+ size_t GetFontSize() const;
+ int GetFontIndex() const;
+
+ static uint32_t packPaintFlags(const SkPaint* paint);
+ static void unpackPaintFlags(SkPaint* paint, uint32_t paintFlags);
+
+ // set typeface and fake bold/italic parameters
+ static void populateSkPaint(SkPaint* paint, const MinikinFont* font, FontFakery fakery);
+private:
+ SkTypeface* mTypeface;
+
+ // A raw pointer to the font data - it should be owned by some other object with
+ // lifetime at least as long as this object.
+ const void* mFontData;
+ size_t mFontSize;
+ int mTtcIndex;
+};
+
+} // namespace android
+
+#endif // _ANDROID_GRAPHICS_MINIKIN_SKIA_H_
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
new file mode 100644
index 000000000000..67b775d98356
--- /dev/null
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2014 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 "MinikinUtils.h"
+
+#include "Paint.h"
+#include "SkPathMeasure.h"
+#include "Typeface.h"
+
+#include <cutils/log.h>
+#include <string>
+
+namespace android {
+
+FontStyle MinikinUtils::prepareMinikinPaint(MinikinPaint* minikinPaint, FontCollection** pFont,
+ const Paint* paint, Typeface* typeface) {
+ const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
+ *pFont = resolvedFace->fFontCollection;
+ FontStyle resolved = resolvedFace->fStyle;
+
+ /* Prepare minikin FontStyle */
+ FontVariant minikinVariant = (paint->getFontVariant() == VARIANT_ELEGANT) ? VARIANT_ELEGANT
+ : VARIANT_COMPACT;
+ const uint32_t langListId = paint->getMinikinLangListId();
+ FontStyle minikinStyle(langListId, minikinVariant, resolved.getWeight(), resolved.getItalic());
+
+ /* Prepare minikin Paint */
+ // Note: it would be nice to handle fractional size values (it would improve smooth zoom
+ // behavior), but historically size has been treated as an int.
+ // TODO: explore whether to enable fractional sizes, possibly when linear text flag is set.
+ minikinPaint->size = (int)paint->getTextSize();
+ minikinPaint->scaleX = paint->getTextScaleX();
+ minikinPaint->skewX = paint->getTextSkewX();
+ minikinPaint->letterSpacing = paint->getLetterSpacing();
+ minikinPaint->paintFlags = MinikinFontSkia::packPaintFlags(paint);
+ minikinPaint->fontFeatureSettings = paint->getFontFeatureSettings();
+ minikinPaint->hyphenEdit = HyphenEdit(paint->getHyphenEdit());
+ return minikinStyle;
+}
+
+void MinikinUtils::doLayout(Layout* layout, const Paint* paint, int bidiFlags,
+ Typeface* typeface, const uint16_t* buf, size_t start, size_t count,
+ size_t bufSize) {
+ FontCollection *font;
+ MinikinPaint minikinPaint;
+ FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, &font, paint, typeface);
+ layout->setFontCollection(font);
+ layout->doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint);
+}
+
+float MinikinUtils::measureText(const Paint* paint, int bidiFlags, Typeface* typeface,
+ const uint16_t* buf, size_t start, size_t count, size_t bufSize, float *advances) {
+ FontCollection *font;
+ MinikinPaint minikinPaint;
+ FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, &font, paint, typeface);
+ return Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint,
+ font, advances);
+}
+
+bool MinikinUtils::hasVariationSelector(Typeface* typeface, uint32_t codepoint, uint32_t vs) {
+ const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
+ return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs);
+}
+
+float MinikinUtils::xOffsetForTextAlign(Paint* paint, const Layout& layout) {
+ switch (paint->getTextAlign()) {
+ case Paint::kCenter_Align:
+ return layout.getAdvance() * -0.5f;
+ break;
+ case Paint::kRight_Align:
+ return -layout.getAdvance();
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+float MinikinUtils::hOffsetForTextAlign(Paint* paint, const Layout& layout, const SkPath& path) {
+ float align = 0;
+ switch (paint->getTextAlign()) {
+ case Paint::kCenter_Align:
+ align = -0.5f;
+ break;
+ case Paint::kRight_Align:
+ align = -1;
+ break;
+ default:
+ return 0;
+ }
+ SkPathMeasure measure(path, false);
+ return align * (layout.getAdvance() - measure.getLength());
+}
+
+}
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
new file mode 100644
index 000000000000..cfaa961ac1fc
--- /dev/null
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+/**
+ * Utilities for making Minikin work, especially from existing objects like
+ * Paint and so on.
+ **/
+
+ // TODO: does this really need to be separate from MinikinSkia?
+
+#ifndef _ANDROID_GRAPHICS_MINIKIN_UTILS_H_
+#define _ANDROID_GRAPHICS_MINIKIN_UTILS_H_
+
+#include <cutils/compiler.h>
+#include <minikin/Layout.h>
+#include "Paint.h"
+#include "MinikinSkia.h"
+#include "Typeface.h"
+
+namespace android {
+
+class MinikinUtils {
+public:
+ ANDROID_API static FontStyle prepareMinikinPaint(MinikinPaint* minikinPaint, FontCollection** pFont,
+ const Paint* paint, Typeface* typeface);
+
+ ANDROID_API static void doLayout(Layout* layout, const Paint* paint, int bidiFlags,
+ Typeface* typeface, const uint16_t* buf, size_t start, size_t count,
+ size_t bufSize);
+
+ ANDROID_API static float measureText(const Paint* paint, int bidiFlags, Typeface* typeface,
+ const uint16_t* buf, size_t start, size_t count, size_t bufSize, float *advances);
+
+ ANDROID_API static bool hasVariationSelector(Typeface* typeface, uint32_t codepoint, uint32_t vs);
+
+ ANDROID_API static float xOffsetForTextAlign(Paint* paint, const Layout& layout);
+
+ ANDROID_API static float hOffsetForTextAlign(Paint* paint, const Layout& layout, const SkPath& path);
+ // f is a functor of type void f(size_t start, size_t end);
+ template <typename F>
+ ANDROID_API static void forFontRun(const Layout& layout, Paint* paint, F& f) {
+ float saveSkewX = paint->getTextSkewX();
+ bool savefakeBold = paint->isFakeBoldText();
+ MinikinFont* curFont = NULL;
+ size_t start = 0;
+ size_t nGlyphs = layout.nGlyphs();
+ for (size_t i = 0; i < nGlyphs; i++) {
+ MinikinFont* nextFont = layout.getFont(i);
+ if (i > 0 && nextFont != curFont) {
+ MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start));
+ f(start, i);
+ paint->setTextSkewX(saveSkewX);
+ paint->setFakeBoldText(savefakeBold);
+ start = i;
+ }
+ curFont = nextFont;
+ }
+ if (nGlyphs > start) {
+ MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start));
+ f(start, nGlyphs);
+ paint->setTextSkewX(saveSkewX);
+ paint->setFakeBoldText(savefakeBold);
+ }
+ }
+};
+
+} // namespace android
+
+#endif // _ANDROID_GRAPHICS_MINIKIN_UTILS_H_
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
new file mode 100644
index 000000000000..9599c30c639b
--- /dev/null
+++ b/libs/hwui/hwui/Paint.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013 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_GRAPHICS_PAINT_H_
+#define ANDROID_GRAPHICS_PAINT_H_
+
+#include <cutils/compiler.h>
+
+#include <SkPaint.h>
+#include <string>
+
+#include <minikin/FontFamily.h>
+
+namespace android {
+
+class ANDROID_API Paint : public SkPaint {
+public:
+ Paint();
+ Paint(const Paint& paint);
+ Paint(const SkPaint& paint);
+ ~Paint();
+
+ Paint& operator=(const Paint& other);
+
+ friend bool operator==(const Paint& a, const Paint& b);
+ friend bool operator!=(const Paint& a, const Paint& b) {
+ return !(a == b);
+ }
+
+ void setLetterSpacing(float letterSpacing) {
+ mLetterSpacing = letterSpacing;
+ }
+
+ float getLetterSpacing() const {
+ return mLetterSpacing;
+ }
+
+ void setFontFeatureSettings(const std::string& fontFeatureSettings) {
+ mFontFeatureSettings = fontFeatureSettings;
+ }
+
+ std::string getFontFeatureSettings() const {
+ return mFontFeatureSettings;
+ }
+
+ void setMinikinLangListId(uint32_t minikinLangListId) {
+ mMinikinLangListId = minikinLangListId;
+ }
+
+ uint32_t getMinikinLangListId() const {
+ return mMinikinLangListId;
+ }
+
+ void setFontVariant(FontVariant variant) {
+ mFontVariant = variant;
+ }
+
+ FontVariant getFontVariant() const {
+ return mFontVariant;
+ }
+
+ void setHyphenEdit(uint32_t hyphen) {
+ mHyphenEdit = hyphen;
+ }
+
+ uint32_t getHyphenEdit() const {
+ return mHyphenEdit;
+ }
+
+private:
+ float mLetterSpacing = 0;
+ std::string mFontFeatureSettings;
+ uint32_t mMinikinLangListId;
+ FontVariant mFontVariant;
+ uint32_t mHyphenEdit = 0;
+};
+
+} // namespace android
+
+#endif // ANDROID_GRAPHICS_PAINT_H_
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
new file mode 100644
index 000000000000..b27672ce16a0
--- /dev/null
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 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 "Paint.h"
+
+namespace android {
+
+Paint::Paint() :
+ SkPaint(), mLetterSpacing(0), mFontFeatureSettings(), mMinikinLangListId(0),
+ mFontVariant(VARIANT_DEFAULT) {
+}
+
+Paint::Paint(const Paint& paint) : SkPaint(paint),
+ mLetterSpacing(paint.mLetterSpacing), mFontFeatureSettings(paint.mFontFeatureSettings),
+ mMinikinLangListId(paint.mMinikinLangListId), mFontVariant(paint.mFontVariant),
+ mHyphenEdit(paint.mHyphenEdit) {
+}
+
+Paint::Paint(const SkPaint& paint) : SkPaint(paint),
+ mLetterSpacing(0), mFontFeatureSettings(), mMinikinLangListId(0),
+ mFontVariant(VARIANT_DEFAULT) {
+}
+
+Paint::~Paint() {
+}
+
+Paint& Paint::operator=(const Paint& other) {
+ SkPaint::operator=(other);
+ mLetterSpacing = other.mLetterSpacing;
+ mFontFeatureSettings = other.mFontFeatureSettings;
+ mMinikinLangListId = other.mMinikinLangListId;
+ mFontVariant = other.mFontVariant;
+ mHyphenEdit = other.mHyphenEdit;
+ return *this;
+}
+
+bool operator==(const Paint& a, const Paint& b) {
+ return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b)
+ && a.mLetterSpacing == b.mLetterSpacing
+ && a.mFontFeatureSettings == b.mFontFeatureSettings
+ && a.mMinikinLangListId == b.mMinikinLangListId
+ && a.mFontVariant == b.mFontVariant
+ && a.mHyphenEdit == b.mHyphenEdit;
+}
+
+}
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
new file mode 100644
index 000000000000..c583988554c7
--- /dev/null
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+/**
+ * This is the implementation of the Typeface object. Historically, it has
+ * just been SkTypeface, but we are migrating to Minikin. For the time
+ * being, that choice is hidden under the USE_MINIKIN compile-time flag.
+ */
+
+#include "Typeface.h"
+
+#include "MinikinSkia.h"
+#include "SkTypeface.h"
+#include "SkPaint.h"
+
+#include <minikin/FontCollection.h>
+#include <minikin/FontFamily.h>
+#include <minikin/Layout.h>
+#include <utils/Log.h>
+
+namespace android {
+
+// Resolve the 1..9 weight based on base weight and bold flag
+static void resolveStyle(Typeface* typeface) {
+ int weight = typeface->fBaseWeight / 100;
+ if (typeface->fSkiaStyle & SkTypeface::kBold) {
+ weight += 3;
+ }
+ if (weight > 9) {
+ weight = 9;
+ }
+ bool italic = (typeface->fSkiaStyle & SkTypeface::kItalic) != 0;
+ typeface->fStyle = FontStyle(weight, italic);
+}
+
+Typeface* gDefaultTypeface = NULL;
+pthread_once_t gDefaultTypefaceOnce = PTHREAD_ONCE_INIT;
+
+// This installs a default typeface (from a hardcoded path) that allows
+// layouts to work (not crash on null pointer) before the default
+// typeface is set.
+// TODO: investigate why layouts are being created before Typeface.java
+// class initialization.
+static FontCollection *makeFontCollection() {
+ std::vector<FontFamily *>typefaces;
+ const char *fns[] = {
+ "/system/fonts/Roboto-Regular.ttf",
+ };
+
+ FontFamily *family = new FontFamily();
+ for (size_t i = 0; i < sizeof(fns)/sizeof(fns[0]); i++) {
+ const char *fn = fns[i];
+ ALOGD("makeFontCollection adding %s", fn);
+ SkTypeface *skFace = SkTypeface::CreateFromFile(fn);
+ if (skFace != NULL) {
+ // TODO: might be a nice optimization to get access to the underlying font
+ // data, but would require us opening the file ourselves and passing that
+ // to the appropriate Create method of SkTypeface.
+ MinikinFont *font = new MinikinFontSkia(skFace, NULL, 0, 0);
+ family->addFont(font);
+ font->Unref();
+ } else {
+ ALOGE("failed to create font %s", fn);
+ }
+ }
+ typefaces.push_back(family);
+
+ FontCollection *result = new FontCollection(typefaces);
+ family->Unref();
+ return result;
+}
+
+static void getDefaultTypefaceOnce() {
+ Layout::init();
+ if (gDefaultTypeface == NULL) {
+ // We expect the client to set a default typeface, but provide a
+ // default so we can make progress before that happens.
+ gDefaultTypeface = new Typeface;
+ gDefaultTypeface->fFontCollection = makeFontCollection();
+ gDefaultTypeface->fSkiaStyle = SkTypeface::kNormal;
+ gDefaultTypeface->fBaseWeight = 400;
+ resolveStyle(gDefaultTypeface);
+ }
+}
+
+Typeface* Typeface::resolveDefault(Typeface* src) {
+ if (src == NULL) {
+ pthread_once(&gDefaultTypefaceOnce, getDefaultTypefaceOnce);
+ return gDefaultTypeface;
+ } else {
+ return src;
+ }
+}
+
+Typeface* Typeface::createFromTypeface(Typeface* src, SkTypeface::Style style) {
+ Typeface* resolvedFace = Typeface::resolveDefault(src);
+ Typeface* result = new Typeface;
+ if (result != 0) {
+ result->fFontCollection = resolvedFace->fFontCollection;
+ result->fFontCollection->Ref();
+ result->fSkiaStyle = style;
+ result->fBaseWeight = resolvedFace->fBaseWeight;
+ resolveStyle(result);
+ }
+ return result;
+}
+
+Typeface* Typeface::createWeightAlias(Typeface* src, int weight) {
+ Typeface* resolvedFace = Typeface::resolveDefault(src);
+ Typeface* result = new Typeface;
+ if (result != 0) {
+ result->fFontCollection = resolvedFace->fFontCollection;
+ result->fFontCollection->Ref();
+ result->fSkiaStyle = resolvedFace->fSkiaStyle;
+ result->fBaseWeight = weight;
+ resolveStyle(result);
+ }
+ return result;
+}
+
+Typeface* Typeface::createFromFamilies(const std::vector<FontFamily*>& families) {
+ Typeface* result = new Typeface;
+ result->fFontCollection = new FontCollection(families);
+ if (families.empty()) {
+ ALOGW("createFromFamilies creating empty collection");
+ result->fSkiaStyle = SkTypeface::kNormal;
+ } else {
+ const FontStyle defaultStyle;
+ FontFamily* firstFamily = reinterpret_cast<FontFamily*>(families[0]);
+ MinikinFont* mf = firstFamily->getClosestMatch(defaultStyle).font;
+ if (mf != NULL) {
+ SkTypeface* skTypeface = reinterpret_cast<MinikinFontSkia*>(mf)->GetSkTypeface();
+ // TODO: probably better to query more precise style from family, will be important
+ // when we open up API to access 100..900 weights
+ result->fSkiaStyle = skTypeface->style();
+ } else {
+ result->fSkiaStyle = SkTypeface::kNormal;
+ }
+ }
+ result->fBaseWeight = 400;
+ resolveStyle(result);
+ return result;
+}
+
+void Typeface::unref() {
+ fFontCollection->Unref();
+ delete this;
+}
+
+void Typeface::setDefault(Typeface* face) {
+ gDefaultTypeface = face;
+}
+
+}
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
new file mode 100644
index 000000000000..8862e5a5a911
--- /dev/null
+++ b/libs/hwui/hwui/Typeface.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 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_GRAPHICS_TYPEFACE_IMPL_H_
+#define _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_
+
+#include "SkTypeface.h"
+
+#include <cutils/compiler.h>
+#include <minikin/FontCollection.h>
+#include <vector>
+
+namespace android {
+
+struct ANDROID_API Typeface {
+ FontCollection *fFontCollection;
+
+ // style used for constructing and querying Typeface objects
+ SkTypeface::Style fSkiaStyle;
+ // base weight in CSS-style units, 100..900
+ int fBaseWeight;
+
+ // resolved style actually used for rendering
+ FontStyle fStyle;
+
+ void unref();
+
+ static Typeface* resolveDefault(Typeface* src);
+
+ static Typeface* createFromTypeface(Typeface* src, SkTypeface::Style style);
+
+ static Typeface* createWeightAlias(Typeface* src, int baseweight);
+
+ static Typeface* createFromFamilies(const std::vector<FontFamily*>& families);
+
+ static void setDefault(Typeface* face);
+};
+
+}
+
+#endif // _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_
diff --git a/libs/hwui/hwui_static_deps.mk b/libs/hwui/hwui_static_deps.mk
new file mode 100644
index 000000000000..299095217a8d
--- /dev/null
+++ b/libs/hwui/hwui_static_deps.mk
@@ -0,0 +1,31 @@
+###############################################################################
+#
+#
+# This file contains the shared and static dependencies needed by any target
+# that attempts to statically link HWUI (i.e. libhwui_static build target). This
+# file should be included by any target that lists libhwui_static as a
+# dependency.
+#
+# This is a workaround for the fact that the build system does not add these
+# transitive dependencies when it attempts to link libhwui_static into another
+# library.
+#
+###############################################################################
+
+LOCAL_SHARED_LIBRARIES += \
+ liblog \
+ libcutils \
+ libutils \
+ libEGL \
+ libGLESv2 \
+ libskia \
+ libui \
+ libgui \
+ libprotobuf-cpp-lite \
+ libharfbuzz_ng \
+ libft2 \
+ libminikin
+
+ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
+ LOCAL_SHARED_LIBRARIES += libRS libRScpp
+endif
diff --git a/libs/hwui/protos/ProtoHelpers.h b/libs/hwui/protos/ProtoHelpers.h
new file mode 100644
index 000000000000..832e31200eb6
--- /dev/null
+++ b/libs/hwui/protos/ProtoHelpers.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 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 PROTOHELPERS_H
+#define PROTOHELPERS_H
+
+#include "Rect.h"
+#include "protos/hwui.pb.h"
+
+namespace android {
+namespace uirenderer {
+
+void set(proto::RectF* dest, const Rect& src) {
+ dest->set_left(src.left);
+ dest->set_top(src.top);
+ dest->set_right(src.right);
+ dest->set_bottom(src.bottom);
+}
+
+void set(std::string* dest, const SkPath& src) {
+ size_t size = src.writeToMemory(nullptr);
+ dest->resize(size);
+ src.writeToMemory(&*dest->begin());
+}
+
+} // namespace uirenderer
+} // namespace android
+
+#endif // PROTOHELPERS_H
diff --git a/libs/hwui/protos/hwui.proto b/libs/hwui/protos/hwui.proto
new file mode 100644
index 000000000000..dcff80a24974
--- /dev/null
+++ b/libs/hwui/protos/hwui.proto
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+syntax = "proto2";
+
+package android.uirenderer.proto;
+
+option optimize_for = LITE_RUNTIME;
+
+message RenderNode {
+ required uint64 id = 1;
+ required string name = 2;
+ required RenderProperties properties = 3;
+ optional DisplayList display_list = 4;
+ repeated RenderNode children = 5;
+};
+
+message RenderProperties {
+ required int32 left = 1;
+ required int32 right = 2;
+ required int32 top = 3;
+ required int32 bottom = 4;
+ required int32 clip_flags = 5;
+ required float alpha = 6;
+ required float translation_x = 7;
+ required float translation_y = 8;
+ required float translation_z = 9;
+ required float elevation = 10;
+ required float rotation = 11;
+ required float rotation_x = 12;
+ required float rotation_y = 13;
+ required float scale_x = 14;
+ required float scale_y = 15;
+ required float pivot_x = 16;
+ required float pivot_y = 17;
+ required bool has_overlapping_rendering = 18;
+ required bool pivot_explicitly_set = 19;
+ required bool project_backwards = 20;
+ required bool projection_receiver = 21;
+ required RectF clip_bounds = 22;
+ optional Outline outline = 23;
+ optional RevealClip reveal_clip = 24;
+};
+
+message RectF {
+ required float left = 1;
+ required float right = 2;
+ required float top = 3;
+ required float bottom = 4;
+}
+
+message Outline {
+ required bool should_clip = 1;
+ enum Type {
+ None = 0;
+ Empty = 1;
+ ConvexPath = 2;
+ RoundRect = 3;
+ }
+ required Type type = 2;
+ required RectF bounds = 3;
+ required float radius = 4;
+ required float alpha = 5;
+ optional bytes path = 6;
+}
+
+message RevealClip {
+ required float x = 1;
+ required float y = 2;
+ required float radius = 3;
+}
+
+message DisplayList {
+ optional int32 projection_receive_index = 1;
+ repeated DrawOp draw_ops = 2;
+}
+
+message DrawOp {
+ oneof drawop {
+ DrawOp_RenderNode render_node = 1;
+ }
+}
+
+message DrawOp_RenderNode {
+ optional RenderNode node = 1;
+}
diff --git a/libs/hwui/renderstate/Blend.cpp b/libs/hwui/renderstate/Blend.cpp
index 29927ed8667b..93f787d31745 100644
--- a/libs/hwui/renderstate/Blend.cpp
+++ b/libs/hwui/renderstate/Blend.cpp
@@ -30,6 +30,26 @@ struct Blender {
GLenum dst;
};
+// assumptions made by lookup tables in either this file or ProgramCache
+static_assert(0 == SkXfermode::kClear_Mode, "SkXfermode enums have changed");
+static_assert(1 == SkXfermode::kSrc_Mode, "SkXfermode enums have changed");
+static_assert(2 == SkXfermode::kDst_Mode, "SkXfermode enums have changed");
+static_assert(3 == SkXfermode::kSrcOver_Mode, "SkXfermode enums have changed");
+static_assert(4 == SkXfermode::kDstOver_Mode, "SkXfermode enums have changed");
+static_assert(5 == SkXfermode::kSrcIn_Mode, "SkXfermode enums have changed");
+static_assert(6 == SkXfermode::kDstIn_Mode, "SkXfermode enums have changed");
+static_assert(7 == SkXfermode::kSrcOut_Mode, "SkXfermode enums have changed");
+static_assert(8 == SkXfermode::kDstOut_Mode, "SkXfermode enums have changed");
+static_assert(9 == SkXfermode::kSrcATop_Mode, "SkXfermode enums have changed");
+static_assert(10 == SkXfermode::kDstATop_Mode, "SkXfermode enums have changed");
+static_assert(11 == SkXfermode::kXor_Mode, "SkXfermode enums have changed");
+static_assert(12 == SkXfermode::kPlus_Mode, "SkXfermode enums have changed");
+static_assert(13 == SkXfermode::kModulate_Mode, "SkXfermode enums have changed");
+static_assert(14 == SkXfermode::kScreen_Mode, "SkXfermode enums have changed");
+static_assert(15 == SkXfermode::kOverlay_Mode, "SkXfermode enums have changed");
+static_assert(16 == SkXfermode::kDarken_Mode, "SkXfermode enums have changed");
+static_assert(17 == SkXfermode::kLighten_Mode, "SkXfermode enums have changed");
+
// In this array, the index of each Blender equals the value of the first
// entry. For instance, gBlends[1] == gBlends[SkXfermode::kSrc_Mode]
const Blender kBlends[] = {
@@ -78,20 +98,6 @@ Blend::Blend()
// gl blending off by default
}
-void Blend::enable(SkXfermode::Mode mode, ModeOrderSwap modeUsage) {
- GLenum srcMode;
- GLenum dstMode;
- getFactors(mode, modeUsage, &srcMode, &dstMode);
- setFactors(srcMode, dstMode);
-}
-
-void Blend::disable() {
- if (mEnabled) {
- glDisable(GL_BLEND);
- mEnabled = false;
- }
-}
-
void Blend::invalidate() {
syncEnabled();
mSrcMode = mDstMode = GL_ZERO;
@@ -112,8 +118,13 @@ void Blend::getFactors(SkXfermode::Mode mode, ModeOrderSwap modeUsage, GLenum* o
void Blend::setFactors(GLenum srcMode, GLenum dstMode) {
if (srcMode == GL_ZERO && dstMode == GL_ZERO) {
- disable();
+ // disable blending
+ if (mEnabled) {
+ glDisable(GL_BLEND);
+ mEnabled = false;
+ }
} else {
+ // enable blending
if (!mEnabled) {
glEnable(GL_BLEND);
mEnabled = true;
diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h
index dcc681d4aa50..df9e5a8af879 100644
--- a/libs/hwui/renderstate/Blend.h
+++ b/libs/hwui/renderstate/Blend.h
@@ -34,9 +34,6 @@ public:
NoSwap,
Swap,
};
-
- void enable(SkXfermode::Mode mode, ModeOrderSwap modeUsage);
- void disable();
void syncEnabled();
static void getFactors(SkXfermode::Mode mode, ModeOrderSwap modeUsage,
diff --git a/libs/hwui/renderstate/MeshState.cpp b/libs/hwui/renderstate/MeshState.cpp
index 0521f6573e39..b575c696586e 100644
--- a/libs/hwui/renderstate/MeshState.cpp
+++ b/libs/hwui/renderstate/MeshState.cpp
@@ -34,7 +34,6 @@ MeshState::MeshState()
glGenBuffers(1, &mUnitQuadBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mUnitQuadBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(kUnitQuadVertices), kUnitQuadVertices, GL_STATIC_DRAW);
-
mCurrentBuffer = mUnitQuadBuffer;
uint16_t regionIndices[kMaxNumberOfQuads * 6];
@@ -78,42 +77,58 @@ void MeshState::dump() {
// Buffer Objects
///////////////////////////////////////////////////////////////////////////////
-bool MeshState::bindMeshBuffer() {
- return bindMeshBuffer(mUnitQuadBuffer);
+void MeshState::bindMeshBuffer(GLuint buffer) {
+ if (mCurrentBuffer != buffer) {
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ mCurrentBuffer = buffer;
+
+ // buffer has changed, so invalidate cached vertex pos/texcoord pointers
+ resetVertexPointers();
+ }
}
-bool MeshState::bindMeshBuffer(GLuint buffer) {
- if (!buffer) buffer = mUnitQuadBuffer;
- return bindMeshBufferInternal(buffer);
+void MeshState::unbindMeshBuffer() {
+ return bindMeshBuffer(0);
}
-bool MeshState::unbindMeshBuffer() {
- return bindMeshBufferInternal(0);
+void MeshState::genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size,
+ const void* data, GLenum usage) {
+ if (!*buffer) {
+ glGenBuffers(1, buffer);
+ }
+ bindMeshBuffer(*buffer);
+ glBufferData(GL_ARRAY_BUFFER, size, data, usage);
}
-bool MeshState::bindMeshBufferInternal(GLuint buffer) {
- if (mCurrentBuffer != buffer) {
- glBindBuffer(GL_ARRAY_BUFFER, buffer);
- mCurrentBuffer = buffer;
- return true;
+void MeshState::deleteMeshBuffer(GLuint buffer) {
+ if (buffer == mCurrentBuffer) {
+ // GL defines that deleting the currently bound VBO rebinds to 0 (no VBO).
+ // Reflect this in our cached value.
+ mCurrentBuffer = 0;
}
- return false;
+ glDeleteBuffers(1, &buffer);
}
///////////////////////////////////////////////////////////////////////////////
// Vertices
///////////////////////////////////////////////////////////////////////////////
-void MeshState::bindPositionVertexPointer(bool force, const GLvoid* vertices, GLsizei stride) {
- if (force || vertices != mCurrentPositionPointer || stride != mCurrentPositionStride) {
+void MeshState::bindPositionVertexPointer(const GLvoid* vertices, GLsizei stride) {
+ // update pos coords if !current vbo, since vertices may point into mutable memory (e.g. stack)
+ if (mCurrentBuffer == 0
+ || vertices != mCurrentPositionPointer
+ || stride != mCurrentPositionStride) {
glVertexAttribPointer(Program::kBindingPosition, 2, GL_FLOAT, GL_FALSE, stride, vertices);
mCurrentPositionPointer = vertices;
mCurrentPositionStride = stride;
}
}
-void MeshState::bindTexCoordsVertexPointer(bool force, const GLvoid* vertices, GLsizei stride) {
- if (force || vertices != mCurrentTexCoordsPointer || stride != mCurrentTexCoordsStride) {
+void MeshState::bindTexCoordsVertexPointer(const GLvoid* vertices, GLsizei stride) {
+ // update tex coords if !current vbo, since vertices may point into mutable memory (e.g. stack)
+ if (mCurrentBuffer == 0
+ || vertices != mCurrentTexCoordsPointer
+ || stride != mCurrentTexCoordsStride) {
glVertexAttribPointer(Program::kBindingTexCoords, 2, GL_FLOAT, GL_FALSE, stride, vertices);
mCurrentTexCoordsPointer = vertices;
mCurrentTexCoordsStride = stride;
@@ -125,10 +140,6 @@ void MeshState::resetVertexPointers() {
mCurrentTexCoordsPointer = this;
}
-void MeshState::resetTexCoordsVertexPointer() {
- mCurrentTexCoordsPointer = this;
-}
-
void MeshState::enableTexCoordsVertexArray() {
if (!mTexCoordsArrayEnabled) {
glEnableVertexAttribArray(Program::kBindingTexCoords);
@@ -148,26 +159,18 @@ void MeshState::disableTexCoordsVertexArray() {
// Indices
///////////////////////////////////////////////////////////////////////////////
-bool MeshState::bindIndicesBufferInternal(const GLuint buffer) {
+void MeshState::bindIndicesBuffer(const GLuint buffer) {
if (mCurrentIndicesBuffer != buffer) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
mCurrentIndicesBuffer = buffer;
- return true;
}
- return false;
-}
-
-bool MeshState::bindQuadIndicesBuffer() {
- return bindIndicesBufferInternal(mQuadListIndices);
}
-bool MeshState::unbindIndicesBuffer() {
+void MeshState::unbindIndicesBuffer() {
if (mCurrentIndicesBuffer) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
mCurrentIndicesBuffer = 0;
- return true;
}
- return false;
}
} /* namespace uirenderer */
diff --git a/libs/hwui/renderstate/MeshState.h b/libs/hwui/renderstate/MeshState.h
index e80f4d0d6c41..dd684686f584 100644
--- a/libs/hwui/renderstate/MeshState.h
+++ b/libs/hwui/renderstate/MeshState.h
@@ -60,20 +60,19 @@ public:
///////////////////////////////////////////////////////////////////////////////
// Buffer objects
///////////////////////////////////////////////////////////////////////////////
- /**
- * Binds the VBO used to render simple textured quads.
- */
- bool bindMeshBuffer();
/**
* Binds the specified VBO if needed. If buffer == 0, binds default simple textured quad.
*/
- bool bindMeshBuffer(GLuint buffer);
+ void bindMeshBuffer(GLuint buffer);
/**
- * Unbinds the VBO used to render simple textured quads.
+ * Unbinds the current VBO if active.
*/
- bool unbindMeshBuffer();
+ void unbindMeshBuffer();
+
+ void genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size, const void* data, GLenum usage);
+ void deleteMeshBuffer(GLuint);
///////////////////////////////////////////////////////////////////////////////
// Vertices
@@ -82,21 +81,20 @@ public:
* Binds an attrib to the specified float vertex pointer.
* Assumes a stride of gTextureVertexStride and a size of 2.
*/
- void bindPositionVertexPointer(bool force, const GLvoid* vertices,
+ void bindPositionVertexPointer(const GLvoid* vertices,
GLsizei stride = kTextureVertexStride);
/**
* Binds an attrib to the specified float vertex pointer.
* Assumes a stride of gTextureVertexStride and a size of 2.
*/
- void bindTexCoordsVertexPointer(bool force, const GLvoid* vertices,
+ void bindTexCoordsVertexPointer(const GLvoid* vertices,
GLsizei stride = kTextureVertexStride);
/**
* Resets the vertex pointers.
*/
void resetVertexPointers();
- void resetTexCoordsVertexPointer();
void enableTexCoordsVertexArray();
void disableTexCoordsVertexArray();
@@ -104,12 +102,8 @@ public:
///////////////////////////////////////////////////////////////////////////////
// Indices
///////////////////////////////////////////////////////////////////////////////
- /**
- * Binds a global indices buffer that can draw up to
- * gMaxNumberOfQuads quads.
- */
- bool bindQuadIndicesBuffer();
- bool unbindIndicesBuffer();
+ void bindIndicesBuffer(const GLuint buffer);
+ void unbindIndicesBuffer();
///////////////////////////////////////////////////////////////////////////////
// Getters - for use in Glop building
@@ -118,8 +112,6 @@ public:
GLuint getQuadListIBO() { return mQuadListIndices; }
private:
MeshState();
- bool bindMeshBufferInternal(const GLuint buffer);
- bool bindIndicesBufferInternal(const GLuint buffer);
GLuint mUnitQuadBuffer;
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
new file mode 100644
index 000000000000..73b6c02a3fdc
--- /dev/null
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2015 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 "OffscreenBufferPool.h"
+
+#include "Caches.h"
+#include "Properties.h"
+#include "renderstate/RenderState.h"
+#include "utils/FatVector.h"
+
+#include <utils/Log.h>
+
+#include <GLES2/gl2.h>
+
+namespace android {
+namespace uirenderer {
+
+////////////////////////////////////////////////////////////////////////////////
+// OffscreenBuffer
+////////////////////////////////////////////////////////////////////////////////
+
+OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
+ uint32_t viewportWidth, uint32_t viewportHeight)
+ : GpuMemoryTracker(GpuObjectType::OffscreenBuffer)
+ , renderState(renderState)
+ , viewportWidth(viewportWidth)
+ , viewportHeight(viewportHeight)
+ , texture(caches) {
+ uint32_t width = computeIdealDimension(viewportWidth);
+ uint32_t height = computeIdealDimension(viewportHeight);
+ caches.textureState().activateTexture(0);
+ texture.resize(width, height, GL_RGBA);
+ texture.blend = true;
+ texture.setWrap(GL_CLAMP_TO_EDGE);
+ // not setting filter on texture, since it's set when drawing, based on transform
+}
+
+Rect OffscreenBuffer::getTextureCoordinates() {
+ const float texX = 1.0f / static_cast<float>(texture.width());
+ const float texY = 1.0f / static_cast<float>(texture.height());
+ return Rect(0, viewportHeight * texY, viewportWidth * texX, 0);
+}
+
+void OffscreenBuffer::dirty(Rect dirtyArea) {
+ dirtyArea.doIntersect(0, 0, viewportWidth, viewportHeight);
+ if (!dirtyArea.isEmpty()) {
+ region.orSelf(android::Rect(dirtyArea.left, dirtyArea.top,
+ dirtyArea.right, dirtyArea.bottom));
+ }
+}
+
+void OffscreenBuffer::updateMeshFromRegion() {
+ // avoid T-junctions as they cause artifacts in between the resultant
+ // geometry when complex transforms occur.
+ // TODO: generate the safeRegion only if necessary based on drawing transform
+ Region safeRegion = Region::createTJunctionFreeRegion(region);
+
+ size_t count;
+ const android::Rect* rects = safeRegion.getArray(&count);
+
+ const float texX = 1.0f / float(texture.width());
+ const float texY = 1.0f / float(texture.height());
+
+ FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
+ TextureVertex* mesh = &meshVector[0];
+ for (size_t i = 0; i < count; i++) {
+ const android::Rect* r = &rects[i];
+
+ const float u1 = r->left * texX;
+ const float v1 = (viewportHeight - r->top) * texY;
+ const float u2 = r->right * texX;
+ const float v2 = (viewportHeight - r->bottom) * texY;
+
+ TextureVertex::set(mesh++, r->left, r->top, u1, v1);
+ TextureVertex::set(mesh++, r->right, r->top, u2, v1);
+ TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
+ TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
+ }
+ elementCount = count * 6;
+ renderState.meshState().genOrUpdateMeshBuffer(&vbo,
+ sizeof(TextureVertex) * count * 4,
+ &meshVector[0],
+ GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer
+}
+
+uint32_t OffscreenBuffer::computeIdealDimension(uint32_t dimension) {
+ return uint32_t(ceilf(dimension / float(LAYER_SIZE)) * LAYER_SIZE);
+}
+
+OffscreenBuffer::~OffscreenBuffer() {
+ texture.deleteTexture();
+ renderState.meshState().deleteMeshBuffer(vbo);
+ elementCount = 0;
+ vbo = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// OffscreenBufferPool
+///////////////////////////////////////////////////////////////////////////////
+
+OffscreenBufferPool::OffscreenBufferPool()
+ : mMaxSize(Properties::layerPoolSize) {
+}
+
+OffscreenBufferPool::~OffscreenBufferPool() {
+ clear(); // TODO: unique_ptr?
+}
+
+int OffscreenBufferPool::Entry::compare(const Entry& lhs, const Entry& rhs) {
+ int deltaInt = int(lhs.width) - int(rhs.width);
+ if (deltaInt != 0) return deltaInt;
+
+ return int(lhs.height) - int(rhs.height);
+}
+
+void OffscreenBufferPool::clear() {
+ for (auto& entry : mPool) {
+ delete entry.layer;
+ }
+ mPool.clear();
+ mSize = 0;
+}
+
+OffscreenBuffer* OffscreenBufferPool::get(RenderState& renderState,
+ const uint32_t width, const uint32_t height) {
+ OffscreenBuffer* layer = nullptr;
+
+ Entry entry(width, height);
+ auto iter = mPool.find(entry);
+
+ if (iter != mPool.end()) {
+ entry = *iter;
+ mPool.erase(iter);
+
+ layer = entry.layer;
+ layer->viewportWidth = width;
+ layer->viewportHeight = height;
+ mSize -= layer->getSizeInBytes();
+ } else {
+ layer = new OffscreenBuffer(renderState, Caches::getInstance(), width, height);
+ }
+
+ return layer;
+}
+
+OffscreenBuffer* OffscreenBufferPool::resize(OffscreenBuffer* layer,
+ const uint32_t width, const uint32_t height) {
+ RenderState& renderState = layer->renderState;
+ if (layer->texture.width() == OffscreenBuffer::computeIdealDimension(width)
+ && layer->texture.height() == OffscreenBuffer::computeIdealDimension(height)) {
+ // resize in place
+ layer->viewportWidth = width;
+ layer->viewportHeight = height;
+
+ // entire area will be repainted (and may be smaller) so clear usage region
+ layer->region.clear();
+ return layer;
+ }
+ putOrDelete(layer);
+ return get(renderState, width, height);
+}
+
+void OffscreenBufferPool::dump() {
+ for (auto entry : mPool) {
+ ALOGD(" Layer size %dx%d", entry.width, entry.height);
+ }
+}
+
+void OffscreenBufferPool::putOrDelete(OffscreenBuffer* layer) {
+ const uint32_t size = layer->getSizeInBytes();
+ // Don't even try to cache a layer that's bigger than the cache
+ if (size < mMaxSize) {
+ // TODO: Use an LRU
+ while (mSize + size > mMaxSize) {
+ OffscreenBuffer* victim = mPool.begin()->layer;
+ mSize -= victim->getSizeInBytes();
+ delete victim;
+ mPool.erase(mPool.begin());
+ }
+
+ // clear region, since it's no longer valid
+ layer->region.clear();
+
+ Entry entry(layer);
+
+ mPool.insert(entry);
+ mSize += size;
+ } else {
+ delete layer;
+ }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
new file mode 100644
index 000000000000..089f131668a0
--- /dev/null
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2015 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_OFFSCREEN_BUFFER_POOL_H
+#define ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
+
+#include <GpuMemoryTracker.h>
+#include "Caches.h"
+#include "Texture.h"
+#include "utils/Macros.h"
+#include <ui/Region.h>
+
+#include <set>
+
+namespace android {
+namespace uirenderer {
+
+class RenderState;
+
+/**
+ * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
+ * encompasses enough information to draw it back on screen (minus paint properties, which are held
+ * by LayerOp).
+ *
+ * Has two distinct sizes - viewportWidth/viewportHeight describe content area,
+ * texture.width/.height are actual allocated texture size. Texture will tend to be larger than the
+ * viewport bounds, since textures are always allocated with width / height as a multiple of 64, for
+ * the purpose of improving reuse.
+ */
+class OffscreenBuffer : GpuMemoryTracker {
+public:
+ OffscreenBuffer(RenderState& renderState, Caches& caches,
+ uint32_t viewportWidth, uint32_t viewportHeight);
+ ~OffscreenBuffer();
+
+ Rect getTextureCoordinates();
+
+ void dirty(Rect dirtyArea);
+
+ // must be called prior to rendering, to construct/update vertex buffer
+ void updateMeshFromRegion();
+
+ // Set by RenderNode for HW layers, TODO for clipped saveLayers
+ void setWindowTransform(const Matrix4& transform) {
+ inverseTransformInWindow.loadInverse(transform);
+ }
+
+ static uint32_t computeIdealDimension(uint32_t dimension);
+
+ uint32_t getSizeInBytes() { return texture.objectSize(); }
+
+ RenderState& renderState;
+
+ uint32_t viewportWidth;
+ uint32_t viewportHeight;
+ Texture texture;
+
+ // Portion of layer that has been drawn to. Used to minimize drawing area when
+ // drawing back to screen / parent FBO.
+ Region region;
+
+ Matrix4 inverseTransformInWindow;
+
+ // vbo / size of mesh
+ GLsizei elementCount = 0;
+ GLuint vbo = 0;
+};
+
+/**
+ * Pool of OffscreenBuffers allocated, but not currently in use.
+ */
+class OffscreenBufferPool {
+public:
+ OffscreenBufferPool();
+ ~OffscreenBufferPool();
+
+ WARN_UNUSED_RESULT OffscreenBuffer* get(RenderState& renderState,
+ const uint32_t width, const uint32_t height);
+
+ WARN_UNUSED_RESULT OffscreenBuffer* resize(OffscreenBuffer* layer,
+ const uint32_t width, const uint32_t height);
+
+ void putOrDelete(OffscreenBuffer* layer);
+
+ /**
+ * Clears the pool. This causes all layers to be deleted.
+ */
+ void clear();
+
+ /**
+ * Returns the maximum size of the pool in bytes.
+ */
+ uint32_t getMaxSize() { return mMaxSize; }
+
+ /**
+ * Returns the current size of the pool in bytes.
+ */
+ uint32_t getSize() { return mSize; }
+
+ size_t getCount() { return mPool.size(); }
+
+ /**
+ * Prints out the content of the pool.
+ */
+ void dump();
+private:
+ struct Entry {
+ Entry() {}
+
+ Entry(const uint32_t layerWidth, const uint32_t layerHeight)
+ : width(OffscreenBuffer::computeIdealDimension(layerWidth))
+ , height(OffscreenBuffer::computeIdealDimension(layerHeight)) {}
+
+ Entry(OffscreenBuffer* layer)
+ : layer(layer)
+ , width(layer->texture.width())
+ , height(layer->texture.height()) {
+ }
+
+ static int compare(const Entry& lhs, const Entry& rhs);
+
+ bool operator==(const Entry& other) const {
+ return compare(*this, other) == 0;
+ }
+
+ bool operator!=(const Entry& other) const {
+ return compare(*this, other) != 0;
+ }
+
+ bool operator<(const Entry& other) const {
+ return Entry::compare(*this, other) < 0;
+ }
+
+ OffscreenBuffer* layer = nullptr;
+ uint32_t width = 0;
+ uint32_t height = 0;
+ }; // struct Entry
+
+ std::multiset<Entry> mPool;
+
+ uint32_t mSize = 0;
+ uint32_t mMaxSize;
+}; // class OffscreenBufferCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 1e39bfa4b583..ea4391b87006 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#include <GpuMemoryTracker.h>
#include "renderstate/RenderState.h"
#include "renderthread/CanvasContext.h"
#include "renderthread/EglManager.h"
#include "utils/GLUtils.h"
+#include <algorithm>
namespace android {
namespace uirenderer {
@@ -38,6 +40,8 @@ RenderState::~RenderState() {
void RenderState::onGLContextCreated() {
LOG_ALWAYS_FATAL_IF(mBlend || mMeshState || mScissor || mStencil,
"State object lifecycle not managed correctly");
+ GpuMemoryTracker::onGLContextCreated();
+
mBlend = new Blend();
mMeshState = new MeshState();
mScissor = new Scissor();
@@ -88,6 +92,8 @@ void RenderState::onGLContextDestroyed() {
}
*/
+ mLayerPool.clear();
+
// TODO: reset all cached state in state objects
std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext);
mAssetAtlas.terminate();
@@ -102,6 +108,21 @@ void RenderState::onGLContextDestroyed() {
mScissor = nullptr;
delete mStencil;
mStencil = nullptr;
+
+ GpuMemoryTracker::onGLContextDestroyed();
+}
+
+void RenderState::flush(Caches::FlushMode mode) {
+ switch (mode) {
+ case Caches::FlushMode::Full:
+ // fall through
+ case Caches::FlushMode::Moderate:
+ // fall through
+ case Caches::FlushMode::Layers:
+ mLayerPool.clear();
+ break;
+ }
+ mCaches->flush(mode);
}
void RenderState::setViewport(GLsizei width, GLsizei height) {
@@ -123,6 +144,21 @@ void RenderState::bindFramebuffer(GLuint fbo) {
}
}
+GLuint RenderState::createFramebuffer() {
+ GLuint ret;
+ glGenFramebuffers(1, &ret);
+ return ret;
+}
+
+void RenderState::deleteFramebuffer(GLuint fbo) {
+ if (mFramebuffer == fbo) {
+ // GL defines that deleting the currently bound FBO rebinds FBO 0.
+ // Reflect this in our cached value.
+ mFramebuffer = 0;
+ }
+ glDeleteFramebuffers(1, &fbo);
+}
+
void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) {
if (mode == DrawGlInfo::kModeProcessNoContext) {
// If there's no context we don't need to interrupt as there's
@@ -173,17 +209,6 @@ void RenderState::debugOverdraw(bool enable, bool clear) {
}
}
-void RenderState::requireGLContext() {
- assertOnGLThread();
- LOG_ALWAYS_FATAL_IF(!mRenderThread.eglManager().hasEglContext(),
- "No GL context!");
-}
-
-void RenderState::assertOnGLThread() {
- pthread_t curr = pthread_self();
- LOG_ALWAYS_FATAL_IF(!pthread_equal(mThreadId, curr), "Wrong thread!");
-}
-
class DecStrongTask : public renderthread::RenderTask {
public:
DecStrongTask(VirtualLightRefBase* object) : mObject(object) {}
@@ -199,19 +224,25 @@ private:
};
void RenderState::postDecStrong(VirtualLightRefBase* object) {
- mRenderThread.queue(new DecStrongTask(object));
+ if (pthread_equal(mThreadId, pthread_self())) {
+ object->decStrong(nullptr);
+ } else {
+ mRenderThread.queue(new DecStrongTask(object));
+ }
}
///////////////////////////////////////////////////////////////////////////////
// Render
///////////////////////////////////////////////////////////////////////////////
-void RenderState::render(const Glop& glop) {
+void RenderState::render(const Glop& glop, const Matrix4& orthoMatrix) {
const Glop::Mesh& mesh = glop.mesh;
const Glop::Mesh::Vertices& vertices = mesh.vertices;
const Glop::Mesh::Indices& indices = mesh.indices;
const Glop::Fill& fill = glop.fill;
+ GL_CHECKPOINT(MODERATE);
+
// ---------------------------------------------
// ---------- Program + uniform setup ----------
// ---------------------------------------------
@@ -221,17 +252,17 @@ void RenderState::render(const Glop& glop) {
fill.program->setColor(fill.color);
}
- fill.program->set(glop.transform.ortho,
+ fill.program->set(orthoMatrix,
glop.transform.modelView,
glop.transform.meshTransform(),
glop.transform.transformFlags & TransformFlags::OffsetByFudgeFactor);
// Color filter uniforms
- if (fill.filterMode == ProgramDescription::kColorBlend) {
+ if (fill.filterMode == ProgramDescription::ColorFilterMode::Blend) {
const FloatColor& color = fill.filter.color;
glUniform4f(mCaches->program().getUniform("colorBlend"),
color.r, color.g, color.b, color.a);
- } else if (fill.filterMode == ProgramDescription::kColorMatrix) {
+ } else if (fill.filterMode == ProgramDescription::ColorFilterMode::Matrix) {
glUniformMatrix4fv(mCaches->program().getUniform("colorMatrix"), 1, GL_FALSE,
fill.filter.matrix.matrix);
glUniform4fv(mCaches->program().getUniform("colorMatrixVector"), 1,
@@ -255,32 +286,33 @@ void RenderState::render(const Glop& glop) {
roundedOutRadius);
}
+ GL_CHECKPOINT(MODERATE);
+
// --------------------------------
// ---------- Mesh setup ----------
// --------------------------------
// vertices
- const bool force = meshState().bindMeshBufferInternal(vertices.bufferObject)
- || (vertices.position != nullptr);
- meshState().bindPositionVertexPointer(force, vertices.position, vertices.stride);
+ meshState().bindMeshBuffer(vertices.bufferObject);
+ meshState().bindPositionVertexPointer(vertices.position, vertices.stride);
// indices
- meshState().bindIndicesBufferInternal(indices.bufferObject);
+ meshState().bindIndicesBuffer(indices.bufferObject);
if (vertices.attribFlags & VertexAttribFlags::TextureCoord) {
const Glop::Fill::TextureData& texture = fill.texture;
// texture always takes slot 0, shader samplers increment from there
mCaches->textureState().activateTexture(0);
+ mCaches->textureState().bindTexture(texture.target, texture.texture->id());
if (texture.clamp != GL_INVALID_ENUM) {
- texture.texture->setWrap(texture.clamp, true, false, texture.target);
+ texture.texture->setWrap(texture.clamp, false, false, texture.target);
}
if (texture.filter != GL_INVALID_ENUM) {
- texture.texture->setFilter(texture.filter, true, false, texture.target);
+ texture.texture->setFilter(texture.filter, false, false, texture.target);
}
- mCaches->textureState().bindTexture(texture.target, texture.texture->id);
meshState().enableTexCoordsVertexArray();
- meshState().bindTexCoordsVertexPointer(force, vertices.texCoord, vertices.stride);
+ meshState().bindTexCoordsVertexPointer(vertices.texCoord, vertices.stride);
if (texture.textureTransform) {
glUniformMatrix4fv(fill.program->getUniform("mainTextureTransform"), 1,
@@ -306,11 +338,18 @@ void RenderState::render(const Glop& glop) {
// Shader uniforms
SkiaShader::apply(*mCaches, fill.skiaShaderData);
+ GL_CHECKPOINT(MODERATE);
+ Texture* texture = (fill.skiaShaderData.skiaShaderType & kBitmap_SkiaShaderType) ?
+ fill.skiaShaderData.bitmapData.bitmapTexture : nullptr;
+ const AutoTexture autoCleanup(texture);
+
// ------------------------------------
// ---------- GL state setup ----------
// ------------------------------------
blend().setFactors(glop.blend.src, glop.blend.dst);
+ GL_CHECKPOINT(MODERATE);
+
// ------------------------------------
// ---------- Actual drawing ----------
// ------------------------------------
@@ -320,12 +359,10 @@ void RenderState::render(const Glop& glop) {
GLsizei elementsCount = mesh.elementCount;
const GLbyte* vertexData = static_cast<const GLbyte*>(vertices.position);
while (elementsCount > 0) {
- GLsizei drawCount = MathUtils::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6);
-
- // rebind pointers without forcing, since initial bind handled above
- meshState().bindPositionVertexPointer(false, vertexData, vertices.stride);
+ GLsizei drawCount = std::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6);
+ meshState().bindPositionVertexPointer(vertexData, vertices.stride);
if (vertices.attribFlags & VertexAttribFlags::TextureCoord) {
- meshState().bindTexCoordsVertexPointer(false,
+ meshState().bindTexCoordsVertexPointer(
vertexData + kMeshTextureOffset, vertices.stride);
}
@@ -339,6 +376,8 @@ void RenderState::render(const Glop& glop) {
glDrawArrays(mesh.primitiveMode, 0, mesh.elementCount);
}
+ GL_CHECKPOINT(MODERATE);
+
// -----------------------------------
// ---------- Mesh teardown ----------
// -----------------------------------
@@ -348,6 +387,8 @@ void RenderState::render(const Glop& glop) {
if (vertices.attribFlags & VertexAttribFlags::Color) {
glDisableVertexAttribArray(colorLocation);
}
+
+ GL_CHECKPOINT(MODERATE);
}
void RenderState::dump() {
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 4fd792c1b503..731d9bbb3355 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -16,24 +16,26 @@
#ifndef RENDERSTATE_H
#define RENDERSTATE_H
-#include <set>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <utils/Mutex.h>
-#include <utils/Functor.h>
-#include <utils/RefBase.h>
-#include <private/hwui/DrawGlInfo.h>
-#include <renderstate/Blend.h>
-
#include "AssetAtlas.h"
#include "Caches.h"
#include "Glop.h"
+#include "renderstate/Blend.h"
#include "renderstate/MeshState.h"
+#include "renderstate/OffscreenBufferPool.h"
#include "renderstate/PixelBufferState.h"
#include "renderstate/Scissor.h"
#include "renderstate/Stencil.h"
#include "utils/Macros.h"
+#include <set>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <ui/Region.h>
+#include <utils/Mutex.h>
+#include <utils/Functor.h>
+#include <utils/RefBase.h>
+#include <private/hwui/DrawGlInfo.h>
+
namespace android {
namespace uirenderer {
@@ -49,15 +51,21 @@ class RenderThread;
// wrapper of Caches for users to migrate to.
class RenderState {
PREVENT_COPY_AND_ASSIGN(RenderState);
+ friend class renderthread::RenderThread;
+ friend class Caches;
public:
void onGLContextCreated();
void onGLContextDestroyed();
+ void flush(Caches::FlushMode flushMode);
+
void setViewport(GLsizei width, GLsizei height);
void getViewport(GLsizei* outWidth, GLsizei* outHeight);
void bindFramebuffer(GLuint fbo);
- GLint getFramebuffer() { return mFramebuffer; }
+ GLuint getFramebuffer() { return mFramebuffer; }
+ GLuint createFramebuffer();
+ void deleteFramebuffer(GLuint fbo);
void invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info);
@@ -78,13 +86,11 @@ public:
mRegisteredContexts.erase(context);
}
- void requireGLContext();
-
// TODO: This system is a little clunky feeling, this could use some
// more thinking...
void postDecStrong(VirtualLightRefBase* object);
- void render(const Glop& glop);
+ void render(const Glop& glop, const Matrix4& orthoMatrix);
AssetAtlas& assetAtlas() { return mAssetAtlas; }
Blend& blend() { return *mBlend; }
@@ -92,14 +98,13 @@ public:
Scissor& scissor() { return *mScissor; }
Stencil& stencil() { return *mStencil; }
+ OffscreenBufferPool& layerPool() { return mLayerPool; }
+
void dump();
-private:
- friend class renderthread::RenderThread;
- friend class Caches;
+private:
void interruptForFunctorInvoke();
void resumeFromFunctorInvoke();
- void assertOnGLThread();
RenderState(renderthread::RenderThread& thread);
~RenderState();
@@ -113,6 +118,8 @@ private:
Scissor* mScissor = nullptr;
Stencil* mStencil = nullptr;
+ OffscreenBufferPool mLayerPool;
+
AssetAtlas mAssetAtlas;
std::set<Layer*> mActiveLayers;
std::set<renderthread::CanvasContext*> mRegisteredContexts;
diff --git a/libs/hwui/renderstate/Scissor.cpp b/libs/hwui/renderstate/Scissor.cpp
index 95dcd18867d9..61dd8c3200a4 100644
--- a/libs/hwui/renderstate/Scissor.cpp
+++ b/libs/hwui/renderstate/Scissor.cpp
@@ -15,6 +15,8 @@
*/
#include "renderstate/Scissor.h"
+#include "Rect.h"
+
#include <utils/Log.h>
namespace android {
@@ -71,6 +73,26 @@ bool Scissor::set(GLint x, GLint y, GLint width, GLint height) {
return false;
}
+void Scissor::set(int viewportHeight, const Rect& clip) {
+ // transform to Y-flipped GL space, and prevent negatives
+ GLint x = std::max(0, (int)clip.left);
+ GLint y = std::max(0, viewportHeight - (int)clip.bottom);
+ GLint width = std::max(0, ((int)clip.right) - x);
+ GLint height = std::max(0, (viewportHeight - (int)clip.top) - y);
+
+ if (x != mScissorX
+ || y != mScissorY
+ || width != mScissorWidth
+ || height != mScissorHeight) {
+ glScissor(x, y, width, height);
+
+ mScissorX = x;
+ mScissorY = y;
+ mScissorWidth = width;
+ mScissorHeight = height;
+ }
+}
+
void Scissor::reset() {
mScissorX = mScissorY = mScissorWidth = mScissorHeight = 0;
}
diff --git a/libs/hwui/renderstate/Scissor.h b/libs/hwui/renderstate/Scissor.h
index b37ec583686f..f30224470059 100644
--- a/libs/hwui/renderstate/Scissor.h
+++ b/libs/hwui/renderstate/Scissor.h
@@ -22,11 +22,14 @@
namespace android {
namespace uirenderer {
+class Rect;
+
class Scissor {
friend class RenderState;
public:
bool setEnabled(bool enabled);
bool set(GLint x, GLint y, GLint width, GLint height);
+ void set(int viewportHeight, const Rect& clip);
void reset();
bool isEnabled() { return mEnabled; }
void dump();
diff --git a/libs/hwui/renderstate/Stencil.cpp b/libs/hwui/renderstate/Stencil.cpp
index 319cfe4ba0d0..d25ad514e892 100644
--- a/libs/hwui/renderstate/Stencil.cpp
+++ b/libs/hwui/renderstate/Stencil.cpp
@@ -34,10 +34,6 @@ namespace uirenderer {
#define STENCIL_MASK_VALUE 0x1
#endif
-Stencil::Stencil()
- : mState(kDisabled) {
-}
-
uint8_t Stencil::getStencilSize() {
return STENCIL_BUFFER_SIZE;
}
@@ -64,14 +60,14 @@ void Stencil::clear() {
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
- if (mState == kTest) {
+ if (mState == StencilState::Test) {
// reset to test state, with immutable stencil
glStencilMask(0);
}
}
void Stencil::enableTest(int incrementThreshold) {
- if (mState != kTest) {
+ if (mState != StencilState::Test) {
enable();
if (incrementThreshold > 0) {
glStencilFunc(GL_EQUAL, incrementThreshold, 0xff);
@@ -82,12 +78,12 @@ void Stencil::enableTest(int incrementThreshold) {
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glStencilMask(0);
- mState = kTest;
+ mState = StencilState::Test;
}
}
void Stencil::enableWrite(int incrementThreshold) {
- if (mState != kWrite) {
+ if (mState != StencilState::Write) {
enable();
if (incrementThreshold > 0) {
glStencilFunc(GL_ALWAYS, 1, 0xff);
@@ -100,7 +96,7 @@ void Stencil::enableWrite(int incrementThreshold) {
}
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glStencilMask(0xff);
- mState = kWrite;
+ mState = StencilState::Write;
}
}
@@ -109,7 +105,7 @@ void Stencil::enableDebugTest(GLint value, bool greater) {
glStencilFunc(greater ? GL_LESS : GL_EQUAL, value, 0xffffffff);
// We only want to test, let's keep everything
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
- mState = kTest;
+ mState = StencilState::Test;
glStencilMask(0);
}
@@ -119,20 +115,20 @@ void Stencil::enableDebugWrite() {
// The test always passes so the first two values are meaningless
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
- mState = kWrite;
+ mState = StencilState::Write;
glStencilMask(0xff);
}
void Stencil::enable() {
- if (mState == kDisabled) {
+ if (mState == StencilState::Disabled) {
glEnable(GL_STENCIL_TEST);
}
}
void Stencil::disable() {
- if (mState != kDisabled) {
+ if (mState != StencilState::Disabled) {
glDisable(GL_STENCIL_TEST);
- mState = kDisabled;
+ mState = StencilState::Disabled;
}
}
diff --git a/libs/hwui/renderstate/Stencil.h b/libs/hwui/renderstate/Stencil.h
index 3a8f8ebad48d..5f7d4056b51d 100644
--- a/libs/hwui/renderstate/Stencil.h
+++ b/libs/hwui/renderstate/Stencil.h
@@ -17,10 +17,6 @@
#ifndef ANDROID_HWUI_STENCIL_H
#define ANDROID_HWUI_STENCIL_H
-#ifndef LOG_TAG
- #define LOG_TAG "OpenGLRenderer"
-#endif
-
#include <GLES2/gl2.h>
#include <cutils/compiler.h>
@@ -34,8 +30,6 @@ namespace uirenderer {
class ANDROID_API Stencil {
public:
- Stencil();
-
/**
* Returns the desired size for the stencil buffer. If the returned value
* is 0, then no stencil buffer is required.
@@ -85,32 +79,31 @@ public:
* Indicates whether either test or write is enabled.
*/
bool isEnabled() {
- return mState != kDisabled;
+ return mState != StencilState::Disabled;
}
/**
* Indicates whether testing only is enabled.
*/
bool isTestEnabled() {
- return mState == kTest;
+ return mState == StencilState::Test;
}
bool isWriteEnabled() {
- return mState == kWrite;
+ return mState == StencilState::Write;
}
void dump();
private:
- void enable();
-
- enum StencilState {
- kDisabled,
- kTest,
- kWrite
+ enum class StencilState {
+ Disabled,
+ Test,
+ Write
};
- StencilState mState;
+ void enable();
+ StencilState mState = StencilState::Disabled;
}; // class Stencil
diff --git a/libs/hwui/renderstate/TextureState.cpp b/libs/hwui/renderstate/TextureState.cpp
index 987d4cd55a5e..78b8edae2eed 100644
--- a/libs/hwui/renderstate/TextureState.cpp
+++ b/libs/hwui/renderstate/TextureState.cpp
@@ -15,6 +15,14 @@
*/
#include "renderstate/TextureState.h"
+#include "Caches.h"
+#include "utils/TraceUtils.h"
+
+#include <GLES3/gl3.h>
+#include <memory>
+#include <SkCanvas.h>
+#include <SkBitmap.h>
+
namespace android {
namespace uirenderer {
@@ -35,6 +43,7 @@ TextureState::TextureState()
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
LOG_ALWAYS_FATAL_IF(maxTextureUnits < kTextureUnitsCount,
"At least %d texture units are required!", kTextureUnitsCount);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
}
void TextureState::activateTexture(GLuint textureUnit) {
diff --git a/libs/hwui/renderstate/TextureState.h b/libs/hwui/renderstate/TextureState.h
index d3c014c00bce..ec94d7e9e267 100644
--- a/libs/hwui/renderstate/TextureState.h
+++ b/libs/hwui/renderstate/TextureState.h
@@ -23,9 +23,13 @@
#include <SkXfermode.h>
#include <memory>
+class SkBitmap;
+
namespace android {
namespace uirenderer {
+class Texture;
+
class TextureState {
friend class Caches; // TODO: move to RenderState
public:
@@ -71,6 +75,7 @@ public:
* Clear the cache of bound textures.
*/
void unbindTexture(GLuint texture);
+
private:
// total number of texture units available for use
static const int kTextureUnitsCount = 4;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 4cf8b152ed40..e6399d4ec789 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -14,27 +14,49 @@
* limitations under the License.
*/
+#include <GpuMemoryTracker.h>
#include "CanvasContext.h"
#include "AnimationContext.h"
#include "Caches.h"
#include "DeferredLayerUpdater.h"
#include "EglManager.h"
+#include "LayerUpdateQueue.h"
#include "LayerRenderer.h"
#include "OpenGLRenderer.h"
#include "Properties.h"
#include "RenderThread.h"
+#include "hwui/Canvas.h"
#include "renderstate/RenderState.h"
#include "renderstate/Stencil.h"
+#include "protos/hwui.pb.h"
+#include "utils/GLUtils.h"
+#include "utils/TimeUtils.h"
-#include <algorithm>
-#include <strings.h>
#include <cutils/properties.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <private/hwui/DrawGlInfo.h>
+#include <strings.h>
+
+#include <algorithm>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include <cstdlib>
#define TRIM_MEMORY_COMPLETE 80
#define TRIM_MEMORY_UI_HIDDEN 20
+#define ENABLE_RENDERNODE_SERIALIZATION false
+
+#define LOG_FRAMETIME_MMA 0
+
+#if LOG_FRAMETIME_MMA
+static float sBenchMma = 0;
+static int sFrameCount = 0;
+static const float NANOS_PER_MILLIS_F = 1000000.0f;
+#endif
+
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -45,104 +67,121 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent,
, mEglManager(thread.eglManager())
, mOpaque(!translucent)
, mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
- , mRootRenderNode(rootRenderNode)
, mJankTracker(thread.timeLord().frameIntervalNanos())
- , mProfiler(mFrames) {
+ , mProfiler(mFrames)
+ , mContentDrawBounds(0, 0, 0, 0) {
+ mRenderNodes.emplace_back(rootRenderNode);
mRenderThread.renderState().registerCanvasContext(this);
mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
}
CanvasContext::~CanvasContext() {
- destroy();
+ destroy(nullptr);
mRenderThread.renderState().unregisterCanvasContext(this);
}
-void CanvasContext::destroy() {
+void CanvasContext::destroy(TreeObserver* observer) {
stopDrawing();
setSurface(nullptr);
- freePrefetechedLayers();
- destroyHardwareResources();
+ freePrefetchedLayers(observer);
+ destroyHardwareResources(observer);
mAnimationContext->destroy();
+#if !HWUI_NEW_OPS
if (mCanvas) {
delete mCanvas;
mCanvas = nullptr;
}
+#endif
}
-void CanvasContext::setSurface(ANativeWindow* window) {
+void CanvasContext::setSurface(Surface* surface) {
ATRACE_CALL();
- mNativeWindow = window;
+ mNativeSurface = surface;
if (mEglSurface != EGL_NO_SURFACE) {
mEglManager.destroySurface(mEglSurface);
mEglSurface = EGL_NO_SURFACE;
}
- if (window) {
- mEglSurface = mEglManager.createSurface(window);
+ if (surface) {
+ mEglSurface = mEglManager.createSurface(surface);
}
+ mFrameNumber = -1;
+
if (mEglSurface != EGL_NO_SURFACE) {
const bool preserveBuffer = (mSwapBehavior != kSwap_discardBuffer);
mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
mHaveNewSurface = true;
- makeCurrent();
+ mSwapHistory.clear();
} else {
mRenderThread.removeFrameCallback(this);
}
}
-void CanvasContext::swapBuffers(const SkRect& dirty, EGLint width, EGLint height) {
- if (CC_UNLIKELY(!mEglManager.swapBuffers(mEglSurface, dirty, width, height))) {
- setSurface(nullptr);
- }
- mHaveNewSurface = false;
-}
-
-void CanvasContext::requireSurface() {
- LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
- "requireSurface() called but no surface set!");
- makeCurrent();
-}
-
void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) {
mSwapBehavior = swapBehavior;
}
-bool CanvasContext::initialize(ANativeWindow* window) {
- setSurface(window);
- if (mCanvas) return false;
+void CanvasContext::initialize(Surface* surface) {
+ setSurface(surface);
+#if !HWUI_NEW_OPS
+ if (mCanvas) return;
mCanvas = new OpenGLRenderer(mRenderThread.renderState());
mCanvas->initProperties();
- return true;
+#endif
}
-void CanvasContext::updateSurface(ANativeWindow* window) {
- setSurface(window);
+void CanvasContext::updateSurface(Surface* surface) {
+ setSurface(surface);
}
-bool CanvasContext::pauseSurface(ANativeWindow* window) {
+bool CanvasContext::pauseSurface(Surface* surface) {
return mRenderThread.removeFrameCallback(this);
}
+void CanvasContext::setStopped(bool stopped) {
+ if (mStopped != stopped) {
+ mStopped = stopped;
+ if (mStopped) {
+ mRenderThread.removeFrameCallback(this);
+ if (mEglManager.isCurrent(mEglSurface)) {
+ mEglManager.makeCurrent(EGL_NO_SURFACE);
+ }
+ }
+ }
+}
+
// TODO: don't pass viewport size, it's automatic via EGL
void CanvasContext::setup(int width, int height, float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+#if HWUI_NEW_OPS
+ mLightGeometry.radius = lightRadius;
+ mLightInfo.ambientShadowAlpha = ambientShadowAlpha;
+ mLightInfo.spotShadowAlpha = spotShadowAlpha;
+#else
if (!mCanvas) return;
mCanvas->initLight(lightRadius, ambientShadowAlpha, spotShadowAlpha);
+#endif
}
void CanvasContext::setLightCenter(const Vector3& lightCenter) {
+#if HWUI_NEW_OPS
+ mLightGeometry.center = lightCenter;
+#else
if (!mCanvas) return;
mCanvas->setLightCenter(lightCenter);
+#endif
}
void CanvasContext::setOpaque(bool opaque) {
mOpaque = opaque;
}
-void CanvasContext::makeCurrent() {
+bool CanvasContext::makeCurrent() {
+ if (mStopped) return false;
+
// TODO: Figure out why this workaround is needed, see b/13913604
// In the meantime this matches the behavior of GLRenderer, so it is not a regression
EGLint error = 0;
@@ -150,21 +189,15 @@ void CanvasContext::makeCurrent() {
if (error) {
setSurface(nullptr);
}
-}
-
-void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
- bool success = layerUpdater->apply();
- LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!");
- if (layerUpdater->backingLayer()->deferredUpdateScheduled) {
- mCanvas->pushLayerUpdate(layerUpdater->backingLayer());
- }
+ return !error;
}
static bool wasSkipped(FrameInfo* info) {
return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame);
}
-void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued) {
+void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
+ int64_t syncQueued, RenderNode* target) {
mRenderThread.removeFrameCallback(this);
// If the previous frame was dropped we don't need to hold onto it, so
@@ -177,28 +210,58 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
mCurrentFrameInfo->markSyncStart();
info.damageAccumulator = &mDamageAccumulator;
+#if HWUI_NEW_OPS
+ info.layerUpdateQueue = &mLayerUpdateQueue;
+#else
info.renderer = mCanvas;
- info.canvasContext = this;
+#endif
mAnimationContext->startFrame(info.mode);
- mRootRenderNode->prepareTree(info);
+ for (const sp<RenderNode>& node : mRenderNodes) {
+ // Only the primary target node will be drawn full - all other nodes would get drawn in
+ // real time mode. In case of a window, the primary node is the window content and the other
+ // node(s) are non client / filler nodes.
+ info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY);
+ node->prepareTree(info);
+ GL_CHECKPOINT(MODERATE);
+ }
mAnimationContext->runRemainingAnimations(info);
+ GL_CHECKPOINT(MODERATE);
- freePrefetechedLayers();
+ freePrefetchedLayers(info.observer);
+ GL_CHECKPOINT(MODERATE);
- if (CC_UNLIKELY(!mNativeWindow.get())) {
+ if (CC_UNLIKELY(!mNativeSurface.get())) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
info.out.canDrawThisFrame = false;
return;
}
- int runningBehind = 0;
- // TODO: This query is moderately expensive, investigate adding some sort
- // of fast-path based off when we last called eglSwapBuffers() as well as
- // last vsync time. Or something.
- mNativeWindow->query(mNativeWindow.get(),
- NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
- info.out.canDrawThisFrame = !runningBehind;
+ if (CC_LIKELY(mSwapHistory.size())) {
+ nsecs_t latestVsync = mRenderThread.timeLord().latestVsync();
+ const SwapHistory& lastSwap = mSwapHistory.back();
+ nsecs_t vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync);
+ // The slight fudge-factor is to deal with cases where
+ // the vsync was estimated due to being slow handling the signal.
+ // See the logic in TimeLord#computeFrameTimeNanos or in
+ // Choreographer.java for details on when this happens
+ if (vsyncDelta < 2_ms) {
+ // Already drew for this vsync pulse, UI draw request missed
+ // the deadline for RT animations
+ info.out.canDrawThisFrame = false;
+ } else if (lastSwap.swapTime < latestVsync) {
+ info.out.canDrawThisFrame = true;
+ } else {
+ // We're maybe behind? Find out for sure
+ int runningBehind = 0;
+ // TODO: Have this method be on Surface, too, not just ANativeWindow...
+ ANativeWindow* window = mNativeSurface.get();
+ window->query(window, NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
+ info.out.canDrawThisFrame = !runningBehind;
+ }
+ } else {
+ info.out.canDrawThisFrame = true;
+ }
if (!info.out.canDrawThisFrame) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
@@ -223,8 +286,10 @@ void CanvasContext::notifyFramePending() {
}
void CanvasContext::draw() {
+#if !HWUI_NEW_OPS
LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
"drawRenderNode called on a context with no canvas or surface!");
+#endif
SkRect dirty;
mDamageAccumulator.finish(&dirty);
@@ -237,56 +302,258 @@ void CanvasContext::draw() {
mCurrentFrameInfo->markIssueDrawCommandsStart();
- EGLint width, height;
- mEglManager.beginFrame(mEglSurface, &width, &height);
- if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
- mCanvas->setViewport(width, height);
+ Frame frame = mEglManager.beginFrame(mEglSurface);
+
+ if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) {
+ // can't rely on prior content of window if viewport size changes
dirty.setEmpty();
- } else if (!mBufferPreserved || mHaveNewSurface) {
+ mLastFrameWidth = frame.width();
+ mLastFrameHeight = frame.height();
+ } else if (mHaveNewSurface || frame.bufferAge() == 0) {
+ // New surface needs a full draw
dirty.setEmpty();
} else {
- if (!dirty.isEmpty() && !dirty.intersect(0, 0, width, height)) {
+ if (!dirty.isEmpty() && !dirty.intersect(0, 0, frame.width(), frame.height())) {
ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?",
- SK_RECT_ARGS(dirty), width, height);
+ SK_RECT_ARGS(dirty), frame.width(), frame.height());
dirty.setEmpty();
}
profiler().unionDirty(&dirty);
}
- if (!dirty.isEmpty()) {
- mCanvas->prepareDirty(dirty.fLeft, dirty.fTop,
- dirty.fRight, dirty.fBottom, mOpaque);
- } else {
- mCanvas->prepare(mOpaque);
+ if (dirty.isEmpty()) {
+ dirty.set(0, 0, frame.width(), frame.height());
}
+ // At this point dirty is the area of the screen to update. However,
+ // the area of the frame we need to repaint is potentially different, so
+ // stash the screen area for later
+ SkRect screenDirty(dirty);
+
+ // If the buffer age is 0 we do a full-screen repaint (handled above)
+ // If the buffer age is 1 the buffer contents are the same as they were
+ // last frame so there's nothing to union() against
+ // Therefore we only care about the > 1 case.
+ if (frame.bufferAge() > 1) {
+ if (frame.bufferAge() > (int) mSwapHistory.size()) {
+ // We don't have enough history to handle this old of a buffer
+ // Just do a full-draw
+ dirty.set(0, 0, frame.width(), frame.height());
+ } else {
+ // At this point we haven't yet added the latest frame
+ // to the damage history (happens below)
+ // So we need to damage
+ for (int i = mSwapHistory.size() - 1;
+ i > ((int) mSwapHistory.size()) - frame.bufferAge(); i--) {
+ dirty.join(mSwapHistory[i].damage);
+ }
+ }
+ }
+
+ mEglManager.damageFrame(frame, dirty);
+
+#if HWUI_NEW_OPS
+ auto& caches = Caches::getInstance();
+ FrameBuilder frameBuilder(dirty, frame.width(), frame.height(), mLightGeometry, caches);
+
+ frameBuilder.deferLayers(mLayerUpdateQueue);
+ mLayerUpdateQueue.clear();
+
+ frameBuilder.deferRenderNodeScene(mRenderNodes, mContentDrawBounds);
+
+ BakedOpRenderer renderer(caches, mRenderThread.renderState(),
+ mOpaque, mLightInfo);
+ frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
+ profiler().draw(&renderer);
+ bool drew = renderer.didDraw();
+
+ // post frame cleanup
+ caches.clearGarbage();
+ caches.pathCache.trim();
+ caches.tessellationCache.trim();
+
+#if DEBUG_MEMORY_USAGE
+ mCaches.dumpMemoryUsage();
+#else
+ if (CC_UNLIKELY(Properties::debugLevel & kDebugMemory)) {
+ caches.dumpMemoryUsage();
+ }
+#endif
+
+#else
+ mCanvas->prepareDirty(frame.width(), frame.height(),
+ dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom, mOpaque);
+
Rect outBounds;
- mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
+ // It there are multiple render nodes, they are laid out as follows:
+ // #0 - backdrop (content + caption)
+ // #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds)
+ // #2 - additional overlay nodes
+ // Usually the backdrop cannot be seen since it will be entirely covered by the content. While
+ // resizing however it might become partially visible. The following render loop will crop the
+ // backdrop against the content and draw the remaining part of it. It will then draw the content
+ // cropped to the backdrop (since that indicates a shrinking of the window).
+ //
+ // Additional nodes will be drawn on top with no particular clipping semantics.
+
+ // The bounds of the backdrop against which the content should be clipped.
+ Rect backdropBounds = mContentDrawBounds;
+ // Usually the contents bounds should be mContentDrawBounds - however - we will
+ // move it towards the fixed edge to give it a more stable appearance (for the moment).
+ Rect contentBounds;
+ // If there is no content bounds we ignore the layering as stated above and start with 2.
+ int layer = (mContentDrawBounds.isEmpty() || mRenderNodes.size() == 1) ? 2 : 0;
+ // Draw all render nodes. Note that
+ for (const sp<RenderNode>& node : mRenderNodes) {
+ if (layer == 0) { // Backdrop.
+ // Draw the backdrop clipped to the inverse content bounds, but assume that the content
+ // was moved to the upper left corner.
+ const RenderProperties& properties = node->properties();
+ Rect targetBounds(properties.getLeft(), properties.getTop(),
+ properties.getRight(), properties.getBottom());
+ // Move the content bounds towards the fixed corner of the backdrop.
+ const int x = targetBounds.left;
+ const int y = targetBounds.top;
+ contentBounds.set(x, y, x + mContentDrawBounds.getWidth(),
+ y + mContentDrawBounds.getHeight());
+ // Remember the intersection of the target bounds and the intersection bounds against
+ // which we have to crop the content.
+ backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight());
+ backdropBounds.doIntersect(targetBounds);
+ // Check if we have to draw something on the left side ...
+ if (targetBounds.left < contentBounds.left) {
+ mCanvas->save(SaveFlags::Clip);
+ if (mCanvas->clipRect(targetBounds.left, targetBounds.top,
+ contentBounds.left, targetBounds.bottom,
+ SkRegion::kIntersect_Op)) {
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ // Reduce the target area by the area we have just painted.
+ targetBounds.left = std::min(contentBounds.left, targetBounds.right);
+ mCanvas->restore();
+ }
+ // ... or on the right side ...
+ if (targetBounds.right > contentBounds.right &&
+ !targetBounds.isEmpty()) {
+ mCanvas->save(SaveFlags::Clip);
+ if (mCanvas->clipRect(contentBounds.right, targetBounds.top,
+ targetBounds.right, targetBounds.bottom,
+ SkRegion::kIntersect_Op)) {
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ // Reduce the target area by the area we have just painted.
+ targetBounds.right = std::max(targetBounds.left, contentBounds.right);
+ mCanvas->restore();
+ }
+ // ... or at the top ...
+ if (targetBounds.top < contentBounds.top &&
+ !targetBounds.isEmpty()) {
+ mCanvas->save(SaveFlags::Clip);
+ if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right,
+ contentBounds.top,
+ SkRegion::kIntersect_Op)) {
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ // Reduce the target area by the area we have just painted.
+ targetBounds.top = std::min(contentBounds.top, targetBounds.bottom);
+ mCanvas->restore();
+ }
+ // ... or at the bottom.
+ if (targetBounds.bottom > contentBounds.bottom &&
+ !targetBounds.isEmpty()) {
+ mCanvas->save(SaveFlags::Clip);
+ if (mCanvas->clipRect(targetBounds.left, contentBounds.bottom, targetBounds.right,
+ targetBounds.bottom, SkRegion::kIntersect_Op)) {
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ mCanvas->restore();
+ }
+ } else if (layer == 1) { // Content
+ // It gets cropped against the bounds of the backdrop to stay inside.
+ mCanvas->save(SaveFlags::MatrixClip);
+
+ // We shift and clip the content to match its final location in the window.
+ const float left = mContentDrawBounds.left;
+ const float top = mContentDrawBounds.top;
+ const float dx = backdropBounds.left - left;
+ const float dy = backdropBounds.top - top;
+ const float width = backdropBounds.getWidth();
+ const float height = backdropBounds.getHeight();
+
+ mCanvas->translate(dx, dy);
+ if (mCanvas->clipRect(left, top, left + width, top + height, SkRegion::kIntersect_Op)) {
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ mCanvas->restore();
+ } else { // draw the rest on top at will!
+ mCanvas->drawRenderNode(node.get(), outBounds);
+ }
+ layer++;
+ }
profiler().draw(mCanvas);
bool drew = mCanvas->finish();
+#endif
+
+ waitOnFences();
+
+ GL_CHECKPOINT(LOW);
// Even if we decided to cancel the frame, from the perspective of jank
// metrics the frame was swapped at this point
mCurrentFrameInfo->markSwapBuffers();
- if (drew) {
- swapBuffers(dirty, width, height);
+ if (drew || mEglManager.damageRequiresSwap()) {
+ if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) {
+ setSurface(nullptr);
+ }
+ SwapHistory& swap = mSwapHistory.next();
+ swap.damage = screenDirty;
+ swap.swapTime = systemTime(CLOCK_MONOTONIC);
+ swap.vsyncTime = mRenderThread.timeLord().latestVsync();
+ mHaveNewSurface = false;
+ mFrameNumber = -1;
}
// TODO: Use a fence for real completion?
mCurrentFrameInfo->markFrameCompleted();
+
+#if LOG_FRAMETIME_MMA
+ float thisFrame = mCurrentFrameInfo->duration(
+ FrameInfoIndex::IssueDrawCommandsStart,
+ FrameInfoIndex::FrameCompleted) / NANOS_PER_MILLIS_F;
+ if (sFrameCount) {
+ sBenchMma = ((9 * sBenchMma) + thisFrame) / 10;
+ } else {
+ sBenchMma = thisFrame;
+ }
+ if (++sFrameCount == 10) {
+ sFrameCount = 1;
+ ALOGD("Average frame time: %.4f", sBenchMma);
+ }
+#endif
+
mJankTracker.addFrame(*mCurrentFrameInfo);
mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
+ if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
+ mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data());
+ }
+
+ GpuMemoryTracker::onFrameCompleted();
}
// Called by choreographer to do an RT-driven animation
void CanvasContext::doFrame() {
- if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) {
- return;
- }
+#if HWUI_NEW_OPS
+ if (CC_UNLIKELY(mEglSurface == EGL_NO_SURFACE)) return;
+#else
+ if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) return;
+#endif
+ prepareAndDraw(nullptr);
+}
+void CanvasContext::prepareAndDraw(RenderNode* node) {
ATRACE_CALL();
nsecs_t vsync = mRenderThread.timeLord().computeFrameTimeNanos();
@@ -295,8 +562,8 @@ void CanvasContext::doFrame() {
.addFlag(FrameInfoFlags::RTAnimation)
.setVsync(vsync, vsync);
- TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
- prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC));
+ TreeInfo info(TreeInfo::MODE_RT_ONLY, *this);
+ prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node);
if (info.out.canDrawThisFrame) {
draw();
}
@@ -313,35 +580,41 @@ void CanvasContext::invokeFunctor(RenderThread& thread, Functor* functor) {
}
void CanvasContext::markLayerInUse(RenderNode* node) {
- if (mPrefetechedLayers.erase(node)) {
+ if (mPrefetchedLayers.erase(node)) {
node->decStrong(nullptr);
}
}
-static void destroyPrefetechedNode(RenderNode* node) {
- ALOGW("Incorrectly called buildLayer on View: %s, destroying layer...", node->getName());
- node->destroyHardwareResources();
- node->decStrong(nullptr);
-}
-
-void CanvasContext::freePrefetechedLayers() {
- if (mPrefetechedLayers.size()) {
- std::for_each(mPrefetechedLayers.begin(), mPrefetechedLayers.end(), destroyPrefetechedNode);
- mPrefetechedLayers.clear();
+void CanvasContext::freePrefetchedLayers(TreeObserver* observer) {
+ if (mPrefetchedLayers.size()) {
+ for (auto& node : mPrefetchedLayers) {
+ ALOGW("Incorrectly called buildLayer on View: %s, destroying layer...",
+ node->getName());
+ node->destroyHardwareResources(observer);
+ node->decStrong(observer);
+ }
+ mPrefetchedLayers.clear();
}
}
-void CanvasContext::buildLayer(RenderNode* node) {
+void CanvasContext::buildLayer(RenderNode* node, TreeObserver* observer) {
ATRACE_CALL();
- if (!mEglManager.hasEglContext() || !mCanvas) {
- return;
- }
+ if (!mEglManager.hasEglContext()) return;
+#if !HWUI_NEW_OPS
+ if (!mCanvas) return;
+#endif
+
// buildLayer() will leave the tree in an unknown state, so we must stop drawing
stopDrawing();
- TreeInfo info(TreeInfo::MODE_FULL, mRenderThread.renderState());
+ TreeInfo info(TreeInfo::MODE_FULL, *this);
info.damageAccumulator = &mDamageAccumulator;
+ info.observer = observer;
+#if HWUI_NEW_OPS
+ info.layerUpdateQueue = &mLayerUpdateQueue;
+#else
info.renderer = mCanvas;
+#endif
info.runAnimations = false;
node->prepareTree(info);
SkRect ignore;
@@ -350,11 +623,22 @@ void CanvasContext::buildLayer(RenderNode* node) {
// purposes when the frame is actually drawn
node->setPropertyFieldsDirty(RenderNode::GENERIC);
+#if HWUI_NEW_OPS
+ static const std::vector< sp<RenderNode> > emptyNodeList;
+ auto& caches = Caches::getInstance();
+ FrameBuilder frameBuilder(mLayerUpdateQueue, mLightGeometry, caches);
+ mLayerUpdateQueue.clear();
+ BakedOpRenderer renderer(caches, mRenderThread.renderState(),
+ mOpaque, mLightInfo);
+ LOG_ALWAYS_FATAL_IF(renderer.didDraw(), "shouldn't draw in buildlayer case");
+ frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
+#else
mCanvas->markLayersAsBuildLayers();
mCanvas->flushLayerUpdates();
+#endif
node->incStrong(nullptr);
- mPrefetechedLayers.insert(node);
+ mPrefetchedLayers.insert(node);
}
bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
@@ -362,16 +646,18 @@ bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap)
return LayerRenderer::copyLayer(mRenderThread.renderState(), layer->backingLayer(), bitmap);
}
-void CanvasContext::destroyHardwareResources() {
+void CanvasContext::destroyHardwareResources(TreeObserver* observer) {
stopDrawing();
if (mEglManager.hasEglContext()) {
- freePrefetechedLayers();
- mRootRenderNode->destroyHardwareResources();
+ freePrefetchedLayers(observer);
+ for (const sp<RenderNode>& node : mRenderNodes) {
+ node->destroyHardwareResources(observer);
+ }
Caches& caches = Caches::getInstance();
// Make sure to release all the textures we were owning as there won't
// be another draw
caches.textureCache.resetMarkInUse(this);
- caches.flush(Caches::kFlushMode_Layers);
+ mRenderThread.renderState().flush(Caches::FlushMode::Layers);
}
}
@@ -381,10 +667,10 @@ void CanvasContext::trimMemory(RenderThread& thread, int level) {
ATRACE_CALL();
if (level >= TRIM_MEMORY_COMPLETE) {
- Caches::getInstance().flush(Caches::kFlushMode_Full);
+ thread.renderState().flush(Caches::FlushMode::Full);
thread.eglManager().destroy();
} else if (level >= TRIM_MEMORY_UI_HIDDEN) {
- Caches::getInstance().flush(Caches::kFlushMode_Moderate);
+ thread.renderState().flush(Caches::FlushMode::Moderate);
}
}
@@ -395,7 +681,7 @@ void CanvasContext::runWithGlContext(RenderTask* task) {
}
Layer* CanvasContext::createTextureLayer() {
- requireSurface();
+ mEglManager.initialize();
return LayerRenderer::createTextureLayer(mRenderThread.renderState());
}
@@ -430,6 +716,79 @@ void CanvasContext::resetFrameStats() {
mRenderThread.jankTracker().reset();
}
+void CanvasContext::serializeDisplayListTree() {
+#if ENABLE_RENDERNODE_SERIALIZATION
+ using namespace google::protobuf::io;
+ char package[128];
+ // Check whether tracing is enabled for this process.
+ FILE * file = fopen("/proc/self/cmdline", "r");
+ if (file) {
+ if (!fgets(package, 128, file)) {
+ ALOGE("Error reading cmdline: %s (%d)", strerror(errno), errno);
+ fclose(file);
+ return;
+ }
+ fclose(file);
+ } else {
+ ALOGE("Error opening /proc/self/cmdline: %s (%d)", strerror(errno),
+ errno);
+ return;
+ }
+ char path[1024];
+ snprintf(path, 1024, "/data/data/%s/cache/rendertree_dump", package);
+ int fd = open(path, O_CREAT | O_WRONLY, S_IRWXU | S_IRGRP | S_IROTH);
+ if (fd == -1) {
+ ALOGD("Failed to open '%s'", path);
+ return;
+ }
+ proto::RenderNode tree;
+ // TODO: Streaming writes?
+ mRootRenderNode->copyTo(&tree);
+ std::string data = tree.SerializeAsString();
+ write(fd, data.c_str(), data.length());
+ close(fd);
+#endif
+}
+
+void CanvasContext::waitOnFences() {
+ if (mFrameFences.size()) {
+ ATRACE_CALL();
+ for (auto& fence : mFrameFences) {
+ fence->getResult();
+ }
+ mFrameFences.clear();
+ }
+}
+
+class CanvasContext::FuncTaskProcessor : public TaskProcessor<bool> {
+public:
+ FuncTaskProcessor(Caches& caches)
+ : TaskProcessor<bool>(&caches.tasks) {}
+
+ virtual void onProcess(const sp<Task<bool> >& task) override {
+ FuncTask* t = static_cast<FuncTask*>(task.get());
+ t->func();
+ task->setResult(true);
+ }
+};
+
+void CanvasContext::enqueueFrameWork(std::function<void()>&& func) {
+ if (!mFrameWorkProcessor.get()) {
+ mFrameWorkProcessor = new FuncTaskProcessor(Caches::getInstance());
+ }
+ sp<FuncTask> task(new FuncTask());
+ task->func = func;
+ mFrameWorkProcessor->add(task);
+}
+
+int64_t CanvasContext::getFrameNumber() {
+ // mFrameNumber is reset to -1 when the surface changes or we swap buffers
+ if (mFrameNumber == -1 && mNativeSurface.get()) {
+ mFrameNumber = static_cast<int64_t>(mNativeSurface->getNextFrameNumber());
+ }
+ return mFrameNumber;
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index f2fa9cdcbefd..e739b2949cf9 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -18,23 +18,35 @@
#define CANVASCONTEXT_H_
#include "DamageAccumulator.h"
-#include "IContextFactory.h"
#include "FrameInfo.h"
#include "FrameInfoVisualizer.h"
+#include "FrameMetricsReporter.h"
+#include "IContextFactory.h"
+#include "LayerUpdateQueue.h"
#include "RenderNode.h"
+#include "thread/Task.h"
+#include "thread/TaskProcessor.h"
#include "utils/RingBuffer.h"
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
+#if HWUI_NEW_OPS
+#include "BakedOpDispatcher.h"
+#include "BakedOpRenderer.h"
+#include "FrameBuilder.h"
+#endif
+
#include <cutils/compiler.h>
#include <EGL/egl.h>
#include <SkBitmap.h>
#include <SkRect.h>
#include <utils/Functor.h>
-#include <utils/Vector.h>
+#include <gui/Surface.h>
+#include <functional>
#include <set>
#include <string>
+#include <vector>
namespace android {
namespace uirenderer {
@@ -67,29 +79,31 @@ public:
// Won't take effect until next EGLSurface creation
void setSwapBehavior(SwapBehavior swapBehavior);
- bool initialize(ANativeWindow* window);
- void updateSurface(ANativeWindow* window);
- bool pauseSurface(ANativeWindow* window);
- bool hasSurface() { return mNativeWindow.get(); }
+ void initialize(Surface* surface);
+ void updateSurface(Surface* surface);
+ bool pauseSurface(Surface* surface);
+ void setStopped(bool stopped);
+ bool hasSurface() { return mNativeSurface.get(); }
void setup(int width, int height, float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
void setLightCenter(const Vector3& lightCenter);
void setOpaque(bool opaque);
- void makeCurrent();
- void processLayerUpdate(DeferredLayerUpdater* layerUpdater);
- void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued);
+ bool makeCurrent();
+ void prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
+ int64_t syncQueued, RenderNode* target);
void draw();
- void destroy();
+ void destroy(TreeObserver* observer);
- // IFrameCallback, Chroreographer-driven frame callback entry point
+ // IFrameCallback, Choreographer-driven frame callback entry point
virtual void doFrame() override;
+ void prepareAndDraw(RenderNode* node);
- void buildLayer(RenderNode* node);
+ void buildLayer(RenderNode* node, TreeObserver* observer);
bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
void markLayerInUse(RenderNode* node);
- void destroyHardwareResources();
+ void destroyHardwareResources(TreeObserver* observer);
static void trimMemory(RenderThread& thread, int level);
static void invokeFunctor(RenderThread& thread, Functor* functor);
@@ -112,32 +126,93 @@ public:
void setName(const std::string&& name) { mName = name; }
const std::string& name() { return mName; }
+ void serializeDisplayListTree();
+
+ void addRenderNode(RenderNode* node, bool placeFront) {
+ int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
+ mRenderNodes.emplace(mRenderNodes.begin() + pos, node);
+ }
+
+ void removeRenderNode(RenderNode* node) {
+ mRenderNodes.erase(std::remove(mRenderNodes.begin(), mRenderNodes.end(), node),
+ mRenderNodes.end());
+ }
+
+ void setContentDrawBounds(int left, int top, int right, int bottom) {
+ mContentDrawBounds.set(left, top, right, bottom);
+ }
+
+ RenderState& getRenderState() {
+ return mRenderThread.renderState();
+ }
+
+ void addFrameMetricsObserver(FrameMetricsObserver* observer) {
+ if (mFrameMetricsReporter.get() == nullptr) {
+ mFrameMetricsReporter.reset(new FrameMetricsReporter());
+ }
+
+ mFrameMetricsReporter->addObserver(observer);
+ }
+
+ void removeFrameMetricsObserver(FrameMetricsObserver* observer) {
+ if (mFrameMetricsReporter.get() != nullptr) {
+ mFrameMetricsReporter->removeObserver(observer);
+ if (!mFrameMetricsReporter->hasObservers()) {
+ mFrameMetricsReporter.reset(nullptr);
+ }
+ }
+ }
+
+ // Used to queue up work that needs to be completed before this frame completes
+ ANDROID_API void enqueueFrameWork(std::function<void()>&& func);
+
+ ANDROID_API int64_t getFrameNumber();
+
private:
friend class RegisterFrameCallbackTask;
// TODO: Replace with something better for layer & other GL object
// lifecycle tracking
friend class android::uirenderer::RenderState;
- void setSurface(ANativeWindow* window);
- void swapBuffers(const SkRect& dirty, EGLint width, EGLint height);
- void requireSurface();
+ void setSurface(Surface* window);
+
+ void freePrefetchedLayers(TreeObserver* observer);
+
+ void waitOnFences();
- void freePrefetechedLayers();
+ EGLint mLastFrameWidth = 0;
+ EGLint mLastFrameHeight = 0;
RenderThread& mRenderThread;
EglManager& mEglManager;
- sp<ANativeWindow> mNativeWindow;
+ sp<Surface> mNativeSurface;
EGLSurface mEglSurface = EGL_NO_SURFACE;
+ bool mStopped = false;
bool mBufferPreserved = false;
SwapBehavior mSwapBehavior = kSwap_default;
+ struct SwapHistory {
+ SkRect damage;
+ nsecs_t vsyncTime;
+ nsecs_t swapTime;
+ };
+
+ RingBuffer<SwapHistory, 3> mSwapHistory;
+ int64_t mFrameNumber = -1;
bool mOpaque;
+#if HWUI_NEW_OPS
+ BakedOpRenderer::LightInfo mLightInfo;
+ FrameBuilder::LightGeometry mLightGeometry = { {0, 0, 0}, 0 };
+#else
OpenGLRenderer* mCanvas = nullptr;
+#endif
+
bool mHaveNewSurface = false;
DamageAccumulator mDamageAccumulator;
+ LayerUpdateQueue mLayerUpdateQueue;
std::unique_ptr<AnimationContext> mAnimationContext;
- const sp<RenderNode> mRootRenderNode;
+ std::vector< sp<RenderNode> > mRenderNodes;
FrameInfo* mCurrentFrameInfo = nullptr;
// Ring buffer large enough for 2 seconds worth of frames
@@ -145,8 +220,22 @@ private:
std::string mName;
JankTracker mJankTracker;
FrameInfoVisualizer mProfiler;
+ std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter;
+
+ std::set<RenderNode*> mPrefetchedLayers;
+
+ // Stores the bounds of the main content.
+ Rect mContentDrawBounds;
+
+ // TODO: This is really a Task<void> but that doesn't really work
+ // when Future<> expects to be able to get/set a value
+ struct FuncTask : public Task<bool> {
+ std::function<void()> func;
+ };
+ class FuncTaskProcessor;
- std::set<RenderNode*> mPrefetechedLayers;
+ std::vector< sp<FuncTask> > mFrameFences;
+ sp<TaskProcessor<bool> > mFrameWorkProcessor;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index a4ac13bb95a1..c9c07b3df292 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define ATRACE_TAG ATRACE_TAG_VIEW
-
#include "DrawFrameTask.h"
#include <utils/Log.h>
@@ -34,15 +32,17 @@ namespace renderthread {
DrawFrameTask::DrawFrameTask()
: mRenderThread(nullptr)
, mContext(nullptr)
- , mSyncResult(kSync_OK) {
+ , mSyncResult(SyncResult::OK) {
}
DrawFrameTask::~DrawFrameTask() {
}
-void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context) {
+void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context,
+ RenderNode* targetNode) {
mRenderThread = thread;
mContext = context;
+ mTargetNode = targetNode;
}
void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -65,11 +65,12 @@ void DrawFrameTask::removeLayerUpdate(DeferredLayerUpdater* layer) {
}
}
-int DrawFrameTask::drawFrame() {
+int DrawFrameTask::drawFrame(TreeObserver* observer) {
LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
- mSyncResult = kSync_OK;
+ mSyncResult = SyncResult::OK;
mSyncQueued = systemTime(CLOCK_MONOTONIC);
+ mObserver = observer;
postAndWait();
return mSyncResult;
@@ -87,7 +88,8 @@ void DrawFrameTask::run() {
bool canUnblockUiThread;
bool canDrawThisFrame;
{
- TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState());
+ TreeInfo info(TreeInfo::MODE_FULL, *mContext);
+ info.observer = mObserver;
canUnblockUiThread = syncFrameState(info);
canDrawThisFrame = info.out.canDrawThisFrame;
}
@@ -113,24 +115,30 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) {
ATRACE_CALL();
int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
mRenderThread->timeLord().vsyncReceived(vsync);
- mContext->makeCurrent();
+ bool canDraw = mContext->makeCurrent();
Caches::getInstance().textureCache.resetMarkInUse(mContext);
for (size_t i = 0; i < mLayers.size(); i++) {
- mContext->processLayerUpdate(mLayers[i].get());
+ mLayers[i]->apply();
}
mLayers.clear();
- mContext->prepareTree(info, mFrameInfo, mSyncQueued);
+ mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
// This is after the prepareTree so that any pending operations
// (RenderNode tree state, prefetched layers, etc...) will be flushed.
- if (CC_UNLIKELY(!mContext->hasSurface())) {
- mSyncResult |= kSync_LostSurfaceRewardIfFound;
+ if (CC_UNLIKELY(!mContext->hasSurface() || !canDraw)) {
+ if (!mContext->hasSurface()) {
+ mSyncResult |= SyncResult::LostSurfaceRewardIfFound;
+ } else {
+ // If we have a surface but can't draw we must be stopped
+ mSyncResult |= SyncResult::ContextIsStopped;
+ }
+ info.out.canDrawThisFrame = false;
}
if (info.out.hasAnimations) {
if (info.out.requiresUiRedraw) {
- mSyncResult |= kSync_UIRedrawRequired;
+ mSyncResult |= SyncResult::UIRedrawRequired;
}
}
// If prepareTextures is false, we ran out of texture cache space
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index ebefcba9f6a5..c02d376098a6 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -32,7 +32,7 @@ namespace android {
namespace uirenderer {
class DeferredLayerUpdater;
-class DisplayListData;
+class DisplayList;
class RenderNode;
namespace renderthread {
@@ -40,15 +40,18 @@ namespace renderthread {
class CanvasContext;
class RenderThread;
-enum SyncResult {
- kSync_OK = 0,
- kSync_UIRedrawRequired = 1 << 0,
- kSync_LostSurfaceRewardIfFound = 1 << 1,
+namespace SyncResult {
+enum {
+ OK = 0,
+ UIRedrawRequired = 1 << 0,
+ LostSurfaceRewardIfFound = 1 << 1,
+ ContextIsStopped = 1 << 2,
};
+}
/*
* This is a special Super Task. It is re-used multiple times by RenderProxy,
- * and contains state (such as layer updaters & new DisplayListDatas) that is
+ * and contains state (such as layer updaters & new DisplayLists) that is
* tracked across many frames not just a single frame.
* It is the sync-state task, and will kick off the post-sync draw
*/
@@ -57,12 +60,12 @@ public:
DrawFrameTask();
virtual ~DrawFrameTask();
- void setContext(RenderThread* thread, CanvasContext* context);
+ void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
void pushLayerUpdate(DeferredLayerUpdater* layer);
void removeLayerUpdate(DeferredLayerUpdater* layer);
- int drawFrame();
+ int drawFrame(TreeObserver* observer);
int64_t* frameInfo() { return mFrameInfo; }
@@ -78,6 +81,7 @@ private:
RenderThread* mRenderThread;
CanvasContext* mContext;
+ RenderNode* mTargetNode = nullptr;
/*********************************************
* Single frame data
@@ -86,6 +90,7 @@ private:
int mSyncResult;
int64_t mSyncQueued;
+ TreeObserver* mObserver;
int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
};
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index eb332d59fee3..ac6a28fe6289 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -17,19 +17,18 @@
#include "EglManager.h"
#include "Caches.h"
+#include "DeviceInfo.h"
#include "Properties.h"
#include "RenderThread.h"
#include "renderstate/RenderState.h"
-
+#include "utils/StringUtils.h"
#include <cutils/log.h>
#include <cutils/properties.h>
#include <EGL/eglext.h>
+#include <string>
-#define PROPERTY_RENDER_DIRTY_REGIONS "debug.hwui.render_dirty_regions"
#define GLES_VERSION 2
-#define WAIT_FOR_GPU_COMPLETION 0
-
// Android-specific addition that is used to show when frames began in systrace
EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface);
@@ -63,10 +62,27 @@ static const char* egl_error_str() {
return egl_error_str(eglGetError());
}
-static bool load_dirty_regions_property() {
- char buf[PROPERTY_VALUE_MAX];
- int len = property_get(PROPERTY_RENDER_DIRTY_REGIONS, buf, "true");
- return !strncasecmp("true", buf, len);
+static struct {
+ bool bufferAge = false;
+ bool setDamage = false;
+} EglExtensions;
+
+void Frame::map(const SkRect& in, EGLint* out) const {
+ /* The rectangles are specified relative to the bottom-left of the surface
+ * and the x and y components of each rectangle specify the bottom-left
+ * position of that rectangle.
+ *
+ * HWUI does everything with 0,0 being top-left, so need to map
+ * the rect
+ */
+ SkIRect idirty;
+ in.roundOut(&idirty);
+ EGLint y = mHeight - (idirty.y() + idirty.height());
+ // layout: {x, y, width, height}
+ out[0] = idirty.x();
+ out[1] = y;
+ out[2] = idirty.width();
+ out[3] = idirty.height();
}
EglManager::EglManager(RenderThread& thread)
@@ -75,12 +91,9 @@ EglManager::EglManager(RenderThread& thread)
, mEglConfig(nullptr)
, mEglContext(EGL_NO_CONTEXT)
, mPBufferSurface(EGL_NO_SURFACE)
- , mAllowPreserveBuffer(load_dirty_regions_property())
, mCurrentSurface(EGL_NO_SURFACE)
, mAtlasMap(nullptr)
, mAtlasMapSize(0) {
- mCanSetPreserveBuffer = mAllowPreserveBuffer;
- ALOGD("Use EGL_SWAP_BEHAVIOR_PRESERVED: %s", mAllowPreserveBuffer ? "true" : "false");
}
void EglManager::initialize() {
@@ -98,20 +111,43 @@ void EglManager::initialize() {
ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor);
+ initExtensions();
+
+ // Now that extensions are loaded, pick a swap behavior
+ if (Properties::enablePartialUpdates) {
+ if (Properties::useBufferAge && EglExtensions.bufferAge) {
+ mSwapBehavior = SwapBehavior::BufferAge;
+ } else {
+ mSwapBehavior = SwapBehavior::Preserved;
+ }
+ }
+
loadConfig();
createContext();
createPBufferSurface();
makeCurrent(mPBufferSurface);
+ DeviceInfo::initialize();
mRenderThread.renderState().onGLContextCreated();
initAtlas();
}
+void EglManager::initExtensions() {
+ auto extensions = StringUtils::split(
+ eglQueryString(mEglDisplay, EGL_EXTENSIONS));
+ EglExtensions.bufferAge = extensions.has("EGL_EXT_buffer_age");
+ EglExtensions.setDamage = extensions.has("EGL_KHR_partial_update");
+ LOG_ALWAYS_FATAL_IF(!extensions.has("EGL_KHR_swap_buffers_with_damage"),
+ "Missing required extension EGL_KHR_swap_buffers_with_damage");
+}
+
bool EglManager::hasEglContext() {
return mEglDisplay != EGL_NO_DISPLAY;
}
void EglManager::loadConfig() {
- EGLint swapBehavior = mCanSetPreserveBuffer ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+ ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior));
+ EGLint swapBehavior = (mSwapBehavior == SwapBehavior::Preserved)
+ ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
EGLint attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
@@ -128,20 +164,23 @@ void EglManager::loadConfig() {
EGLint num_configs = 1;
if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_configs, &num_configs)
|| num_configs != 1) {
- // Failed to get a valid config
- if (mCanSetPreserveBuffer) {
- ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
+ if (mSwapBehavior == SwapBehavior::Preserved) {
// Try again without dirty regions enabled
- mCanSetPreserveBuffer = false;
+ ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
+ mSwapBehavior = SwapBehavior::Discard;
loadConfig();
} else {
+ // Failed to get a valid config
LOG_ALWAYS_FATAL("Failed to choose config, error = %s", egl_error_str());
}
}
}
void EglManager::createContext() {
- EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION, EGL_NONE };
+ EGLint attribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION,
+ EGL_NONE
+ };
mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attribs);
LOG_ALWAYS_FATAL_IF(mEglContext == EGL_NO_CONTEXT,
"Failed to create context, error = %s", egl_error_str());
@@ -189,6 +228,13 @@ EGLSurface EglManager::createSurface(EGLNativeWindowType window) {
LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
"Failed to create EGLSurface for window %p, eglErr = %s",
(void*) window, egl_error_str());
+
+ if (mSwapBehavior != SwapBehavior::Preserved) {
+ LOG_ALWAYS_FATAL_IF(eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED) == EGL_FALSE,
+ "Failed to set swap behavior to destroyed for window %p, eglErr = %s",
+ (void*) window, egl_error_str());
+ }
+
return surface;
}
@@ -238,64 +284,72 @@ bool EglManager::makeCurrent(EGLSurface surface, EGLint* errOut) {
return true;
}
-void EglManager::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) {
+EGLint EglManager::queryBufferAge(EGLSurface surface) {
+ switch (mSwapBehavior) {
+ case SwapBehavior::Discard:
+ return 0;
+ case SwapBehavior::Preserved:
+ return 1;
+ case SwapBehavior::BufferAge:
+ EGLint bufferAge;
+ eglQuerySurface(mEglDisplay, surface, EGL_BUFFER_AGE_EXT, &bufferAge);
+ return bufferAge;
+ }
+ return 0;
+}
+
+Frame EglManager::beginFrame(EGLSurface surface) {
LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
"Tried to beginFrame on EGL_NO_SURFACE!");
makeCurrent(surface);
- if (width) {
- eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, width);
- }
- if (height) {
- eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height);
- }
+ Frame frame;
+ frame.mSurface = surface;
+ eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, &frame.mWidth);
+ eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, &frame.mHeight);
+ frame.mBufferAge = queryBufferAge(surface);
eglBeginFrame(mEglDisplay, surface);
+ return frame;
}
-bool EglManager::swapBuffers(EGLSurface surface, const SkRect& dirty,
- EGLint width, EGLint height) {
+void EglManager::damageFrame(const Frame& frame, const SkRect& dirty) {
+#ifdef EGL_KHR_partial_update
+ if (EglExtensions.setDamage && mSwapBehavior == SwapBehavior::BufferAge) {
+ EGLint rects[4];
+ frame.map(dirty, rects);
+ if (!eglSetDamageRegionKHR(mEglDisplay, frame.mSurface, rects, 1)) {
+ LOG_ALWAYS_FATAL("Failed to set damage region on surface %p, error=%s",
+ (void*)frame.mSurface, egl_error_str());
+ }
+ }
+#endif
+}
-#if WAIT_FOR_GPU_COMPLETION
- {
+bool EglManager::damageRequiresSwap() {
+ return EglExtensions.setDamage && mSwapBehavior == SwapBehavior::BufferAge;
+}
+
+bool EglManager::swapBuffers(const Frame& frame, const SkRect& screenDirty) {
+
+ if (CC_UNLIKELY(Properties::waitForGpuCompletion)) {
ATRACE_NAME("Finishing GPU work");
fence();
}
-#endif
-#ifdef EGL_KHR_swap_buffers_with_damage
- if (CC_LIKELY(Properties::swapBuffersWithDamage)) {
- SkIRect idirty;
- dirty.roundOut(&idirty);
- /*
- * EGL_KHR_swap_buffers_with_damage spec states:
- *
- * The rectangles are specified relative to the bottom-left of the surface
- * and the x and y components of each rectangle specify the bottom-left
- * position of that rectangle.
- *
- * HWUI does everything with 0,0 being top-left, so need to map
- * the rect
- */
- EGLint y = height - (idirty.y() + idirty.height());
- // layout: {x, y, width, height}
- EGLint rects[4] = { idirty.x(), y, idirty.width(), idirty.height() };
- EGLint numrects = dirty.isEmpty() ? 0 : 1;
- eglSwapBuffersWithDamageKHR(mEglDisplay, surface, rects, numrects);
- } else {
- eglSwapBuffers(mEglDisplay, surface);
- }
-#else
- eglSwapBuffers(mEglDisplay, surface);
-#endif
+ EGLint rects[4];
+ frame.map(screenDirty, rects);
+ eglSwapBuffersWithDamageKHR(mEglDisplay, frame.mSurface, rects,
+ screenDirty.isEmpty() ? 0 : 1);
EGLint err = eglGetError();
if (CC_LIKELY(err == EGL_SUCCESS)) {
return true;
}
- if (err == EGL_BAD_SURFACE) {
+ if (err == EGL_BAD_SURFACE || err == EGL_BAD_NATIVE_WINDOW) {
// For some reason our surface was destroyed out from under us
// This really shouldn't happen, but if it does we can recover easily
// by just not trying to use the surface anymore
- ALOGW("swapBuffers encountered EGL_BAD_SURFACE on %p, halting rendering...", surface);
+ ALOGW("swapBuffers encountered EGL error %d on %p, halting rendering...",
+ err, frame.mSurface);
return false;
}
LOG_ALWAYS_FATAL("Encountered EGL error %d %s during rendering",
@@ -312,18 +366,13 @@ void EglManager::fence() {
}
bool EglManager::setPreserveBuffer(EGLSurface surface, bool preserve) {
- if (CC_UNLIKELY(!mAllowPreserveBuffer)) return false;
-
- bool preserved = false;
- if (mCanSetPreserveBuffer) {
- preserved = eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR,
- preserve ? EGL_BUFFER_PRESERVED : EGL_BUFFER_DESTROYED);
- if (CC_UNLIKELY(!preserved)) {
- ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s",
- (void*) surface, egl_error_str());
- }
- }
- if (CC_UNLIKELY(!preserved)) {
+ if (mSwapBehavior != SwapBehavior::Preserved) return false;
+
+ bool preserved = eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR,
+ preserve ? EGL_BUFFER_PRESERVED : EGL_BUFFER_DESTROYED);
+ if (!preserved) {
+ ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s",
+ (void*) surface, egl_error_str());
// Maybe it's already set?
EGLint swapBehavior;
if (eglQuerySurface(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, &swapBehavior)) {
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 0a8cfd30df71..459baed70e40 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -27,6 +27,29 @@ namespace uirenderer {
namespace renderthread {
class RenderThread;
+class EglManager;
+
+class Frame {
+public:
+ EGLint width() const { return mWidth; }
+ EGLint height() const { return mHeight; }
+
+ // See: https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_buffer_age.txt
+ // for what this means
+ EGLint bufferAge() const { return mBufferAge; }
+
+private:
+ friend class EglManager;
+
+ EGLSurface mSurface;
+ EGLint mWidth;
+ EGLint mHeight;
+ EGLint mBufferAge;
+
+ // Maps from 0,0 in top-left to 0,0 in bottom-left
+ // If out is not an EGLint[4] you're going to have a bad time
+ void map(const SkRect& in, EGLint* out) const;
+};
// This class contains the shared global EGL objects, such as EGLDisplay
// and EGLConfig, which are re-used by CanvasContext
@@ -45,8 +68,13 @@ public:
bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; }
// Returns true if the current surface changed, false if it was already current
bool makeCurrent(EGLSurface surface, EGLint* errOut = nullptr);
- void beginFrame(EGLSurface surface, EGLint* width, EGLint* height);
- bool swapBuffers(EGLSurface surface, const SkRect& dirty, EGLint width, EGLint height);
+ Frame beginFrame(EGLSurface surface);
+ void damageFrame(const Frame& frame, const SkRect& dirty);
+ // If this returns true it is mandatory that swapBuffers is called
+ // if damageFrame is called without subsequent calls to damageFrame().
+ // See EGL_KHR_partial_update for more information
+ bool damageRequiresSwap();
+ bool swapBuffers(const Frame& frame, const SkRect& screenDirty);
// Returns true iff the surface is now preserving buffers.
bool setPreserveBuffer(EGLSurface surface, bool preserve);
@@ -62,10 +90,12 @@ private:
// EglContext is never destroyed, method is purposely not implemented
~EglManager();
+ void initExtensions();
void createPBufferSurface();
void loadConfig();
void createContext();
void initAtlas();
+ EGLint queryBufferAge(EGLSurface surface);
RenderThread& mRenderThread;
@@ -74,14 +104,18 @@ private:
EGLContext mEglContext;
EGLSurface mPBufferSurface;
- const bool mAllowPreserveBuffer;
- bool mCanSetPreserveBuffer;
-
EGLSurface mCurrentSurface;
sp<GraphicBuffer> mAtlasBuffer;
int64_t* mAtlasMap;
size_t mAtlasMapSize;
+
+ enum class SwapBehavior {
+ Discard,
+ Preserved,
+ BufferAge,
+ };
+ SwapBehavior mSwapBehavior = SwapBehavior::Discard;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 6d9acd429279..54af2829cf40 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -19,6 +19,7 @@
#include "DeferredLayerUpdater.h"
#include "DisplayList.h"
#include "LayerRenderer.h"
+#include "Readback.h"
#include "Rect.h"
#include "renderthread/CanvasContext.h"
#include "renderthread/RenderTask.h"
@@ -52,13 +53,6 @@ namespace renderthread {
MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \
ARGS(method) *args = (ARGS(method) *) task->payload()
-namespace DumpFlags {
- enum {
- FrameStats = 1 << 0,
- Reset = 1 << 1,
- };
-};
-
CREATE_BRIDGE4(createContext, RenderThread* thread, bool translucent,
RenderNode* rootRenderNode, IContextFactory* contextFactory) {
return new CanvasContext(*args->thread, args->translucent,
@@ -74,7 +68,7 @@ RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextF
args->thread = &mRenderThread;
args->contextFactory = contextFactory;
mContext = (CanvasContext*) postAndWait(task);
- mDrawFrameTask.setContext(&mRenderThread, mContext);
+ mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
}
RenderProxy::~RenderProxy() {
@@ -91,7 +85,7 @@ void RenderProxy::destroyContext() {
SETUP_TASK(destroyContext);
args->context = mContext;
mContext = nullptr;
- mDrawFrameTask.setContext(nullptr, nullptr);
+ mDrawFrameTask.setContext(nullptr, nullptr, nullptr);
// This is also a fence as we need to be certain that there are no
// outstanding mDrawFrame tasks posted before it is destroyed
postAndWait(task);
@@ -139,40 +133,53 @@ void RenderProxy::setName(const char* name) {
postAndWait(task); // block since name/value pointers owned by caller
}
-CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) {
- return (void*) args->context->initialize(args->window);
+CREATE_BRIDGE2(initialize, CanvasContext* context, Surface* surface) {
+ args->context->initialize(args->surface);
+ return nullptr;
}
-bool RenderProxy::initialize(const sp<ANativeWindow>& window) {
+void RenderProxy::initialize(const sp<Surface>& surface) {
SETUP_TASK(initialize);
args->context = mContext;
- args->window = window.get();
- return (bool) postAndWait(task);
+ args->surface = surface.get();
+ post(task);
}
-CREATE_BRIDGE2(updateSurface, CanvasContext* context, ANativeWindow* window) {
- args->context->updateSurface(args->window);
+CREATE_BRIDGE2(updateSurface, CanvasContext* context, Surface* surface) {
+ args->context->updateSurface(args->surface);
return nullptr;
}
-void RenderProxy::updateSurface(const sp<ANativeWindow>& window) {
+void RenderProxy::updateSurface(const sp<Surface>& surface) {
SETUP_TASK(updateSurface);
args->context = mContext;
- args->window = window.get();
+ args->surface = surface.get();
postAndWait(task);
}
-CREATE_BRIDGE2(pauseSurface, CanvasContext* context, ANativeWindow* window) {
- return (void*) args->context->pauseSurface(args->window);
+CREATE_BRIDGE2(pauseSurface, CanvasContext* context, Surface* surface) {
+ return (void*) args->context->pauseSurface(args->surface);
}
-bool RenderProxy::pauseSurface(const sp<ANativeWindow>& window) {
+bool RenderProxy::pauseSurface(const sp<Surface>& surface) {
SETUP_TASK(pauseSurface);
args->context = mContext;
- args->window = window.get();
+ args->surface = surface.get();
return (bool) postAndWait(task);
}
+CREATE_BRIDGE2(setStopped, CanvasContext* context, bool stopped) {
+ args->context->setStopped(args->stopped);
+ return nullptr;
+}
+
+void RenderProxy::setStopped(bool stopped) {
+ SETUP_TASK(setStopped);
+ args->context = mContext;
+ args->stopped = stopped;
+ postAndWait(task);
+}
+
CREATE_BRIDGE6(setup, CanvasContext* context, int width, int height,
float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
args->context->setup(args->width, args->height, args->lightRadius,
@@ -220,18 +227,19 @@ int64_t* RenderProxy::frameInfo() {
return mDrawFrameTask.frameInfo();
}
-int RenderProxy::syncAndDrawFrame() {
- return mDrawFrameTask.drawFrame();
+int RenderProxy::syncAndDrawFrame(TreeObserver* observer) {
+ return mDrawFrameTask.drawFrame(observer);
}
-CREATE_BRIDGE1(destroy, CanvasContext* context) {
- args->context->destroy();
+CREATE_BRIDGE2(destroy, CanvasContext* context, TreeObserver* observer) {
+ args->context->destroy(args->observer);
return nullptr;
}
-void RenderProxy::destroy() {
+void RenderProxy::destroy(TreeObserver* observer) {
SETUP_TASK(destroy);
args->context = mContext;
+ args->observer = observer;
// destroyCanvasAndSurface() needs a fence as when it returns the
// underlying BufferQueue is going to be released from under
// the render thread.
@@ -271,30 +279,30 @@ void RenderProxy::runWithGlContext(RenderTask* gltask) {
postAndWait(task);
}
-CREATE_BRIDGE2(createTextureLayer, RenderThread* thread, CanvasContext* context) {
+CREATE_BRIDGE1(createTextureLayer, CanvasContext* context) {
Layer* layer = args->context->createTextureLayer();
if (!layer) return nullptr;
- return new DeferredLayerUpdater(*args->thread, layer);
+ return new DeferredLayerUpdater(layer);
}
DeferredLayerUpdater* RenderProxy::createTextureLayer() {
SETUP_TASK(createTextureLayer);
args->context = mContext;
- args->thread = &mRenderThread;
void* retval = postAndWait(task);
DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(retval);
return layer;
}
-CREATE_BRIDGE2(buildLayer, CanvasContext* context, RenderNode* node) {
- args->context->buildLayer(args->node);
+CREATE_BRIDGE3(buildLayer, CanvasContext* context, RenderNode* node, TreeObserver* observer) {
+ args->context->buildLayer(args->node, args->observer);
return nullptr;
}
-void RenderProxy::buildLayer(RenderNode* node) {
+void RenderProxy::buildLayer(RenderNode* node, TreeObserver* observer) {
SETUP_TASK(buildLayer);
args->context = mContext;
args->node = node;
+ args->observer = observer;
postAndWait(task);
}
@@ -331,15 +339,16 @@ void RenderProxy::detachSurfaceTexture(DeferredLayerUpdater* layer) {
postAndWait(task);
}
-CREATE_BRIDGE1(destroyHardwareResources, CanvasContext* context) {
- args->context->destroyHardwareResources();
+CREATE_BRIDGE2(destroyHardwareResources, CanvasContext* context, TreeObserver* observer) {
+ args->context->destroyHardwareResources(args->observer);
return nullptr;
}
-void RenderProxy::destroyHardwareResources() {
+void RenderProxy::destroyHardwareResources(TreeObserver* observer) {
SETUP_TASK(destroyHardwareResources);
args->context = mContext;
- post(task);
+ args->observer = observer;
+ postAndWait(task);
}
CREATE_BRIDGE2(trimMemory, RenderThread* thread, int level) {
@@ -384,6 +393,12 @@ void RenderProxy::fence() {
postAndWait(task);
}
+void RenderProxy::staticFence() {
+ SETUP_TASK(fence);
+ UNUSED(args);
+ staticPostAndWait(task);
+}
+
CREATE_BRIDGE1(stopDrawing, CanvasContext* context) {
args->context->stopDrawing();
return nullptr;
@@ -409,13 +424,15 @@ void RenderProxy::notifyFramePending() {
CREATE_BRIDGE4(dumpProfileInfo, CanvasContext* context, RenderThread* thread,
int fd, int dumpFlags) {
args->context->profiler().dumpData(args->fd);
- args->thread->jankTracker().dump(args->fd);
if (args->dumpFlags & DumpFlags::FrameStats) {
args->context->dumpFrames(args->fd);
}
if (args->dumpFlags & DumpFlags::Reset) {
args->context->resetFrameStats();
}
+ if (args->dumpFlags & DumpFlags::JankStats) {
+ args->thread->jankTracker().dump(args->fd);
+ }
return nullptr;
}
@@ -450,6 +467,11 @@ CREATE_BRIDGE2(dumpGraphicsMemory, int fd, RenderThread* thread) {
} else {
fprintf(file, "\nNo caches instance.\n");
}
+#if HWUI_NEW_OPS
+ fprintf(file, "\nPipeline=FrameBuilder\n");
+#else
+ fprintf(file, "\nPipeline=OpenGLRenderer\n");
+#endif
fflush(file);
return nullptr;
}
@@ -462,7 +484,8 @@ void RenderProxy::dumpGraphicsMemory(int fd) {
staticPostAndWait(task);
}
-CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, size_t size) {
+CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map,
+ size_t size) {
CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size);
args->buffer->decStrong(nullptr);
return nullptr;
@@ -491,6 +514,124 @@ void RenderProxy::setProcessStatsBuffer(int fd) {
post(task);
}
+CREATE_BRIDGE3(addRenderNode, CanvasContext* context, RenderNode* node, bool placeFront) {
+ args->context->addRenderNode(args->node, args->placeFront);
+ return nullptr;
+}
+
+void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
+ SETUP_TASK(addRenderNode);
+ args->context = mContext;
+ args->node = node;
+ args->placeFront = placeFront;
+ post(task);
+}
+
+CREATE_BRIDGE2(removeRenderNode, CanvasContext* context, RenderNode* node) {
+ args->context->removeRenderNode(args->node);
+ return nullptr;
+}
+
+void RenderProxy::removeRenderNode(RenderNode* node) {
+ SETUP_TASK(removeRenderNode);
+ args->context = mContext;
+ args->node = node;
+ post(task);
+}
+
+CREATE_BRIDGE2(drawRenderNode, CanvasContext* context, RenderNode* node) {
+ args->context->prepareAndDraw(args->node);
+ return nullptr;
+}
+
+void RenderProxy::drawRenderNode(RenderNode* node) {
+ SETUP_TASK(drawRenderNode);
+ args->context = mContext;
+ args->node = node;
+ // Be pseudo-thread-safe and don't use any member variables
+ staticPostAndWait(task);
+}
+
+CREATE_BRIDGE5(setContentDrawBounds, CanvasContext* context, int left, int top,
+ int right, int bottom) {
+ args->context->setContentDrawBounds(args->left, args->top, args->right, args->bottom);
+ return nullptr;
+}
+
+void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) {
+ SETUP_TASK(setContentDrawBounds);
+ args->context = mContext;
+ args->left = left;
+ args->top = top;
+ args->right = right;
+ args->bottom = bottom;
+ staticPostAndWait(task);
+}
+
+CREATE_BRIDGE1(serializeDisplayListTree, CanvasContext* context) {
+ args->context->serializeDisplayListTree();
+ return nullptr;
+}
+
+void RenderProxy::serializeDisplayListTree() {
+ SETUP_TASK(serializeDisplayListTree);
+ args->context = mContext;
+ post(task);
+}
+
+CREATE_BRIDGE2(addFrameMetricsObserver, CanvasContext* context,
+ FrameMetricsObserver* frameStatsObserver) {
+ args->context->addFrameMetricsObserver(args->frameStatsObserver);
+ if (args->frameStatsObserver != nullptr) {
+ args->frameStatsObserver->decStrong(args->context);
+ }
+ return nullptr;
+}
+
+void RenderProxy::addFrameMetricsObserver(FrameMetricsObserver* observer) {
+ SETUP_TASK(addFrameMetricsObserver);
+ args->context = mContext;
+ args->frameStatsObserver = observer;
+ if (observer != nullptr) {
+ observer->incStrong(mContext);
+ }
+ post(task);
+}
+
+CREATE_BRIDGE2(removeFrameMetricsObserver, CanvasContext* context,
+ FrameMetricsObserver* frameStatsObserver) {
+ args->context->removeFrameMetricsObserver(args->frameStatsObserver);
+ if (args->frameStatsObserver != nullptr) {
+ args->frameStatsObserver->decStrong(args->context);
+ }
+ return nullptr;
+}
+
+void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observer) {
+ SETUP_TASK(removeFrameMetricsObserver);
+ args->context = mContext;
+ args->frameStatsObserver = observer;
+ if (observer != nullptr) {
+ observer->incStrong(mContext);
+ }
+ post(task);
+}
+
+CREATE_BRIDGE3(copySurfaceInto, RenderThread* thread,
+ Surface* surface, SkBitmap* bitmap) {
+ return (void*) Readback::copySurfaceInto(*args->thread,
+ *args->surface, args->bitmap);
+}
+
+int RenderProxy::copySurfaceInto(sp<Surface>& surface, SkBitmap* bitmap) {
+ SETUP_TASK(copySurfaceInto);
+ args->bitmap = bitmap;
+ args->surface = surface.get();
+ args->thread = &RenderThread::getInstance();
+ return static_cast<int>(
+ reinterpret_cast<intptr_t>( staticPostAndWait(task) ));
+}
+
void RenderProxy::post(RenderTask* task) {
mRenderThread.queue(task);
}
@@ -509,12 +650,7 @@ void* RenderProxy::staticPostAndWait(MethodInvokeRenderTask* task) {
RenderThread& thread = RenderThread::getInstance();
void* retval;
task->setReturnPtr(&retval);
- Mutex mutex;
- Condition condition;
- SignalingRenderTask syncTask(task, &mutex, &condition);
- AutoMutex _lock(mutex);
- thread.queue(&syncTask);
- condition.wait(mutex);
+ thread.queueAndWait(task);
return retval;
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 5febbe0ab26c..898b31421aad 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -27,9 +27,9 @@
#include <utils/Mutex.h>
#include <utils/Timers.h>
#include <utils/StrongPointer.h>
-#include <utils/Vector.h>
#include "../Caches.h"
+#include "../FrameMetricsObserver.h"
#include "../IContextFactory.h"
#include "CanvasContext.h"
#include "DrawFrameTask.h"
@@ -39,9 +39,10 @@ namespace uirenderer {
class DeferredLayerUpdater;
class RenderNode;
-class DisplayListData;
+class DisplayList;
class Layer;
class Rect;
+class TreeObserver;
namespace renderthread {
@@ -49,6 +50,14 @@ class ErrorChannel;
class RenderThread;
class RenderProxyBridge;
+namespace DumpFlags {
+ enum {
+ FrameStats = 1 << 0,
+ Reset = 1 << 1,
+ JankStats = 1 << 2,
+ };
+};
+
/*
* RenderProxy is strictly single threaded. All methods must be invoked on the owning
* thread. It is important to note that RenderProxy may be deleted while it has
@@ -67,33 +76,35 @@ public:
ANDROID_API bool loadSystemProperties();
ANDROID_API void setName(const char* name);
- ANDROID_API bool initialize(const sp<ANativeWindow>& window);
- ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
- ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window);
+ ANDROID_API void initialize(const sp<Surface>& surface);
+ ANDROID_API void updateSurface(const sp<Surface>& surface);
+ ANDROID_API bool pauseSurface(const sp<Surface>& surface);
+ ANDROID_API void setStopped(bool stopped);
ANDROID_API void setup(int width, int height, float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
ANDROID_API void setLightCenter(const Vector3& lightCenter);
ANDROID_API void setOpaque(bool opaque);
ANDROID_API int64_t* frameInfo();
- ANDROID_API int syncAndDrawFrame();
- ANDROID_API void destroy();
+ ANDROID_API int syncAndDrawFrame(TreeObserver* observer);
+ ANDROID_API void destroy(TreeObserver* observer);
ANDROID_API static void invokeFunctor(Functor* functor, bool waitForCompletion);
ANDROID_API void runWithGlContext(RenderTask* task);
ANDROID_API DeferredLayerUpdater* createTextureLayer();
- ANDROID_API void buildLayer(RenderNode* node);
+ ANDROID_API void buildLayer(RenderNode* node, TreeObserver* observer);
ANDROID_API bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap);
ANDROID_API void pushLayerUpdate(DeferredLayerUpdater* layer);
ANDROID_API void cancelLayerUpdate(DeferredLayerUpdater* layer);
ANDROID_API void detachSurfaceTexture(DeferredLayerUpdater* layer);
- ANDROID_API void destroyHardwareResources();
+ ANDROID_API void destroyHardwareResources(TreeObserver* observer);
ANDROID_API static void trimMemory(int level);
ANDROID_API static void overrideProperty(const char* name, const char* value);
ANDROID_API void fence();
+ ANDROID_API static void staticFence();
ANDROID_API void stopDrawing();
ANDROID_API void notifyFramePending();
@@ -105,6 +116,19 @@ public:
ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size);
ANDROID_API void setProcessStatsBuffer(int fd);
+ ANDROID_API void serializeDisplayListTree();
+
+ ANDROID_API void addRenderNode(RenderNode* node, bool placeFront);
+ ANDROID_API void removeRenderNode(RenderNode* node);
+ ANDROID_API void drawRenderNode(RenderNode* node);
+ ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
+
+ ANDROID_API void addFrameMetricsObserver(FrameMetricsObserver* observer);
+ ANDROID_API void removeFrameMetricsObserver(FrameMetricsObserver* observer);
+ ANDROID_API long getDroppedFrameReportCount();
+
+ ANDROID_API static int copySurfaceInto(sp<Surface>& surface, SkBitmap* bitmap);
+
private:
RenderThread& mRenderThread;
CanvasContext* mContext;
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 64075f1c346a..9fb30c928c00 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -25,12 +25,11 @@
#include <gui/ISurfaceComposer.h>
#include <gui/SurfaceComposerClient.h>
#include <sys/resource.h>
+#include <utils/Condition.h>
#include <utils/Log.h>
+#include <utils/Mutex.h>
namespace android {
-using namespace uirenderer::renderthread;
-ANDROID_SINGLETON_STATIC_INSTANCE(RenderThread);
-
namespace uirenderer {
namespace renderthread {
@@ -136,7 +135,22 @@ public:
}
};
-RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
+static bool gHasRenderThreadInstance = false;
+
+bool RenderThread::hasInstance() {
+ return gHasRenderThreadInstance;
+}
+
+RenderThread& RenderThread::getInstance() {
+ // This is a pointer because otherwise __cxa_finalize
+ // will try to delete it like a Good Citizen but that causes us to crash
+ // because we don't want to delete the RenderThread normally.
+ static RenderThread* sInstance = new RenderThread();
+ gHasRenderThreadInstance = true;
+ return *sInstance;
+}
+
+RenderThread::RenderThread() : Thread(true)
, mNextWakeup(LLONG_MAX)
, mDisplayEventReceiver(nullptr)
, mVsyncRequested(false)
@@ -312,6 +326,19 @@ void RenderThread::queue(RenderTask* task) {
}
}
+void RenderThread::queueAndWait(RenderTask* task) {
+ // These need to be local to the thread to avoid the Condition
+ // signaling the wrong thread. The easiest way to achieve that is to just
+ // make this on the stack, although that has a slight cost to it
+ Mutex mutex;
+ Condition condition;
+ SignalingRenderTask syncTask(task, &mutex, &condition);
+
+ AutoMutex _lock(mutex);
+ queue(&syncTask);
+ condition.wait(mutex);
+}
+
void RenderThread::queueAtFront(RenderTask* task) {
AutoMutex _lock(mLock);
mQueue.queueAtFront(task);
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 80960999ef53..076e3d43a2c9 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -25,8 +25,6 @@
#include <cutils/compiler.h>
#include <ui/DisplayInfo.h>
#include <utils/Looper.h>
-#include <utils/Mutex.h>
-#include <utils/Singleton.h>
#include <utils/Thread.h>
#include <memory>
@@ -39,6 +37,7 @@ class DisplayEventReceiver;
namespace uirenderer {
class RenderState;
+class TestUtils;
namespace renderthread {
@@ -71,11 +70,12 @@ protected:
~IFrameCallback() {}
};
-class ANDROID_API RenderThread : public Thread, protected Singleton<RenderThread> {
+class ANDROID_API RenderThread : public Thread {
public:
// RenderThread takes complete ownership of tasks that are queued
// and will delete them after they are run
ANDROID_API void queue(RenderTask* task);
+ ANDROID_API void queueAndWait(RenderTask* task);
ANDROID_API void queueAtFront(RenderTask* task);
void queueAt(RenderTask* task, nsecs_t runAtNs);
void remove(RenderTask* task);
@@ -98,13 +98,16 @@ protected:
virtual bool threadLoop() override;
private:
- friend class Singleton<RenderThread>;
friend class DispatchFrameCallbacks;
friend class RenderProxy;
+ friend class android::uirenderer::TestUtils;
RenderThread();
virtual ~RenderThread();
+ static bool hasInstance();
+ static RenderThread& getInstance();
+
void initThreadLocals();
void initializeDisplayEventReceiver();
static int displayEventReceiverCallback(int fd, int events, void* data);
diff --git a/libs/hwui/tests/Android.mk b/libs/hwui/tests/Android.mk
deleted file mode 100644
index b6f0baf4bf3e..000000000000
--- a/libs/hwui/tests/Android.mk
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Copyright (C) 2014 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.
-#
-
-local_target_dir := $(TARGET_OUT_DATA)/local/tmp
-LOCAL_PATH:= $(call my-dir)/..
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_PATH := $(local_target_dir)
-LOCAL_MODULE:= hwuitest
-LOCAL_MODULE_TAGS := tests
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := hwuitest
-LOCAL_MODULE_STEM_64 := hwuitest64
-
-HWUI_NULL_GPU := false
-
-include $(LOCAL_PATH)/Android.common.mk
-
-LOCAL_SRC_FILES += \
- tests/TestContext.cpp \
- tests/main.cpp
-
-include $(BUILD_EXECUTABLE)
diff --git a/libs/hwui/tests/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index 3687a5003471..146e735839d1 100644
--- a/libs/hwui/tests/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "TestContext.h"
+#include "tests/common/TestContext.h"
namespace android {
namespace uirenderer {
@@ -22,16 +22,35 @@ namespace test {
static const int IDENT_DISPLAYEVENT = 1;
-static DisplayInfo getBuiltInDisplay() {
+static android::DisplayInfo DUMMY_DISPLAY {
+ 1080, //w
+ 1920, //h
+ 320.0, // xdpi
+ 320.0, // ydpi
+ 60.0, // fps
+ 2.0, // density
+ 0, // orientation
+ false, // secure?
+ 0, // appVsyncOffset
+ 0, // presentationDeadline
+ 0, // colorTransform
+};
+
+DisplayInfo getBuiltInDisplay() {
+#if !HWUI_NULL_GPU
DisplayInfo display;
sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
ISurfaceComposer::eDisplayIdMain));
status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &display);
LOG_ALWAYS_FATAL_IF(status, "Failed to get display info\n");
return display;
+#else
+ return DUMMY_DISPLAY;
+#endif
}
-android::DisplayInfo gDisplay = getBuiltInDisplay();
+// Initialize to a dummy default
+android::DisplayInfo gDisplay = DUMMY_DISPLAY;
TestContext::TestContext() {
mLooper = new Looper(true);
@@ -57,10 +76,7 @@ sp<Surface> TestContext::surface() {
}
void TestContext::waitForVsync() {
-#if HWUI_NULL_GPU
- return;
-#endif
-
+#if !HWUI_NULL_GPU
// Request vsync
mDisplayEventReceiver.requestNextVsync();
@@ -70,6 +86,7 @@ void TestContext::waitForVsync() {
// Drain it
DisplayEventReceiver::Event buf[100];
while (mDisplayEventReceiver.getEvents(buf, 100) > 0) { }
+#endif
}
} // namespace test
diff --git a/libs/hwui/tests/TestContext.h b/libs/hwui/tests/common/TestContext.h
index 7b30fc1dc7ce..2bbe5dffd9b8 100644
--- a/libs/hwui/tests/TestContext.h
+++ b/libs/hwui/tests/common/TestContext.h
@@ -32,6 +32,8 @@ namespace test {
extern DisplayInfo gDisplay;
#define dp(x) ((x) * android::uirenderer::test::gDisplay.density)
+DisplayInfo getBuiltInDisplay();
+
class TestContext {
public:
TestContext();
diff --git a/libs/hwui/tests/common/TestScene.cpp b/libs/hwui/tests/common/TestScene.cpp
new file mode 100644
index 000000000000..02bcd4768a65
--- /dev/null
+++ b/libs/hwui/tests/common/TestScene.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 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 "tests/common/TestScene.h"
+
+namespace android {
+namespace uirenderer {
+namespace test {
+
+// Not a static global because we need to force the map to be constructed
+// before we try to add things to it.
+std::unordered_map<std::string, TestScene::Info>& TestScene::testMap() {
+ static std::unordered_map<std::string, TestScene::Info> testMap;
+ return testMap;
+}
+
+void TestScene::registerScene(const TestScene::Info& info) {
+ testMap()[info.name] = info;
+}
+
+} /* namespace test */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h
new file mode 100644
index 000000000000..706f2ff75222
--- /dev/null
+++ b/libs/hwui/tests/common/TestScene.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 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 TESTS_TESTSCENE_H
+#define TESTS_TESTSCENE_H
+
+#include <string>
+#include <unordered_map>
+
+namespace android {
+namespace uirenderer {
+class RenderNode;
+
+#if HWUI_NEW_OPS
+class RecordingCanvas;
+typedef RecordingCanvas TestCanvas;
+#else
+class DisplayListCanvas;
+typedef DisplayListCanvas TestCanvas;
+#endif
+
+namespace test {
+
+class TestScene {
+public:
+ struct Options {
+ int count = 0;
+ int reportFrametimeWeight = 0;
+ };
+
+ template <class T>
+ static test::TestScene* simpleCreateScene(const TestScene::Options&) {
+ return new T();
+ }
+
+ typedef test::TestScene* (*CreateScene)(const TestScene::Options&);
+
+ struct Info {
+ std::string name;
+ std::string description;
+ CreateScene createScene;
+ };
+
+ class Registrar {
+ public:
+ Registrar(const TestScene::Info& info) {
+ TestScene::registerScene(info);
+ }
+ private:
+ Registrar() = delete;
+ Registrar(const Registrar&) = delete;
+ Registrar& operator=(const Registrar&) = delete;
+ };
+
+ virtual ~TestScene() {}
+ virtual void createContent(int width, int height, TestCanvas& renderer) = 0;
+ virtual void doFrame(int frameNr) = 0;
+
+ static std::unordered_map<std::string, Info>& testMap();
+ static void registerScene(const Info& info);
+};
+
+} // namespace test
+} // namespace uirenderer
+} // namespace android
+
+#endif /* TESTS_TESTSCENE_H */
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
new file mode 100644
index 000000000000..c762eed616e4
--- /dev/null
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2015 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 "TestUtils.h"
+
+#include "hwui/Paint.h"
+#include "DeferredLayerUpdater.h"
+#include "LayerRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) {
+ int startA = (start >> 24) & 0xff;
+ int startR = (start >> 16) & 0xff;
+ int startG = (start >> 8) & 0xff;
+ int startB = start & 0xff;
+
+ int endA = (end >> 24) & 0xff;
+ int endR = (end >> 16) & 0xff;
+ int endG = (end >> 8) & 0xff;
+ int endB = end & 0xff;
+
+ return (int)((startA + (int)(fraction * (endA - startA))) << 24)
+ | (int)((startR + (int)(fraction * (endR - startR))) << 16)
+ | (int)((startG + (int)(fraction * (endG - startG))) << 8)
+ | (int)((startB + (int)(fraction * (endB - startB))));
+}
+
+sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater(
+ renderthread::RenderThread& renderThread, uint32_t width, uint32_t height,
+ const SkMatrix& transform) {
+ Layer* layer = LayerRenderer::createTextureLayer(renderThread.renderState());
+ layer->getTransform().load(transform);
+
+ sp<DeferredLayerUpdater> layerUpdater = new DeferredLayerUpdater(layer);
+ layerUpdater->setSize(width, height);
+ layerUpdater->setTransform(&transform);
+
+ // updateLayer so it's ready to draw
+ bool isOpaque = true;
+ bool forceFilter = true;
+ GLenum renderTarget = GL_TEXTURE_EXTERNAL_OES;
+ LayerRenderer::updateTextureLayer(layer, width, height, isOpaque, forceFilter,
+ renderTarget, Matrix4::identity().data);
+
+ return layerUpdater;
+}
+
+void TestUtils::layoutTextUnscaled(const SkPaint& paint, const char* text,
+ std::vector<glyph_t>* outGlyphs, std::vector<float>* outPositions,
+ float* outTotalAdvance, Rect* outBounds) {
+ Rect bounds;
+ float totalAdvance = 0;
+ SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+ SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &SkMatrix::I());
+ while (*text != '\0') {
+ SkUnichar unichar = SkUTF8_NextUnichar(&text);
+ glyph_t glyph = autoCache.getCache()->unicharToGlyph(unichar);
+ autoCache.getCache()->unicharToGlyph(unichar);
+
+ // push glyph and its relative position
+ outGlyphs->push_back(glyph);
+ outPositions->push_back(totalAdvance);
+ outPositions->push_back(0);
+
+ // compute bounds
+ SkGlyph skGlyph = autoCache.getCache()->getUnicharMetrics(unichar);
+ Rect glyphBounds(skGlyph.fWidth, skGlyph.fHeight);
+ glyphBounds.translate(totalAdvance + skGlyph.fLeft, skGlyph.fTop);
+ bounds.unionWith(glyphBounds);
+
+ // advance next character
+ SkScalar skWidth;
+ paint.getTextWidths(&glyph, sizeof(glyph), &skWidth, NULL);
+ totalAdvance += skWidth;
+ }
+ *outBounds = bounds;
+ *outTotalAdvance = totalAdvance;
+}
+
+
+void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text,
+ const SkPaint& paint, float x, float y) {
+ auto utf16 = asciiToUtf16(text);
+ canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, 0, paint, nullptr);
+}
+
+void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text,
+ const SkPaint& paint, const SkPath& path) {
+ auto utf16 = asciiToUtf16(text);
+ canvas->drawTextOnPath(utf16.get(), strlen(text), 0, path, 0, 0, paint, nullptr);
+}
+
+void TestUtils::TestTask::run() {
+ // RenderState only valid once RenderThread is running, so queried here
+ RenderState& renderState = renderthread::RenderThread::getInstance().renderState();
+
+ renderState.onGLContextCreated();
+ rtCallback(renderthread::RenderThread::getInstance());
+ renderState.flush(Caches::FlushMode::Full);
+ renderState.onGLContextDestroyed();
+}
+
+std::unique_ptr<uint16_t[]> TestUtils::asciiToUtf16(const char* str) {
+ const int length = strlen(str);
+ std::unique_ptr<uint16_t[]> utf16(new uint16_t[length]);
+ for (int i = 0; i < length; i++) {
+ utf16.get()[i] = str[i];
+ }
+ return utf16;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
new file mode 100644
index 000000000000..dbaefa470b50
--- /dev/null
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2015 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 TEST_UTILS_H
+#define TEST_UTILS_H
+
+#include <DeviceInfo.h>
+#include <DisplayList.h>
+#include <Matrix.h>
+#include <Rect.h>
+#include <RenderNode.h>
+#include <renderstate/RenderState.h>
+#include <renderthread/RenderThread.h>
+#include <Snapshot.h>
+
+#if HWUI_NEW_OPS
+#include <RecordedOp.h>
+#include <RecordingCanvas.h>
+#else
+#include <DisplayListOp.h>
+#include <DisplayListCanvas.h>
+#endif
+
+#include <memory>
+
+namespace android {
+namespace uirenderer {
+
+#if HWUI_NEW_OPS
+typedef RecordingCanvas TestCanvas;
+#else
+typedef DisplayListCanvas TestCanvas;
+#endif
+
+#define EXPECT_MATRIX_APPROX_EQ(a, b) \
+ EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b))
+
+#define EXPECT_RECT_APPROX_EQ(a, b) \
+ EXPECT_TRUE(MathUtils::areEqual(a.left, b.left) \
+ && MathUtils::areEqual(a.top, b.top) \
+ && MathUtils::areEqual(a.right, b.right) \
+ && MathUtils::areEqual(a.bottom, b.bottom));
+
+#define EXPECT_CLIP_RECT(expRect, clipStatePtr) \
+ EXPECT_NE(nullptr, (clipStatePtr)) << "Op is unclipped"; \
+ if ((clipStatePtr)->mode == ClipMode::Rectangle) { \
+ EXPECT_EQ((expRect), reinterpret_cast<const ClipRect*>(clipStatePtr)->rect); \
+ } else { \
+ ADD_FAILURE() << "ClipState not a rect"; \
+ }
+/**
+ * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
+ * (for e.g. accessing its RenderState)
+ */
+#define RENDERTHREAD_TEST(test_case_name, test_name) \
+ class test_case_name##_##test_name##_RenderThreadTest { \
+ public: \
+ static void doTheThing(renderthread::RenderThread& renderThread); \
+ }; \
+ TEST(test_case_name, test_name) { \
+ TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \
+ }; \
+ void test_case_name##_##test_name##_RenderThreadTest::doTheThing(renderthread::RenderThread& renderThread)
+
+class TestUtils {
+public:
+ class SignalingDtor {
+ public:
+ SignalingDtor()
+ : mSignal(nullptr) {}
+ SignalingDtor(int* signal)
+ : mSignal(signal) {}
+ void setSignal(int* signal) {
+ mSignal = signal;
+ }
+ ~SignalingDtor() {
+ if (mSignal) {
+ (*mSignal)++;
+ }
+ }
+ private:
+ int* mSignal;
+ };
+
+ static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) {
+ for (int i = 0; i < 16; i++) {
+ if (!MathUtils::areEqual(a[i], b[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static std::unique_ptr<Snapshot> makeSnapshot(const Matrix4& transform, const Rect& clip) {
+ std::unique_ptr<Snapshot> snapshot(new Snapshot());
+ snapshot->clip(clip, SkRegion::kReplace_Op); // store clip first, so it isn't transformed
+ *(snapshot->transform) = transform;
+ return snapshot;
+ }
+
+ static SkBitmap createSkBitmap(int width, int height,
+ SkColorType colorType = kN32_SkColorType) {
+ SkBitmap bitmap;
+ SkImageInfo info = SkImageInfo::Make(width, height,
+ colorType, kPremul_SkAlphaType);
+ bitmap.setInfo(info);
+ bitmap.allocPixels(info);
+ return bitmap;
+ }
+
+ static sp<DeferredLayerUpdater> createTextureLayerUpdater(
+ renderthread::RenderThread& renderThread, uint32_t width, uint32_t height,
+ const SkMatrix& transform);
+
+ template<class CanvasType>
+ static std::unique_ptr<DisplayList> createDisplayList(int width, int height,
+ std::function<void(CanvasType& canvas)> canvasCallback) {
+ CanvasType canvas(width, height);
+ canvasCallback(canvas);
+ return std::unique_ptr<DisplayList>(canvas.finishRecording());
+ }
+
+ static sp<RenderNode> createNode(int left, int top, int right, int bottom,
+ std::function<void(RenderProperties& props, TestCanvas& canvas)> setup) {
+#if HWUI_NULL_GPU
+ // if RenderNodes are being sync'd/used, device info will be needed, since
+ // DeviceInfo::maxTextureSize() affects layer property
+ DeviceInfo::initialize();
+#endif
+
+ sp<RenderNode> node = new RenderNode();
+ RenderProperties& props = node->mutateStagingProperties();
+ props.setLeftTopRightBottom(left, top, right, bottom);
+ if (setup) {
+ TestCanvas canvas(props.getWidth(), props.getHeight());
+ setup(props, canvas);
+ node->setStagingDisplayList(canvas.finishRecording(), nullptr);
+ }
+ node->setPropertyFieldsDirty(0xFFFFFFFF);
+ return node;
+ }
+
+ static void recordNode(RenderNode& node,
+ std::function<void(TestCanvas&)> contentCallback) {
+ TestCanvas canvas(node.stagingProperties().getWidth(),
+ node.stagingProperties().getHeight());
+ contentCallback(canvas);
+ node.setStagingDisplayList(canvas.finishRecording(), nullptr);
+ }
+
+ /**
+ * Forces a sync of a tree of RenderNode, such that every descendant will have its staging
+ * properties and DisplayList moved to the render copies.
+ *
+ * Note: does not check dirtiness bits, so any non-staging DisplayLists will be discarded.
+ * For this reason, this should generally only be called once on a tree.
+ */
+ static void syncHierarchyPropertiesAndDisplayList(sp<RenderNode>& node) {
+ syncHierarchyPropertiesAndDisplayListImpl(node.get());
+ }
+
+ static sp<RenderNode>& getSyncedNode(sp<RenderNode>& node) {
+ syncHierarchyPropertiesAndDisplayList(node);
+ return node;
+ }
+
+ typedef std::function<void(renderthread::RenderThread& thread)> RtCallback;
+
+ class TestTask : public renderthread::RenderTask {
+ public:
+ TestTask(RtCallback rtCallback)
+ : rtCallback(rtCallback) {}
+ virtual ~TestTask() {}
+ virtual void run() override;
+ RtCallback rtCallback;
+ };
+
+ /**
+ * NOTE: requires surfaceflinger to run, otherwise this method will wait indefinitely.
+ */
+ static void runOnRenderThread(RtCallback rtCallback) {
+ TestTask task(rtCallback);
+ renderthread::RenderThread::getInstance().queueAndWait(&task);
+ }
+
+ static bool isRenderThreadRunning() {
+ return renderthread::RenderThread::hasInstance();
+ }
+
+ static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
+
+ static void layoutTextUnscaled(const SkPaint& paint, const char* text,
+ std::vector<glyph_t>* outGlyphs, std::vector<float>* outPositions,
+ float* outTotalAdvance, Rect* outBounds);
+
+ static void drawUtf8ToCanvas(Canvas* canvas, const char* text,
+ const SkPaint& paint, float x, float y);
+
+ static void drawUtf8ToCanvas(Canvas* canvas, const char* text,
+ const SkPaint& paint, const SkPath& path);
+
+ static std::unique_ptr<uint16_t[]> asciiToUtf16(const char* str);
+
+private:
+ static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
+ node->syncProperties();
+ node->syncDisplayList(nullptr);
+ auto displayList = node->getDisplayList();
+ if (displayList) {
+ for (auto&& childOp : displayList->getChildren()) {
+ syncHierarchyPropertiesAndDisplayListImpl(childOp->renderNode);
+ }
+ }
+ }
+
+}; // class TestUtils
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* TEST_UTILS_H */
diff --git a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
new file mode 100644
index 000000000000..a5fd71266314
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+
+class ClippingAnimation;
+
+static TestScene::Registrar _RectGrid(TestScene::Info{
+ "clip",
+ "Complex clip cases"
+ "Low CPU/GPU load.",
+ TestScene::simpleCreateScene<ClippingAnimation>
+});
+
+class ClippingAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ card = TestUtils::createNode(0, 0, 200, 400,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ {
+ canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
+ canvas.translate(100, 100);
+ canvas.rotate(45);
+ canvas.translate(-100, -100);
+ canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
+ canvas.drawColor(Color::Blue_500, SkXfermode::kSrcOver_Mode);
+ }
+ canvas.restore();
+
+ canvas.save(SaveFlags::MatrixClip);
+ {
+ SkPath clipCircle;
+ clipCircle.addCircle(100, 300, 100);
+ canvas.clipPath(&clipCircle, SkRegion::kIntersect_Op);
+ canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode);
+ }
+ canvas.restore();
+
+ // put on a layer, to test stencil attachment
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
+ props.setAlpha(0.9f);
+ });
+ canvas.drawRenderNode(card.get());
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
new file mode 100644
index 000000000000..f184411b4139
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+#include "utils/Color.h"
+
+#include <minikin/Layout.h>
+#include <hwui/Paint.h>
+
+#include <cstdio>
+
+class GlyphStressAnimation;
+
+static TestScene::Registrar _GlyphStress(TestScene::Info{
+ "glyphstress",
+ "A stress test for both the glyph cache, and glyph rendering.",
+ TestScene::simpleCreateScene<GlyphStressAnimation>
+});
+
+class GlyphStressAnimation : public TestScene {
+public:
+ sp<RenderNode> container;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ container = TestUtils::createNode(0, 0, width, height, nullptr);
+ doFrame(0); // update container
+
+ canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ canvas.drawRenderNode(container.get());
+ }
+
+ void doFrame(int frameNr) override {
+ std::unique_ptr<uint16_t[]> text = TestUtils::asciiToUtf16(
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ ssize_t textLength = 26 * 2;
+
+ TestCanvas canvas(
+ container->stagingProperties().getWidth(),
+ container->stagingProperties().getHeight());
+ Paint paint;
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ paint.setAntiAlias(true);
+ paint.setColor(Color::Black);
+ for (int i = 0; i < 5; i++) {
+ paint.setTextSize(10 + (frameNr % 20) + i * 20);
+ canvas.drawText(text.get(), 0, textLength, textLength,
+ 0, 100 * (i + 2), kBidi_Force_LTR, paint, nullptr);
+ }
+
+ container->setStagingDisplayList(canvas.finishRecording(), nullptr);
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp
new file mode 100644
index 000000000000..c212df4f978d
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+
+class HwLayerAnimation;
+
+static TestScene::Registrar _HwLayer(TestScene::Info{
+ "hwlayer",
+ "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
+ "Tests the hardware layer codepath.",
+ TestScene::simpleCreateScene<HwLayerAnimation>
+});
+
+class HwLayerAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ card = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
+ canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
+ });
+ canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+ canvas.drawRenderNode(card.get());
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
new file mode 100644
index 000000000000..8035dc45f23c
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+#include "utils/Color.h"
+
+#include <cstdio>
+
+class ListViewAnimation;
+
+static TestScene::Registrar _ListView(TestScene::Info{
+ "listview",
+ "A mock ListView of scrolling content. Doesn't re-bind/re-record views as they are recycled, so"
+ "won't upload much content (either glyphs, or bitmaps).",
+ TestScene::simpleCreateScene<ListViewAnimation>
+});
+
+class ListViewAnimation : public TestScene {
+public:
+ int cardHeight;
+ int cardSpacing;
+ int cardWidth;
+ int cardLeft;
+ sp<RenderNode> listView;
+ std::vector< sp<RenderNode> > cards;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ srand(0);
+ cardHeight = dp(60);
+ cardSpacing = dp(16);
+ cardWidth = std::min((height - cardSpacing * 2), (int)dp(300));
+ cardLeft = (width - cardWidth) / 2;
+
+ for (int y = 0; y < height + (cardHeight + cardSpacing - 1); y += (cardHeight + cardSpacing)) {
+ cards.push_back(createCard(cards.size(), y));
+ }
+ listView = TestUtils::createNode(0, 0, width, height,
+ [this](RenderProperties& props, TestCanvas& canvas) {
+ for (size_t ci = 0; ci < cards.size(); ci++) {
+ canvas.drawRenderNode(cards[ci].get());
+ }
+ });
+
+ canvas.drawColor(Color::Grey_500, SkXfermode::kSrcOver_Mode);
+ canvas.drawRenderNode(listView.get());
+ }
+
+ void doFrame(int frameNr) override {
+ int scrollPx = dp(frameNr) * 3;
+ int cardIndexOffset = scrollPx / (cardSpacing + cardHeight);
+ int pxOffset = -(scrollPx % (cardSpacing + cardHeight));
+
+ TestCanvas canvas(
+ listView->stagingProperties().getWidth(),
+ listView->stagingProperties().getHeight());
+ for (size_t ci = 0; ci < cards.size(); ci++) {
+ // update card position
+ auto card = cards[(ci + cardIndexOffset) % cards.size()];
+ int top = ((int)ci) * (cardSpacing + cardHeight) + pxOffset;
+ card->mutateStagingProperties().setLeftTopRightBottom(
+ cardLeft, top, cardLeft + cardWidth, top + cardHeight);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+
+ // draw it to parent DisplayList
+ canvas.drawRenderNode(cards[ci].get());
+ }
+ listView->setStagingDisplayList(canvas.finishRecording(), nullptr);
+ }
+private:
+ SkBitmap createRandomCharIcon() {
+ int size = cardHeight - (dp(10) * 2);
+ SkBitmap bitmap = TestUtils::createSkBitmap(size, size);
+ SkCanvas canvas(bitmap);
+ canvas.clear(0);
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ SkColor randomColor = BrightColors[rand() % BrightColorsCount];
+ paint.setColor(randomColor);
+ canvas.drawCircle(size / 2, size / 2, size / 2, paint);
+
+ bool bgDark = SkColorGetR(randomColor) + SkColorGetG(randomColor) + SkColorGetB(randomColor)
+ < 128 * 3;
+ paint.setColor(bgDark ? Color::White : Color::Grey_700);
+ paint.setTextAlign(SkPaint::kCenter_Align);
+ paint.setTextSize(size / 2);
+ char charToShow = 'A' + (rand() % 26);
+ canvas.drawText(&charToShow, 1, size / 2, /*approximate centering*/ size * 0.7, paint);
+ return bitmap;
+ }
+
+ static SkBitmap createBoxBitmap(bool filled) {
+ int size = dp(20);
+ int stroke = dp(2);
+ SkBitmap bitmap = TestUtils::createSkBitmap(size, size);
+ SkCanvas canvas(bitmap);
+ canvas.clear(Color::Transparent);
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setColor(filled ? Color::Yellow_500 : Color::Grey_700);
+ paint.setStyle(filled ? SkPaint::kStrokeAndFill_Style : SkPaint::kStroke_Style);
+ paint.setStrokeWidth(stroke);
+ canvas.drawRect(SkRect::MakeLTRB(stroke, stroke, size - stroke, size - stroke), paint);
+ return bitmap;
+ }
+
+ sp<RenderNode> createCard(int cardId, int top) {
+ return TestUtils::createNode(cardLeft, top, cardLeft + cardWidth, top + cardHeight,
+ [this, cardId](RenderProperties& props, TestCanvas& canvas) {
+ static SkBitmap filledBox = createBoxBitmap(true);
+ static SkBitmap strokedBox = createBoxBitmap(false);
+
+ // TODO: switch to using round rect clipping, once merging correctly handles that
+ SkPaint roundRectPaint;
+ roundRectPaint.setAntiAlias(true);
+ roundRectPaint.setColor(Color::White);
+ canvas.drawRoundRect(0, 0, cardWidth, cardHeight, dp(6), dp(6), roundRectPaint);
+
+ SkPaint textPaint;
+ textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500);
+ textPaint.setTextSize(dp(20));
+ textPaint.setAntiAlias(true);
+ char buf[256];
+ snprintf(buf, sizeof(buf), "This card is #%d", cardId);
+ TestUtils::drawUtf8ToCanvas(&canvas, buf, textPaint, cardHeight, dp(25));
+ textPaint.setTextSize(dp(15));
+ TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint,
+ cardHeight, dp(45));
+
+ canvas.drawBitmap(createRandomCharIcon(), dp(10), dp(10), nullptr);
+
+ const SkBitmap& boxBitmap = rand() % 2 ? filledBox : strokedBox;
+ canvas.drawBitmap(boxBitmap, cardWidth - dp(10) - boxBitmap.width(), dp(10), nullptr);
+ });
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/OpPropAnimation.cpp b/libs/hwui/tests/common/scenes/OpPropAnimation.cpp
new file mode 100644
index 000000000000..5dfb2b4a8b33
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/OpPropAnimation.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+#include "utils/Color.h"
+
+class OpPropAnimation;
+
+static TestScene::Registrar _Shapes(TestScene::Info{
+ "opprops",
+ "A minimal demonstration of CanvasProperty drawing operations.",
+ TestScene::simpleCreateScene<OpPropAnimation>
+});
+
+class OpPropAnimation : public TestScene {
+public:
+ sp<CanvasPropertyPaint> mPaint = new CanvasPropertyPaint(SkPaint());
+
+ sp<CanvasPropertyPrimitive> mRoundRectLeft = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mRoundRectTop = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mRoundRectRight = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mRoundRectBottom = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mRoundRectRx = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mRoundRectRy = new CanvasPropertyPrimitive(0);
+
+ sp<CanvasPropertyPrimitive> mCircleX = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mCircleY = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mCircleRadius = new CanvasPropertyPrimitive(0);
+
+ sp<RenderNode> content;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ content = TestUtils::createNode(0, 0, width, height,
+ [this, width, height](RenderProperties& props, TestCanvas& canvas) {
+ mPaint->value.setAntiAlias(true);
+ mPaint->value.setColor(Color::Blue_500);
+
+ mRoundRectRight->value = width / 2;
+ mRoundRectBottom->value = height / 2;
+
+ mCircleX->value = width * 0.75;
+ mCircleY->value = height * 0.75;
+
+ canvas.drawColor(Color::White, SkXfermode::Mode::kSrcOver_Mode);
+ canvas.drawRoundRect(mRoundRectLeft.get(), mRoundRectTop.get(),
+ mRoundRectRight.get(), mRoundRectBottom.get(),
+ mRoundRectRx.get(), mRoundRectRy.get(), mPaint.get());
+ canvas.drawCircle(mCircleX.get(), mCircleY.get(), mCircleRadius.get(), mPaint.get());
+ });
+ canvas.drawRenderNode(content.get());
+ }
+
+ void doFrame(int frameNr) override {
+ float value = (abs((frameNr % 200) - 100)) / 100.0f;
+ mRoundRectRx->value = dp(10) + value * dp(40);
+ mRoundRectRy->value = dp(10) + value * dp(80);
+ mCircleRadius->value = value * dp(200);
+ content->setPropertyFieldsDirty(RenderNode::GENERIC);
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/OvalAnimation.cpp b/libs/hwui/tests/common/scenes/OvalAnimation.cpp
new file mode 100644
index 000000000000..e56f2f97007e
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/OvalAnimation.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+#include "utils/Color.h"
+
+class OvalAnimation;
+
+static TestScene::Registrar _Oval(TestScene::Info{
+ "oval",
+ "Draws 1 oval.",
+ TestScene::simpleCreateScene<OvalAnimation>
+});
+
+class OvalAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ card = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setColor(Color::Black);
+ canvas.drawOval(0, 0, 200, 200, paint);
+ });
+ canvas.drawRenderNode(card.get());
+ }
+
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp
new file mode 100644
index 000000000000..84265a4774a5
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+
+class PartialDamageAnimation;
+
+static TestScene::Registrar _PartialDamage(TestScene::Info{
+ "partialdamage",
+ "Tests the partial invalidation path. Draws a grid of rects and animates 1 "
+ "of them, should be low CPU & GPU load if EGL_EXT_buffer_age or "
+ "EGL_KHR_partial_update is supported by the device & are enabled in hwui.",
+ TestScene::simpleCreateScene<PartialDamageAnimation>
+});
+
+class PartialDamageAnimation : public TestScene {
+public:
+ std::vector< sp<RenderNode> > cards;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ static SkColor COLORS[] = {
+ 0xFFF44336,
+ 0xFF9C27B0,
+ 0xFF2196F3,
+ 0xFF4CAF50,
+ };
+
+ canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+
+ for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
+ for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
+ SkColor color = COLORS[static_cast<int>((y / dp(116))) % 4];
+ sp<RenderNode> card = TestUtils::createNode(x, y,
+ x + dp(100), y + dp(100),
+ [color](RenderProperties& props, TestCanvas& canvas) {
+ canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+ });
+ canvas.drawRenderNode(card.get());
+ cards.push_back(card);
+ }
+ }
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ cards[0]->mutateStagingProperties().setTranslationX(curFrame);
+ cards[0]->mutateStagingProperties().setTranslationY(curFrame);
+ cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+
+ TestUtils::recordNode(*cards[0], [curFrame](TestCanvas& canvas) {
+ SkColor color = TestUtils::interpolateColor(
+ curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0);
+ canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+ });
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/RecentsAnimation.cpp b/libs/hwui/tests/common/scenes/RecentsAnimation.cpp
new file mode 100644
index 000000000000..6509edd4077f
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/RecentsAnimation.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+#include "utils/Color.h"
+
+class RecentsAnimation;
+
+static TestScene::Registrar _Recents(TestScene::Info{
+ "recents",
+ "A recents-like scrolling list of textures. "
+ "Consists of updating a texture every frame",
+ TestScene::simpleCreateScene<RecentsAnimation>
+});
+
+class RecentsAnimation : public TestScene {
+public:
+ void createContent(int width, int height, TestCanvas& renderer) override {
+ static SkColor COLORS[] = {
+ Color::Red_500,
+ Color::Purple_500,
+ Color::Blue_500,
+ Color::Green_500,
+ };
+
+ thumbnailSize = std::min(std::min(width, height) / 2, 720);
+ int cardsize = std::min(width, height) - dp(64);
+
+ renderer.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ renderer.insertReorderBarrier(true);
+
+ int x = dp(32);
+ for (int i = 0; i < 4; i++) {
+ int y = (height / 4) * i;
+ SkBitmap thumb = TestUtils::createSkBitmap(thumbnailSize, thumbnailSize);
+ thumb.eraseColor(COLORS[i]);
+ sp<RenderNode> card = createCard(x, y, cardsize, cardsize, thumb);
+ card->mutateStagingProperties().setElevation(i * dp(8));
+ renderer.drawRenderNode(card.get());
+ mThumbnail = thumb;
+ mCards.push_back(card);
+ }
+
+ renderer.insertReorderBarrier(false);
+ }
+
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ for (size_t ci = 0; ci < mCards.size(); ci++) {
+ mCards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+ mCards[ci]->setPropertyFieldsDirty(RenderNode::Y);
+ }
+ mThumbnail.eraseColor(TestUtils::interpolateColor(
+ curFrame / 150.0f, Color::Green_500, Color::DeepOrange_500));
+ }
+
+private:
+ sp<RenderNode> createCard(int x, int y, int width, int height,
+ const SkBitmap& thumb) {
+ return TestUtils::createNode(x, y, x + width, y + height,
+ [&thumb, width, height](RenderProperties& props, TestCanvas& canvas) {
+ props.setElevation(dp(16));
+ props.mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
+ props.mutableOutline().setShouldClip(true);
+
+ canvas.drawColor(Color::Grey_200, SkXfermode::kSrcOver_Mode);
+ canvas.drawBitmap(thumb, 0, 0, thumb.width(), thumb.height(),
+ 0, 0, width, height, nullptr);
+ });
+ }
+
+ SkBitmap mThumbnail;
+ std::vector< sp<RenderNode> > mCards;
+ int thumbnailSize;
+};
diff --git a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
new file mode 100644
index 000000000000..a9293ab244dd
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+
+class RectGridAnimation;
+
+static TestScene::Registrar _RectGrid(TestScene::Info{
+ "rectgrid",
+ "A dense grid of 1x1 rects that should visually look like a single rect. "
+ "Low CPU/GPU load.",
+ TestScene::simpleCreateScene<RectGridAnimation>
+});
+
+class RectGridAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.insertReorderBarrier(true);
+
+ card = TestUtils::createNode(50, 50, 250, 250,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
+
+ SkRegion region;
+ for (int xOffset = 0; xOffset < 200; xOffset+=2) {
+ for (int yOffset = 0; yOffset < 200; yOffset+=2) {
+ region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
+ }
+ }
+
+ SkPaint paint;
+ paint.setColor(0xff00ffff);
+ canvas.drawRegion(region, paint);
+ });
+ canvas.drawRenderNode(card.get());
+
+ canvas.insertReorderBarrier(false);
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
new file mode 100644
index 000000000000..6904bec304e3
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+
+class SaveLayerAnimation;
+
+static TestScene::Registrar _SaveLayer(TestScene::Info{
+ "savelayer",
+ "A nested pair of clipped saveLayer operations. "
+ "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
+ TestScene::simpleCreateScene<SaveLayerAnimation>
+});
+
+class SaveLayerAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode); // background
+
+ card = TestUtils::createNode(0, 0, 400, 800,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ // nested clipped saveLayers
+ canvas.saveLayerAlpha(0, 0, 400, 400, 200, SaveFlags::ClipToLayer);
+ canvas.drawColor(Color::Green_700, SkXfermode::kSrcOver_Mode);
+ canvas.clipRect(50, 50, 350, 350, SkRegion::kIntersect_Op);
+ canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer);
+ canvas.drawColor(Color::Blue_500, SkXfermode::kSrcOver_Mode);
+ canvas.restore();
+ canvas.restore();
+
+ // single unclipped saveLayer
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.translate(0, 400);
+ canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::Flags(0)); // unclipped
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setColor(Color::Green_700);
+ canvas.drawCircle(200, 200, 200, paint);
+ canvas.restore();
+ canvas.restore();
+ });
+
+ canvas.drawRenderNode(card.get());
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp
new file mode 100644
index 000000000000..d3249b8f585a
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+
+class ShadowGrid2Animation;
+
+static TestScene::Registrar _ShadowGrid2(TestScene::Info{
+ "shadowgrid2",
+ "A dense grid of rounded rects that cast a shadow. This is a higher CPU load "
+ "variant of shadowgrid. Very high CPU load, high GPU load.",
+ TestScene::simpleCreateScene<ShadowGrid2Animation>
+});
+
+class ShadowGrid2Animation : public TestScene {
+public:
+ std::vector< sp<RenderNode> > cards;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.insertReorderBarrier(true);
+
+ for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
+ for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
+ sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
+ canvas.drawRenderNode(card.get());
+ cards.push_back(card);
+ }
+ }
+
+ canvas.insertReorderBarrier(false);
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ for (size_t ci = 0; ci < cards.size(); ci++) {
+ cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+ cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+ cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+ }
+private:
+ sp<RenderNode> createCard(int x, int y, int width, int height) {
+ return TestUtils::createNode(x, y, x + width, y + height,
+ [width, height](RenderProperties& props, TestCanvas& canvas) {
+ props.setElevation(dp(16));
+ props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
+ props.mutableOutline().setShouldClip(true);
+ canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+ });
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp
new file mode 100644
index 000000000000..5ffedf09bc70
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+
+class ShadowGridAnimation;
+
+static TestScene::Registrar _ShadowGrid(TestScene::Info{
+ "shadowgrid",
+ "A grid of rounded rects that cast a shadow. Simplified scenario of an "
+ "Android TV-style launcher interface. High CPU/GPU load.",
+ TestScene::simpleCreateScene<ShadowGridAnimation>
+});
+
+class ShadowGridAnimation : public TestScene {
+public:
+ std::vector< sp<RenderNode> > cards;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.insertReorderBarrier(true);
+
+ for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
+ for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
+ sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
+ canvas.drawRenderNode(card.get());
+ cards.push_back(card);
+ }
+ }
+
+ canvas.insertReorderBarrier(false);
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ for (size_t ci = 0; ci < cards.size(); ci++) {
+ cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+ cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+ cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+ }
+private:
+ sp<RenderNode> createCard(int x, int y, int width, int height) {
+ return TestUtils::createNode(x, y, x + width, y + height,
+ [width, height](RenderProperties& props, TestCanvas& canvas) {
+ props.setElevation(dp(16));
+ props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
+ props.mutableOutline().setShouldClip(true);
+ canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+ });
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
new file mode 100644
index 000000000000..6d27c9d61a29
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+#include "utils/Color.h"
+
+#include <cstdio>
+
+class ShapeAnimation;
+
+static TestScene::Registrar _Shapes(TestScene::Info{
+ "shapes",
+ "A grid of shape drawing test cases.",
+ TestScene::simpleCreateScene<ShapeAnimation>
+});
+
+class ShapeAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ card = TestUtils::createNode(0, 0, width, height,
+ [width](RenderProperties& props, TestCanvas& canvas) {
+ std::function<void(TestCanvas&, float, const SkPaint&)> ops[] = {
+ [](TestCanvas& canvas, float size, const SkPaint& paint) {
+ canvas.drawArc(0, 0, size, size, 50, 189, true, paint);
+ },
+ [](TestCanvas& canvas, float size, const SkPaint& paint) {
+ canvas.drawOval(0, 0, size, size, paint);
+ },
+ [](TestCanvas& canvas, float size, const SkPaint& paint) {
+ SkPath diamondPath;
+ diamondPath.moveTo(size / 2, 0);
+ diamondPath.lineTo(size, size / 2);
+ diamondPath.lineTo(size / 2, size);
+ diamondPath.lineTo(0, size / 2);
+ diamondPath.close();
+ canvas.drawPath(diamondPath, paint);
+ },
+ [](TestCanvas& canvas, float size, const SkPaint& paint) {
+ float data[] = {0, 0, size, size, 0, size, size, 0 };
+ canvas.drawLines(data, sizeof(data) / sizeof(float), paint);
+ },
+ [](TestCanvas& canvas, float size, const SkPaint& paint) {
+ float data[] = {0, 0, size, size, 0, size, size, 0 };
+ canvas.drawPoints(data, sizeof(data) / sizeof(float), paint);
+ },
+ [](TestCanvas& canvas, float size, const SkPaint& paint) {
+ canvas.drawRect(0, 0, size, size, paint);
+ },
+ [](TestCanvas& canvas, float size, const SkPaint& paint) {
+ float rad = size / 4;
+ canvas.drawRoundRect(0, 0, size, size, rad, rad, paint);
+ }
+ };
+ float cellSpace = dp(4);
+ float cellSize = floorf(width / 7 - cellSpace);
+
+ // each combination of strokeWidth + style gets a column
+ int outerCount = canvas.save(SaveFlags::MatrixClip);
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ SkPaint::Style styles[] = {
+ SkPaint::kStroke_Style, SkPaint::kFill_Style, SkPaint::kStrokeAndFill_Style };
+ for (auto style : styles) {
+ paint.setStyle(style);
+ for (auto strokeWidth : { 0.0f, 0.5f, 8.0f }) {
+ paint.setStrokeWidth(strokeWidth);
+ // fill column with each op
+ int middleCount = canvas.save(SaveFlags::MatrixClip);
+ for (auto op : ops) {
+ int innerCount = canvas.save(SaveFlags::MatrixClip);
+ canvas.clipRect(0, 0, cellSize, cellSize, SkRegion::kIntersect_Op);
+ canvas.drawColor(Color::White, SkXfermode::Mode::kSrcOver_Mode);
+ op(canvas, cellSize, paint);
+ canvas.restoreToCount(innerCount);
+ canvas.translate(cellSize + cellSpace, 0);
+ }
+ canvas.restoreToCount(middleCount);
+ canvas.translate(0, cellSize + cellSpace);
+ }
+ }
+ canvas.restoreToCount(outerCount);
+ });
+ canvas.drawColor(Color::Grey_500, SkXfermode::Mode::kSrcOver_Mode);
+ canvas.drawRenderNode(card.get());
+ }
+
+ void doFrame(int frameNr) override {
+ card->mutateStagingProperties().setTranslationY(frameNr % 150);
+ card->setPropertyFieldsDirty(RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/common/scenes/TestSceneBase.h b/libs/hwui/tests/common/scenes/TestSceneBase.h
new file mode 100644
index 000000000000..935ddcf9212d
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/TestSceneBase.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 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 TESTS_SCENES_TESTSCENEBASE_H
+#define TESTS_SCENES_TESTSCENEBASE_H
+
+#include "DisplayListCanvas.h"
+#include "RecordingCanvas.h"
+#include "RenderNode.h"
+#include "tests/common/TestContext.h"
+#include "tests/common/TestScene.h"
+#include "tests/common/TestUtils.h"
+#include "utils/Color.h"
+
+#include <functional>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::test;
+
+#endif /* TESTS_SCENES_TESTSCENEBASE_H_ */
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
new file mode 100644
index 000000000000..be8f48b9fd17
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 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 "TestSceneBase.h"
+#include "utils/Color.h"
+
+class TextAnimation;
+
+static TestScene::Registrar _Text(TestScene::Info{
+ "text",
+ "Draws a bunch of text.",
+ TestScene::simpleCreateScene<TextAnimation>
+});
+
+class TextAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ card = TestUtils::createNode(0, 0, width, height,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ SkPaint paint;
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ paint.setAntiAlias(true);
+ paint.setTextSize(50);
+
+ paint.setColor(Color::Black);
+ for (int i = 0; i < 10; i++) {
+ TestUtils::drawUtf8ToCanvas(&canvas, "Test string", paint, 400, i * 100);
+ }
+
+ SkPath path;
+ path.addOval(SkRect::MakeLTRB(100, 100, 300, 300));
+
+ paint.setColor(Color::Blue_500);
+ TestUtils::drawUtf8ToCanvas(&canvas, "This is a neat circle of text!", paint, path);
+ });
+ canvas.drawRenderNode(card.get());
+ }
+
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/how_to_run.txt b/libs/hwui/tests/how_to_run.txt
deleted file mode 100644
index 686cd7878c50..000000000000
--- a/libs/hwui/tests/how_to_run.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-mmm -j8 frameworks/base/libs/hwui/tests/ &&
- adb push $OUT/data/local/tmp/hwuitest /data/local/tmp/hwuitest &&
- adb shell /data/local/tmp/hwuitest
-
-
-Command arguments:
-hwuitest [testname]
-
-Default test is 'shadowgrid'
-
-List of tests:
-
-shadowgrid: creates a grid of rounded rects that cast shadows, high CPU & GPU load
-
-rectgrid: creates a grid of 1x1 rects
-
-oval: draws 1 oval
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
new file mode 100644
index 000000000000..c5af06160b62
--- /dev/null
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 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 "AnimationContext.h"
+#include "RenderNode.h"
+#include "tests/common/TestContext.h"
+#include "tests/common/TestScene.h"
+#include "tests/common/scenes/TestSceneBase.h"
+#include "renderthread/RenderProxy.h"
+#include "renderthread/RenderTask.h"
+
+#include <cutils/log.h>
+#include <gui/Surface.h>
+#include <ui/PixelFormat.h>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::test;
+
+class ContextFactory : public IContextFactory {
+public:
+ virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
+ return new AnimationContext(clock);
+ }
+};
+
+template<class T>
+class ModifiedMovingAverage {
+public:
+ ModifiedMovingAverage(int weight) : mWeight(weight) {}
+
+ T add(T today) {
+ if (!mHasValue) {
+ mAverage = today;
+ } else {
+ mAverage = (((mWeight - 1) * mAverage) + today) / mWeight;
+ }
+ return mAverage;
+ }
+
+ T average() {
+ return mAverage;
+ }
+
+private:
+ bool mHasValue = false;
+ int mWeight;
+ T mAverage;
+};
+
+void run(const TestScene::Info& info, const TestScene::Options& opts) {
+ // Switch to the real display
+ gDisplay = getBuiltInDisplay();
+
+ std::unique_ptr<TestScene> scene(info.createScene(opts));
+
+ TestContext testContext;
+
+ // create the native surface
+ const int width = gDisplay.w;
+ const int height = gDisplay.h;
+ sp<Surface> surface = testContext.surface();
+
+ sp<RenderNode> rootNode = TestUtils::createNode(0, 0, width, height,
+ [&scene, width, height](RenderProperties& props, TestCanvas& canvas) {
+ props.setClipToBounds(false);
+ scene->createContent(width, height, canvas);
+ });
+
+ ContextFactory factory;
+ std::unique_ptr<RenderProxy> proxy(new RenderProxy(false,
+ rootNode.get(), &factory));
+ proxy->loadSystemProperties();
+ proxy->initialize(surface);
+ float lightX = width / 2.0;
+ proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
+ proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
+
+ // Do a few cold runs then reset the stats so that the caches are all hot
+ for (int i = 0; i < 5; i++) {
+ testContext.waitForVsync();
+ nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+ UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+ proxy->syncAndDrawFrame(nullptr);
+ }
+
+ proxy->resetProfileInfo();
+ proxy->fence();
+
+ ModifiedMovingAverage<double> avgMs(opts.reportFrametimeWeight);
+
+ for (int i = 0; i < opts.count; i++) {
+ testContext.waitForVsync();
+ nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+ {
+ ATRACE_NAME("UI-Draw Frame");
+ UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+ scene->doFrame(i);
+ proxy->syncAndDrawFrame(nullptr);
+ }
+ if (opts.reportFrametimeWeight) {
+ proxy->fence();
+ nsecs_t done = systemTime(CLOCK_MONOTONIC);
+ avgMs.add((done - vsync) / 1000000.0);
+ if (i % 10 == 9) {
+ printf("Average frametime %.3fms\n", avgMs.average());
+ }
+ }
+ }
+
+ proxy->dumpProfileInfo(STDOUT_FILENO, DumpFlags::JankStats);
+}
diff --git a/libs/hwui/tests/macrobench/how_to_run.txt b/libs/hwui/tests/macrobench/how_to_run.txt
new file mode 100644
index 000000000000..b051768f3262
--- /dev/null
+++ b/libs/hwui/tests/macrobench/how_to_run.txt
@@ -0,0 +1,5 @@
+mmm -j8 frameworks/base/libs/hwui/ &&
+ adb push $OUT/data/local/tmp/hwuitest /data/local/tmp/hwuitest &&
+ adb shell /data/local/tmp/hwuitest
+
+Pass --help to get help
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
new file mode 100644
index 000000000000..02a39501e647
--- /dev/null
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2014 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 "tests/common/TestScene.h"
+
+#include "protos/hwui.pb.h"
+#include "Properties.h"
+
+#include <getopt.h>
+#include <stdio.h>
+#include <string>
+#include <unistd.h>
+#include <unordered_map>
+#include <vector>
+#include <pthread.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::test;
+
+static int gRepeatCount = 1;
+static std::vector<TestScene::Info> gRunTests;
+static TestScene::Options gOpts;
+
+void run(const TestScene::Info& info, const TestScene::Options& opts);
+
+static void printHelp() {
+ printf(R"(
+USAGE: hwuitest [OPTIONS] <TESTNAME>
+
+OPTIONS:
+ -c, --count=NUM NUM loops a test should run (example, number of frames)
+ -r, --runs=NUM Repeat the test(s) NUM times
+ -h, --help Display this help
+ --list List all tests
+ --wait-for-gpu Set this to wait for the GPU before producing the
+ next frame. Note that without locked clocks this will
+ pathologically bad performance due to large idle time
+ --report-frametime[=weight] If set, the test will print to stdout the
+ moving average frametime. Weight is optional, default is 10
+ --cpuset=name Adds the test to the specified cpuset before running
+ Not supported on all devices and needs root
+)");
+}
+
+static void listTests() {
+ printf("Tests: \n");
+ for (auto&& test : TestScene::testMap()) {
+ auto&& info = test.second;
+ const char* col1 = info.name.c_str();
+ int dlen = info.description.length();
+ const char* col2 = info.description.c_str();
+ // World's best line breaking algorithm.
+ do {
+ int toPrint = dlen;
+ if (toPrint > 50) {
+ char* found = (char*) memrchr(col2, ' ', 50);
+ if (found) {
+ toPrint = found - col2;
+ } else {
+ toPrint = 50;
+ }
+ }
+ printf("%-20s %.*s\n", col1, toPrint, col2);
+ col1 = "";
+ col2 += toPrint;
+ dlen -= toPrint;
+ while (*col2 == ' ') {
+ col2++; dlen--;
+ }
+ } while (dlen > 0);
+ printf("\n");
+ }
+}
+
+static void moveToCpuSet(const char* cpusetName) {
+ if (access("/dev/cpuset/tasks", F_OK)) {
+ fprintf(stderr, "don't have access to cpusets, skipping...\n");
+ return;
+ }
+ static const int BUF_SIZE = 100;
+ char buffer[BUF_SIZE];
+
+ if (snprintf(buffer, BUF_SIZE, "/dev/cpuset/%s/tasks", cpusetName) >= BUF_SIZE) {
+ fprintf(stderr, "Error, cpusetName too large to fit in buffer '%s'\n", cpusetName);
+ return;
+ }
+ int fd = open(buffer, O_WRONLY | O_CLOEXEC);
+ if (fd == -1) {
+ fprintf(stderr, "Error opening file %d\n", errno);
+ return;
+ }
+ pid_t pid = getpid();
+
+ int towrite = snprintf(buffer, BUF_SIZE, "%ld", (long) pid);
+ if (towrite >= BUF_SIZE) {
+ fprintf(stderr, "Buffer wasn't large enough?\n");
+ } else {
+ if (write(fd, buffer, towrite) != towrite) {
+ fprintf(stderr, "Failed to write, errno=%d", errno);
+ }
+ }
+ close(fd);
+}
+
+// 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,
+ List,
+ WaitForGpu,
+ ReportFrametime,
+ CpuSet,
+};
+}
+
+static const struct option LONG_OPTIONS[] = {
+ { "frames", required_argument, nullptr, 'f' },
+ { "repeat", 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 },
+ { 0, 0, 0, 0 }
+};
+
+static const char* SHORT_OPTIONS = "c:r:h";
+
+void parseOptions(int argc, char* argv[]) {
+ int c;
+ bool error = false;
+ opterr = 0;
+
+ while (true) {
+
+ /* getopt_long stores the option index here. */
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, SHORT_OPTIONS, 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::List:
+ listTests();
+ exit(EXIT_SUCCESS);
+ break;
+
+ case 'c':
+ gOpts.count = atoi(optarg);
+ if (!gOpts.count) {
+ fprintf(stderr, "Invalid frames argument '%s'\n", optarg);
+ error = true;
+ }
+ break;
+
+ case 'r':
+ gRepeatCount = atoi(optarg);
+ if (!gRepeatCount) {
+ fprintf(stderr, "Invalid repeat argument '%s'\n", optarg);
+ error = true;
+ } else {
+ gRepeatCount = (gRepeatCount > 0 ? gRepeatCount : INT_MAX);
+ }
+ break;
+
+ case LongOpts::ReportFrametime:
+ if (optarg) {
+ gOpts.reportFrametimeWeight = atoi(optarg);
+ if (!gOpts.reportFrametimeWeight) {
+ fprintf(stderr, "Invalid report frametime weight '%s'\n", optarg);
+ error = true;
+ }
+ } else {
+ gOpts.reportFrametimeWeight = 10;
+ }
+ break;
+
+ case LongOpts::WaitForGpu:
+ Properties::waitForGpuCompletion = true;
+ break;
+
+ case LongOpts::CpuSet:
+ if (!optarg) {
+ error = true;
+ break;
+ }
+ moveToCpuSet(optarg);
+ break;
+
+ case 'h':
+ printHelp();
+ exit(EXIT_SUCCESS);
+ break;
+
+ case '?':
+ fprintf(stderr, "Unrecognized option '%s'\n", argv[optind - 1]);
+ // fall-through
+ default:
+ error = true;
+ break;
+ }
+ }
+
+ if (error) {
+ fprintf(stderr, "Try 'hwuitest --help' for more information.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Print any remaining command line arguments (not options). */
+ if (optind < argc) {
+ do {
+ const char* test = argv[optind++];
+ auto pos = TestScene::testMap().find(test);
+ if (pos == TestScene::testMap().end()) {
+ fprintf(stderr, "Unknown test '%s'\n", test);
+ exit(EXIT_FAILURE);
+ } else {
+ gRunTests.push_back(pos->second);
+ }
+ } while (optind < argc);
+ } else {
+ gRunTests.push_back(TestScene::testMap()["shadowgrid"]);
+ }
+}
+
+int main(int argc, char* argv[]) {
+ // set defaults
+ gOpts.count = 150;
+
+ parseOptions(argc, argv);
+
+ for (int i = 0; i < gRepeatCount; i++) {
+ for (auto&& test : gRunTests) {
+ run(test, gOpts);
+ }
+ }
+ printf("Success!\n");
+ return 0;
+}
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
deleted file mode 100644
index 80d7029857c4..000000000000
--- a/libs/hwui/tests/main.cpp
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright (C) 2014 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 <cutils/log.h>
-#include <gui/Surface.h>
-#include <ui/PixelFormat.h>
-
-#include <AnimationContext.h>
-#include <DisplayListCanvas.h>
-#include <RenderNode.h>
-#include <renderthread/RenderProxy.h>
-#include <renderthread/RenderTask.h>
-
-#include "TestContext.h"
-
-#include <stdio.h>
-#include <unistd.h>
-
-using namespace android;
-using namespace android::uirenderer;
-using namespace android::uirenderer::renderthread;
-using namespace android::uirenderer::test;
-
-class ContextFactory : public IContextFactory {
-public:
- virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
- return new AnimationContext(clock);
- }
-};
-
-static DisplayListCanvas* startRecording(RenderNode* node) {
- DisplayListCanvas* renderer = new DisplayListCanvas();
- renderer->setViewport(node->stagingProperties().getWidth(),
- node->stagingProperties().getHeight());
- renderer->prepare();
- return renderer;
-}
-
-static void endRecording(DisplayListCanvas* renderer, RenderNode* node) {
- renderer->finish();
- node->setStagingDisplayList(renderer->finishRecording());
- delete renderer;
-}
-
-class TreeContentAnimation {
-public:
- virtual ~TreeContentAnimation() {}
- int frameCount = 150;
- virtual int getFrameCount() { return frameCount; }
- virtual void setFrameCount(int fc) {
- if (fc > 0) {
- frameCount = fc;
- }
- }
- virtual void createContent(int width, int height, DisplayListCanvas* renderer) = 0;
- virtual void doFrame(int frameNr) = 0;
-
- template <class T>
- static void run(int frameCount) {
- T animation;
- animation.setFrameCount(frameCount);
-
- TestContext testContext;
-
- // create the native surface
- const int width = gDisplay.w;
- const int height = gDisplay.h;
- sp<Surface> surface = testContext.surface();
-
- RenderNode* rootNode = new RenderNode();
- rootNode->incStrong(nullptr);
- rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, width, height);
- rootNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- rootNode->mutateStagingProperties().setClipToBounds(false);
- rootNode->setPropertyFieldsDirty(RenderNode::GENERIC);
-
- ContextFactory factory;
- std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode, &factory));
- proxy->loadSystemProperties();
- proxy->initialize(surface);
- float lightX = width / 2.0;
- proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
- proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
-
- android::uirenderer::Rect DUMMY;
-
- DisplayListCanvas* renderer = startRecording(rootNode);
- animation.createContent(width, height, renderer);
- endRecording(renderer, rootNode);
-
- // Do a few cold runs then reset the stats so that the caches are all hot
- for (int i = 0; i < 3; i++) {
- testContext.waitForVsync();
- proxy->syncAndDrawFrame();
- }
- proxy->resetProfileInfo();
-
- for (int i = 0; i < animation.getFrameCount(); i++) {
- testContext.waitForVsync();
-
- ATRACE_NAME("UI-Draw Frame");
- nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
- UiFrameInfoBuilder(proxy->frameInfo())
- .setVsync(vsync, vsync);
- animation.doFrame(i);
- proxy->syncAndDrawFrame();
- }
-
- proxy->dumpProfileInfo(STDOUT_FILENO, 0);
- rootNode->decStrong(nullptr);
- }
-};
-
-class ShadowGridAnimation : public TreeContentAnimation {
-public:
- std::vector< sp<RenderNode> > cards;
- void createContent(int width, int height, DisplayListCanvas* renderer) override {
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(true);
-
- for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
- for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
- sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
- renderer->drawRenderNode(card.get());
- cards.push_back(card);
- }
- }
-
- renderer->insertReorderBarrier(false);
- }
- void doFrame(int frameNr) override {
- int curFrame = frameNr % 150;
- for (size_t ci = 0; ci < cards.size(); ci++) {
- cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
- cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
- cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- }
- }
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->mutateStagingProperties().setElevation(dp(16));
- node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
- node->mutateStagingProperties().mutableOutline().setShouldClip(true);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
-
- DisplayListCanvas* renderer = startRecording(node.get());
- renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
- endRecording(renderer, node.get());
- return node;
- }
-};
-
-class ShadowGrid2Animation : public TreeContentAnimation {
-public:
- std::vector< sp<RenderNode> > cards;
- void createContent(int width, int height, DisplayListCanvas* renderer) override {
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(true);
-
- for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
- for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
- sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
- renderer->drawRenderNode(card.get());
- cards.push_back(card);
- }
- }
-
- renderer->insertReorderBarrier(false);
- }
- void doFrame(int frameNr) override {
- int curFrame = frameNr % 150;
- for (size_t ci = 0; ci < cards.size(); ci++) {
- cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
- cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
- cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- }
- }
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->mutateStagingProperties().setElevation(dp(16));
- node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
- node->mutateStagingProperties().mutableOutline().setShouldClip(true);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
-
- DisplayListCanvas* renderer = startRecording(node.get());
- renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
- endRecording(renderer, node.get());
- return node;
- }
-};
-
-class RectGridAnimation : public TreeContentAnimation {
-public:
- sp<RenderNode> card;
- void createContent(int width, int height, DisplayListCanvas* renderer) override {
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(true);
-
- card = createCard(40, 40, 200, 200);
- renderer->drawRenderNode(card.get());
-
- renderer->insertReorderBarrier(false);
- }
- void doFrame(int frameNr) override {
- int curFrame = frameNr % 150;
- card->mutateStagingProperties().setTranslationX(curFrame);
- card->mutateStagingProperties().setTranslationY(curFrame);
- card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- }
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
- DisplayListCanvas* renderer = startRecording(node.get());
- renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
-
- float rects[width * height];
- int index = 0;
- for (int xOffset = 0; xOffset < width; xOffset+=2) {
- for (int yOffset = 0; yOffset < height; yOffset+=2) {
- rects[index++] = xOffset;
- rects[index++] = yOffset;
- rects[index++] = xOffset + 1;
- rects[index++] = yOffset + 1;
- }
- }
- int count = width * height;
-
- SkPaint paint;
- paint.setColor(0xff00ffff);
- renderer->drawRects(rects, count, &paint);
-
- endRecording(renderer, node.get());
- return node;
- }
-};
-
-class OvalAnimation : public TreeContentAnimation {
-public:
- sp<RenderNode> card;
- void createContent(int width, int height, DisplayListCanvas* renderer) override {
- renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- renderer->insertReorderBarrier(true);
-
- card = createCard(40, 40, 400, 400);
- renderer->drawRenderNode(card.get());
-
- renderer->insertReorderBarrier(false);
- }
-
- void doFrame(int frameNr) override {
- int curFrame = frameNr % 150;
- card->mutateStagingProperties().setTranslationX(curFrame);
- card->mutateStagingProperties().setTranslationY(curFrame);
- card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- }
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
- DisplayListCanvas* renderer = startRecording(node.get());
-
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setColor(0xFF000000);
- renderer->drawOval(0, 0, width, height, paint);
-
- endRecording(renderer, node.get());
- return node;
- }
-};
-
-struct cstr_cmp {
- bool operator()(const char *a, const char *b) const {
- return std::strcmp(a, b) < 0;
- }
-};
-
-typedef void (*testProc)(int);
-
-std::map<const char*, testProc, cstr_cmp> gTestMap {
- {"shadowgrid", TreeContentAnimation::run<ShadowGridAnimation>},
- {"shadowgrid2", TreeContentAnimation::run<ShadowGrid2Animation>},
- {"rectgrid", TreeContentAnimation::run<RectGridAnimation> },
- {"oval", TreeContentAnimation::run<OvalAnimation> },
-};
-
-int main(int argc, char* argv[]) {
- const char* testName = argc > 1 ? argv[1] : "shadowgrid";
- testProc proc = gTestMap[testName];
- if(!proc) {
- printf("Error: couldn't find test %s\n", testName);
- return 1;
- }
- int loopCount = 1;
- if (argc > 2) {
- loopCount = atoi(argv[2]);
- if (!loopCount) {
- printf("Invalid loop count!\n");
- return 1;
- }
- }
- int frameCount = 150;
- if (argc > 3) {
- frameCount = atoi(argv[3]);
- if (frameCount < 1) {
- printf("Invalid frame count!\n");
- return 1;
- }
- }
- if (loopCount < 0) {
- loopCount = INT_MAX;
- }
- for (int i = 0; i < loopCount; i++) {
- proc(frameCount);
- }
- printf("Success!\n");
- return 0;
-}
diff --git a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
new file mode 100644
index 000000000000..06b68d1dea8f
--- /dev/null
+++ b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2015 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 <benchmark/benchmark.h>
+
+#include "DisplayList.h"
+#if HWUI_NEW_OPS
+#include "RecordingCanvas.h"
+#else
+#include "DisplayListCanvas.h"
+#endif
+#include "tests/common/TestUtils.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+#if HWUI_NEW_OPS
+typedef RecordingCanvas TestCanvas;
+#else
+typedef DisplayListCanvas TestCanvas;
+#endif
+
+void BM_DisplayList_alloc(benchmark::State& benchState) {
+ while (benchState.KeepRunning()) {
+ auto displayList = new DisplayList();
+ benchmark::DoNotOptimize(displayList);
+ delete displayList;
+ }
+}
+BENCHMARK(BM_DisplayList_alloc);
+
+void BM_DisplayList_alloc_theoretical(benchmark::State& benchState) {
+ while (benchState.KeepRunning()) {
+ auto displayList = new char[sizeof(DisplayList)];
+ benchmark::DoNotOptimize(displayList);
+ delete[] displayList;
+ }
+}
+BENCHMARK(BM_DisplayList_alloc_theoretical);
+
+void BM_DisplayListCanvas_record_empty(benchmark::State& benchState) {
+ TestCanvas canvas(100, 100);
+ delete canvas.finishRecording();
+
+ while (benchState.KeepRunning()) {
+ canvas.resetRecording(100, 100);
+ benchmark::DoNotOptimize(&canvas);
+ delete canvas.finishRecording();
+ }
+}
+BENCHMARK(BM_DisplayListCanvas_record_empty);
+
+void BM_DisplayListCanvas_record_saverestore(benchmark::State& benchState) {
+ TestCanvas canvas(100, 100);
+ delete canvas.finishRecording();
+
+ while (benchState.KeepRunning()) {
+ canvas.resetRecording(100, 100);
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.save(SaveFlags::MatrixClip);
+ benchmark::DoNotOptimize(&canvas);
+ canvas.restore();
+ canvas.restore();
+ delete canvas.finishRecording();
+ }
+}
+BENCHMARK(BM_DisplayListCanvas_record_saverestore);
+
+void BM_DisplayListCanvas_record_translate(benchmark::State& benchState) {
+ TestCanvas canvas(100, 100);
+ delete canvas.finishRecording();
+
+ while (benchState.KeepRunning()) {
+ canvas.resetRecording(100, 100);
+ canvas.scale(10, 10);
+ benchmark::DoNotOptimize(&canvas);
+ delete canvas.finishRecording();
+ }
+}
+BENCHMARK(BM_DisplayListCanvas_record_translate);
+
+/**
+ * Simulate a simple view drawing a background, overlapped by an image.
+ *
+ * Note that the recording commands are intentionally not perfectly efficient, as the
+ * View system frequently produces unneeded save/restores.
+ */
+void BM_DisplayListCanvas_record_simpleBitmapView(benchmark::State& benchState) {
+ TestCanvas canvas(100, 100);
+ delete canvas.finishRecording();
+
+ SkPaint rectPaint;
+ SkBitmap iconBitmap = TestUtils::createSkBitmap(80, 80);
+
+ while (benchState.KeepRunning()) {
+ canvas.resetRecording(100, 100);
+ {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.drawRect(0, 0, 100, 100, rectPaint);
+ canvas.restore();
+ }
+ {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.translate(10, 10);
+ canvas.drawBitmap(iconBitmap, 0, 0, nullptr);
+ canvas.restore();
+ }
+ benchmark::DoNotOptimize(&canvas);
+ delete canvas.finishRecording();
+ }
+}
+BENCHMARK(BM_DisplayListCanvas_record_simpleBitmapView);
+
+class NullClient: public CanvasStateClient {
+ void onViewportInitialized() override {}
+ void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+ GLuint getTargetFbo() const override { return 0; }
+};
+
+void BM_CanvasState_saverestore(benchmark::State& benchState) {
+ NullClient client;
+ CanvasState state(client);
+ state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3());
+
+ while (benchState.KeepRunning()) {
+ state.save(SaveFlags::MatrixClip);
+ state.save(SaveFlags::MatrixClip);
+ benchmark::DoNotOptimize(&state);
+ state.restore();
+ state.restore();
+ }
+}
+BENCHMARK(BM_CanvasState_saverestore);
+
+void BM_CanvasState_init(benchmark::State& benchState) {
+ NullClient client;
+ CanvasState state(client);
+ state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3());
+
+ while (benchState.KeepRunning()) {
+ state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3());
+ benchmark::DoNotOptimize(&state);
+ }
+}
+BENCHMARK(BM_CanvasState_init);
+
+void BM_CanvasState_translate(benchmark::State& benchState) {
+ NullClient client;
+ CanvasState state(client);
+ state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3());
+
+ while (benchState.KeepRunning()) {
+ state.translate(5, 5, 0);
+ benchmark::DoNotOptimize(&state);
+ state.translate(-5, -5, 0);
+ }
+}
+BENCHMARK(BM_CanvasState_translate);
diff --git a/libs/hwui/tests/microbench/FontBench.cpp b/libs/hwui/tests/microbench/FontBench.cpp
new file mode 100644
index 000000000000..df3d041e722d
--- /dev/null
+++ b/libs/hwui/tests/microbench/FontBench.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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 <benchmark/benchmark.h>
+
+#include "GammaFontRenderer.h"
+#include "tests/common/TestUtils.h"
+
+#include <SkPaint.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+void BM_FontRenderer_precache_cachehits(benchmark::State& state) {
+ TestUtils::runOnRenderThread([&state](renderthread::RenderThread& thread) {
+ SkPaint paint;
+ paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ GammaFontRenderer gammaFontRenderer;
+ FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer();
+ fontRenderer.setFont(&paint, SkMatrix::I());
+
+ std::vector<glyph_t> glyphs;
+ std::vector<float> positions;
+ float totalAdvance;
+ uirenderer::Rect bounds;
+ TestUtils::layoutTextUnscaled(paint, "This is a test",
+ &glyphs, &positions, &totalAdvance, &bounds);
+
+ fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I());
+
+ while (state.KeepRunning()) {
+ fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I());
+ }
+ });
+}
+BENCHMARK(BM_FontRenderer_precache_cachehits);
diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
new file mode 100644
index 000000000000..84ef9c2575f5
--- /dev/null
+++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 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 <benchmark/benchmark.h>
+
+#include "BakedOpState.h"
+#include "BakedOpDispatcher.h"
+#include "BakedOpRenderer.h"
+#include "FrameBuilder.h"
+#include "LayerUpdateQueue.h"
+#include "RecordedOp.h"
+#include "RecordingCanvas.h"
+#include "tests/common/TestContext.h"
+#include "tests/common/TestScene.h"
+#include "tests/common/TestUtils.h"
+#include "Vector.h"
+
+#include <vector>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::test;
+
+const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50};
+const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 };
+
+static sp<RenderNode> createTestNode() {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
+ SkPaint paint;
+
+ // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
+ // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
+ canvas.save(SaveFlags::MatrixClip);
+ for (int i = 0; i < 30; i++) {
+ canvas.translate(0, 10);
+ canvas.drawRect(0, 0, 10, 10, paint);
+ canvas.drawBitmap(bitmap, 5, 0, nullptr);
+ }
+ canvas.restore();
+ });
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+ return node;
+}
+
+void BM_FrameBuilder_defer(benchmark::State& state) {
+ TestUtils::runOnRenderThread([&state](RenderThread& thread) {
+ auto node = createTestNode();
+ while (state.KeepRunning()) {
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*node);
+ benchmark::DoNotOptimize(&frameBuilder);
+ }
+ });
+}
+BENCHMARK(BM_FrameBuilder_defer);
+
+void BM_FrameBuilder_deferAndRender(benchmark::State& state) {
+ TestUtils::runOnRenderThread([&state](RenderThread& thread) {
+ auto node = createTestNode();
+
+ RenderState& renderState = thread.renderState();
+ Caches& caches = Caches::getInstance();
+
+ while (state.KeepRunning()) {
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200,
+ sLightGeometry, caches);
+ frameBuilder.deferRenderNode(*node);
+
+ BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
+ frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
+ benchmark::DoNotOptimize(&renderer);
+ }
+ });
+}
+BENCHMARK(BM_FrameBuilder_deferAndRender);
+
+static sp<RenderNode> getSyncedSceneNode(const char* sceneName) {
+ gDisplay = getBuiltInDisplay(); // switch to real display if present
+
+ TestContext testContext;
+ TestScene::Options opts;
+ std::unique_ptr<TestScene> scene(TestScene::testMap()[sceneName].createScene(opts));
+
+ sp<RenderNode> rootNode = TestUtils::createNode(0, 0, gDisplay.w, gDisplay.h,
+ [&scene](RenderProperties& props, TestCanvas& canvas) {
+ scene->createContent(gDisplay.w, gDisplay.h, canvas);
+ });
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode);
+ return rootNode;
+}
+
+static auto SCENES = {
+ "listview",
+};
+
+void BM_FrameBuilder_defer_scene(benchmark::State& state) {
+ TestUtils::runOnRenderThread([&state](RenderThread& thread) {
+ const char* sceneName = *(SCENES.begin() + state.range_x());
+ state.SetLabel(sceneName);
+ auto node = getSyncedSceneNode(sceneName);
+ while (state.KeepRunning()) {
+ FrameBuilder frameBuilder(SkRect::MakeWH(gDisplay.w, gDisplay.h),
+ gDisplay.w, gDisplay.h,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*node);
+ benchmark::DoNotOptimize(&frameBuilder);
+ }
+ });
+}
+BENCHMARK(BM_FrameBuilder_defer_scene)->DenseRange(0, SCENES.size() - 1);
+
+void BM_FrameBuilder_deferAndRender_scene(benchmark::State& state) {
+ TestUtils::runOnRenderThread([&state](RenderThread& thread) {
+ const char* sceneName = *(SCENES.begin() + state.range_x());
+ state.SetLabel(sceneName);
+ auto node = getSyncedSceneNode(sceneName);
+
+ RenderState& renderState = thread.renderState();
+ Caches& caches = Caches::getInstance();
+
+ while (state.KeepRunning()) {
+ FrameBuilder frameBuilder(SkRect::MakeWH(gDisplay.w, gDisplay.h),
+ gDisplay.w, gDisplay.h,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*node);
+
+ BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
+ frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
+ benchmark::DoNotOptimize(&renderer);
+ }
+ });
+}
+BENCHMARK(BM_FrameBuilder_deferAndRender_scene)->DenseRange(0, SCENES.size() - 1);
diff --git a/libs/hwui/tests/microbench/LinearAllocatorBench.cpp b/libs/hwui/tests/microbench/LinearAllocatorBench.cpp
new file mode 100644
index 000000000000..3c0a6c5ae8ac
--- /dev/null
+++ b/libs/hwui/tests/microbench/LinearAllocatorBench.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 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 <benchmark/benchmark.h>
+
+#include "utils/LinearAllocator.h"
+
+#include <vector>
+
+using namespace android;
+using namespace android::uirenderer;
+
+static void BM_LinearStdAllocator_vectorBaseline(benchmark::State& state) {
+ while (state.KeepRunning()) {
+ std::vector<char> v;
+ for (int j = 0; j < 200; j++) {
+ v.push_back(j);
+ }
+ benchmark::DoNotOptimize(&v);
+ }
+}
+BENCHMARK(BM_LinearStdAllocator_vectorBaseline);
+
+static void BM_LinearStdAllocator_vector(benchmark::State& state) {
+ while (state.KeepRunning()) {
+ LinearAllocator la;
+ LinearStdAllocator<void*> stdAllocator(la);
+ std::vector<char, LinearStdAllocator<char> > v(stdAllocator);
+ for (int j = 0; j < 200; j++) {
+ v.push_back(j);
+ }
+ benchmark::DoNotOptimize(&v);
+ }
+}
+BENCHMARK(BM_LinearStdAllocator_vector);
diff --git a/libs/hwui/tests/microbench/PathParserBench.cpp b/libs/hwui/tests/microbench/PathParserBench.cpp
new file mode 100644
index 000000000000..b43c4c3b63c1
--- /dev/null
+++ b/libs/hwui/tests/microbench/PathParserBench.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 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 <benchmark/benchmark.h>
+
+#include "PathParser.h"
+#include "VectorDrawable.h"
+
+#include <SkPath.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+static const char* sPathString = "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10";
+
+void BM_PathParser_parseStringPathForSkPath(benchmark::State& state) {
+ SkPath skPath;
+ size_t length = strlen(sPathString);
+ PathParser::ParseResult result;
+ while (state.KeepRunning()) {
+ PathParser::parseAsciiStringForSkPath(&skPath, &result, sPathString, length);
+ benchmark::DoNotOptimize(&result);
+ benchmark::DoNotOptimize(&skPath);
+ }
+}
+BENCHMARK(BM_PathParser_parseStringPathForSkPath);
+
+void BM_PathParser_parseStringPathForPathData(benchmark::State& state) {
+ size_t length = strlen(sPathString);
+ PathData outData;
+ PathParser::ParseResult result;
+ while (state.KeepRunning()) {
+ PathParser::getPathDataFromAsciiString(&outData, &result, sPathString, length);
+ benchmark::DoNotOptimize(&result);
+ benchmark::DoNotOptimize(&outData);
+ }
+}
+BENCHMARK(BM_PathParser_parseStringPathForPathData);
diff --git a/libs/hwui/tests/microbench/ShadowBench.cpp b/libs/hwui/tests/microbench/ShadowBench.cpp
new file mode 100644
index 000000000000..a0fc6e8f9f53
--- /dev/null
+++ b/libs/hwui/tests/microbench/ShadowBench.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 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 <benchmark/benchmark.h>
+
+#include "Matrix.h"
+#include "Rect.h"
+#include "Vector.h"
+#include "VertexBuffer.h"
+#include "TessellationCache.h"
+
+#include <SkPath.h>
+
+#include <memory>
+
+using namespace android;
+using namespace android::uirenderer;
+
+struct ShadowTestData {
+ Matrix4 drawTransform;
+ Rect localClip;
+ Matrix4 casterTransformXY;
+ Matrix4 casterTransformZ;
+ Vector3 lightCenter;
+ float lightRadius;
+};
+
+void createShadowTestData(ShadowTestData* out) {
+ static float SAMPLE_DRAW_TRANSFORM[] = {
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1,
+ };
+ static float SAMPLE_CASTERXY[] = {
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 32, 32, 0, 1,
+ };
+ static float SAMPLE_CASTERZ[] = {
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 32, 32, 32, 1,
+ };
+ static Rect SAMPLE_CLIP(0, 0, 1536, 2048);
+ static Vector3 SAMPLE_LIGHT_CENTER{768, -400, 1600};
+ static float SAMPLE_LIGHT_RADIUS = 1600;
+
+ out->drawTransform.load(SAMPLE_DRAW_TRANSFORM);
+ out->localClip = SAMPLE_CLIP;
+ out->casterTransformXY.load(SAMPLE_CASTERXY);
+ out->casterTransformZ.load(SAMPLE_CASTERZ);
+ out->lightCenter = SAMPLE_LIGHT_CENTER;
+ out->lightRadius = SAMPLE_LIGHT_RADIUS;
+}
+
+static inline void tessellateShadows(ShadowTestData& testData, bool opaque,
+ const SkPath& shape, VertexBuffer* ambient, VertexBuffer* spot) {
+ tessellateShadows(&testData.drawTransform, &testData.localClip,
+ opaque, &shape, &testData.casterTransformXY,
+ &testData.casterTransformZ, testData.lightCenter,
+ testData.lightRadius, *ambient, *spot);
+}
+
+void BM_TessellateShadows_roundrect_opaque(benchmark::State& state) {
+ ShadowTestData shadowData;
+ createShadowTestData(&shadowData);
+ SkPath path;
+ path.addRoundRect(SkRect::MakeWH(100, 100), 5, 5);
+
+ while (state.KeepRunning()) {
+ VertexBuffer ambient;
+ VertexBuffer spot;
+ tessellateShadows(shadowData, true, path, &ambient, &spot);
+ benchmark::DoNotOptimize(&ambient);
+ benchmark::DoNotOptimize(&spot);
+ }
+}
+BENCHMARK(BM_TessellateShadows_roundrect_opaque);
+
+void BM_TessellateShadows_roundrect_translucent(benchmark::State& state) {
+ ShadowTestData shadowData;
+ createShadowTestData(&shadowData);
+ SkPath path;
+ path.reset();
+ path.addRoundRect(SkRect::MakeLTRB(0, 0, 100, 100), 5, 5);
+
+ while (state.KeepRunning()) {
+ std::unique_ptr<VertexBuffer> ambient(new VertexBuffer);
+ std::unique_ptr<VertexBuffer> spot(new VertexBuffer);
+ tessellateShadows(shadowData, false, path, ambient.get(), spot.get());
+ benchmark::DoNotOptimize(ambient.get());
+ benchmark::DoNotOptimize(spot.get());
+ }
+}
+BENCHMARK(BM_TessellateShadows_roundrect_translucent);
diff --git a/libs/hwui/tests/microbench/TaskManagerBench.cpp b/libs/hwui/tests/microbench/TaskManagerBench.cpp
new file mode 100644
index 000000000000..c6b9f3bca55f
--- /dev/null
+++ b/libs/hwui/tests/microbench/TaskManagerBench.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 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 <benchmark/benchmark.h>
+
+#include "thread/Task.h"
+#include "thread/TaskManager.h"
+#include "thread/TaskProcessor.h"
+
+#include <vector>
+
+using namespace android;
+using namespace android::uirenderer;
+
+class TrivialTask : public Task<char> {};
+
+class TrivialProcessor : public TaskProcessor<char> {
+public:
+ TrivialProcessor(TaskManager* manager)
+ : TaskProcessor(manager) {}
+ virtual ~TrivialProcessor() {}
+ virtual void onProcess(const sp<Task<char> >& task) override {
+ TrivialTask* t = static_cast<TrivialTask*>(task.get());
+ t->setResult(reinterpret_cast<intptr_t>(t) % 16 == 0 ? 'a' : 'b');
+ }
+};
+
+void BM_TaskManager_allocateTask(benchmark::State& state) {
+ std::vector<sp<TrivialTask> > tasks;
+ tasks.reserve(state.max_iterations);
+
+ while (state.KeepRunning()) {
+ tasks.emplace_back(new TrivialTask);
+ benchmark::DoNotOptimize(tasks.back());
+ }
+}
+BENCHMARK(BM_TaskManager_allocateTask);
+
+void BM_TaskManager_enqueueTask(benchmark::State& state) {
+ TaskManager taskManager;
+ sp<TrivialProcessor> processor(new TrivialProcessor(&taskManager));
+ std::vector<sp<TrivialTask> > tasks;
+ tasks.reserve(state.max_iterations);
+
+ while (state.KeepRunning()) {
+ tasks.emplace_back(new TrivialTask);
+ benchmark::DoNotOptimize(tasks.back());
+ processor->add(tasks.back());
+ }
+
+ for (sp<TrivialTask>& task : tasks) {
+ task->getResult();
+ }
+}
+BENCHMARK(BM_TaskManager_enqueueTask);
+
+void BM_TaskManager_enqueueRunDeleteTask(benchmark::State& state) {
+ TaskManager taskManager;
+ sp<TrivialProcessor> processor(new TrivialProcessor(&taskManager));
+ std::vector<sp<TrivialTask> > tasks;
+ tasks.reserve(state.max_iterations);
+
+ while (state.KeepRunning()) {
+ tasks.emplace_back(new TrivialTask);
+ benchmark::DoNotOptimize(tasks.back());
+ processor->add(tasks.back());
+ }
+ state.ResumeTiming();
+ for (sp<TrivialTask>& task : tasks) {
+ benchmark::DoNotOptimize(task->getResult());
+ }
+ tasks.clear();
+ state.PauseTiming();
+}
+BENCHMARK(BM_TaskManager_enqueueRunDeleteTask);
diff --git a/libs/hwui/tests/microbench/how_to_run.txt b/libs/hwui/tests/microbench/how_to_run.txt
new file mode 100755
index 000000000000..e6f80b278276
--- /dev/null
+++ b/libs/hwui/tests/microbench/how_to_run.txt
@@ -0,0 +1,4 @@
+mmm -j8 frameworks/base/libs/hwui &&
+adb push $ANDROID_PRODUCT_OUT/data/local/tmp/hwuimicro \
+ /data/local/tmp/hwuimicro &&
+ adb shell /data/local/tmp/hwuimicro
diff --git a/libs/hwui/unit_tests/main.cpp b/libs/hwui/tests/microbench/main.cpp
index c9b96360b36b..a0157bc4f9ef 100644
--- a/libs/hwui/unit_tests/main.cpp
+++ b/libs/hwui/tests/microbench/main.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
+#include <benchmark/benchmark.h>
-int main(int argc, char **argv) {
- ::testing::InitGoogleTest(&argc, argv);
- return RUN_ALL_TESTS();
-}
+BENCHMARK_MAIN();
diff --git a/libs/hwui/tests/scripts/prep_volantis.sh b/libs/hwui/tests/scripts/prep_volantis.sh
new file mode 100755
index 000000000000..0572ee55c9b9
--- /dev/null
+++ b/libs/hwui/tests/scripts/prep_volantis.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+# Copyright (C) 2015 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.
+
+adb root
+adb wait-for-device
+adb shell stop mpdecision
+adb shell stop perfd
+adb shell stop
+for pid in $( adb shell ps | awk '{ if ( $9 == "surfaceflinger" ) { print $2 } }' ); do
+ adb shell kill $pid
+done
+adb shell setprop debug.egl.traceGpuCompletion 1
+adb shell daemonize surfaceflinger
+sleep 3
+adb shell setprop service.bootanim.exit 1
+
+# cpu possible frequencies
+# 204000 229500 255000 280500 306000 331500 357000 382500 408000 433500 459000
+# 484500 510000 535500 561000 586500 612000 637500 663000 688500 714000 739500
+# 765000 790500 816000 841500 867000 892500 918000 943500 969000 994500 1020000
+# 1122000 1224000 1326000 1428000 1530000 1632000 1734000 1836000 1938000
+# 2014500 2091000 2193000 2295000 2397000 2499000
+
+S=1326000
+echo "set cpu $cpu to $S hz";
+adb shell "echo userspace > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"
+adb shell "echo $S > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"
+adb shell "echo $S > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq"
+adb shell "echo $S > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed"
+
+#disable hotplug
+adb shell "echo 0 > /sys/devices/system/cpu/cpuquiet/tegra_cpuquiet/enable"
+
+# gbus possible rates
+# 72000 108000 180000 252000 324000 396000 468000 540000 612000 648000
+# 684000 708000 756000 804000 852000 (kHz)
+
+S=324000000
+echo "set gpu to $S hz"
+adb shell "echo 1 > /d/clock/override.gbus/state"
+adb shell "echo $S > /d/clock/override.gbus/rate"
diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
new file mode 100644
index 000000000000..de57cd1e9650
--- /dev/null
+++ b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+
+#include <RecordedOp.h>
+#include <BakedOpDispatcher.h>
+#include <BakedOpRenderer.h>
+#include <tests/common/TestUtils.h>
+
+#include <SkDashPathEffect.h>
+
+using namespace android::uirenderer;
+
+static BakedOpRenderer::LightInfo sLightInfo;
+static Rect sBaseClip(100, 100);
+
+class ValidatingBakedOpRenderer : public BakedOpRenderer {
+public:
+ ValidatingBakedOpRenderer(RenderState& renderState, std::function<void(const Glop& glop)> validator)
+ : BakedOpRenderer(Caches::getInstance(), renderState, true, sLightInfo)
+ , mValidator(validator) {
+ mGlopReceiver = ValidatingGlopReceiver;
+ }
+private:
+ static void ValidatingGlopReceiver(BakedOpRenderer& renderer, const Rect* dirtyBounds,
+ const ClipBase* clip, const Glop& glop) {
+
+ auto vbor = reinterpret_cast<ValidatingBakedOpRenderer*>(&renderer);
+ vbor->mValidator(glop);
+ }
+ std::function<void(const Glop& glop)> mValidator;
+};
+
+typedef void (*BakedOpReceiver)(BakedOpRenderer&, const BakedOpState&);
+
+static void testUnmergedGlopDispatch(renderthread::RenderThread& renderThread, RecordedOp* op,
+ std::function<void(const Glop& glop)> glopVerifier) {
+ // Create op, and wrap with basic state.
+ LinearAllocator allocator;
+ auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), sBaseClip);
+ auto state = BakedOpState::tryConstruct(allocator, *snapshot, *op);
+ ASSERT_NE(nullptr, state);
+
+ int glopCount = 0;
+ auto glopReceiver = [&glopVerifier, &glopCount] (const Glop& glop) {
+ ASSERT_EQ(glopCount++, 0) << "Only one Glop expected";
+ glopVerifier(glop);
+ };
+ ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver);
+
+ // Dispatch based on op type created, similar to Frame/LayerBuilder dispatch behavior
+#define X(Type) \
+ [](BakedOpRenderer& renderer, const BakedOpState& state) { \
+ BakedOpDispatcher::on##Type(renderer, static_cast<const Type&>(*(state.op)), state); \
+ },
+ static BakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X);
+#undef X
+ unmergedReceivers[op->opId](renderer, *state);
+ ASSERT_EQ(1, glopCount) << "Exactly one Glop expected";
+}
+
+RENDERTHREAD_TEST(BakedOpDispatcher, pathTexture_positionOvalArc) {
+ SkPaint strokePaint;
+ strokePaint.setStyle(SkPaint::kStroke_Style);
+ strokePaint.setStrokeWidth(4);
+
+ float intervals[] = {1.0f, 1.0f};
+ auto dashEffect = SkDashPathEffect::Create(intervals, 2, 0);
+ strokePaint.setPathEffect(dashEffect);
+ dashEffect->unref();
+
+ auto textureGlopVerifier = [] (const Glop& glop) {
+ // validate glop produced by renderPathTexture (so texture, unit quad)
+ auto texture = glop.fill.texture.texture;
+ ASSERT_NE(nullptr, texture);
+ float expectedOffset = floor(4 * 1.5f + 0.5f);
+ EXPECT_EQ(expectedOffset, reinterpret_cast<PathTexture*>(texture)->offset)
+ << "Should see conservative offset from PathCache::computeBounds";
+ Rect expectedBounds(10, 15, 20, 25);
+ expectedBounds.outset(expectedOffset);
+
+ Matrix4 expectedModelView;
+ expectedModelView.loadTranslate(10 - expectedOffset, 15 - expectedOffset, 0);
+ expectedModelView.scale(10 + 2 * expectedOffset, 10 + 2 * expectedOffset, 1);
+ EXPECT_EQ(expectedModelView, glop.transform.modelView)
+ << "X and Y offsets, and scale both applied to model view";
+ };
+
+ // Arc and Oval will render functionally the same glop, differing only in texture content
+ ArcOp arcOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint, 0, 270, true);
+ testUnmergedGlopDispatch(renderThread, &arcOp, textureGlopVerifier);
+
+ OvalOp ovalOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint);
+ testUnmergedGlopDispatch(renderThread, &ovalOp, textureGlopVerifier);
+}
+
+RENDERTHREAD_TEST(BakedOpDispatcher, onLayerOp_bufferless) {
+ SkPaint layerPaint;
+ layerPaint.setAlpha(128);
+ OffscreenBuffer* buffer = nullptr; // no providing a buffer, should hit rect fallback case
+ LayerOp op(Rect(10, 10), Matrix4::identity(), nullptr, &layerPaint, &buffer);
+ testUnmergedGlopDispatch(renderThread, &op, [&renderThread] (const Glop& glop) {
+ // rect glop is dispatched with paint props applied
+ EXPECT_EQ(renderThread.renderState().meshState().getUnitQuadVBO(),
+ glop.mesh.vertices.bufferObject) << "Unit quad should be drawn";
+ EXPECT_EQ(nullptr, glop.fill.texture.texture) << "Should be no texture when layer is null";
+ EXPECT_FLOAT_EQ(128 / 255.0f, glop.fill.color.a) << "Rect quad should use op alpha";
+ });
+}
+
+static int getGlopTransformFlags(renderthread::RenderThread& renderThread, RecordedOp* op) {
+ int result = 0;
+ testUnmergedGlopDispatch(renderThread, op, [&result] (const Glop& glop) {
+ result = glop.transform.transformFlags;
+ });
+ return result;
+}
+
+RENDERTHREAD_TEST(BakedOpDispatcher, offsetFlags) {
+ Rect bounds(10, 15, 20, 25);
+ SkPaint paint;
+ SkPaint aaPaint;
+ aaPaint.setAntiAlias(true);
+
+ RoundRectOp roundRectOp(bounds, Matrix4::identity(), nullptr, &paint, 0, 270);
+ EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &roundRectOp))
+ << "Expect no offset for round rect op.";
+
+ const float points[4] = {0.5, 0.5, 1.0, 1.0};
+ PointsOp antiAliasedPointsOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4);
+ EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedPointsOp))
+ << "Expect no offset for AA points.";
+ PointsOp pointsOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4);
+ EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &pointsOp))
+ << "Expect an offset for non-AA points.";
+
+ LinesOp antiAliasedLinesOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4);
+ EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedLinesOp))
+ << "Expect no offset for AA lines.";
+ LinesOp linesOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4);
+ EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &linesOp))
+ << "Expect an offset for non-AA lines.";
+} \ No newline at end of file
diff --git a/libs/hwui/tests/unit/BakedOpRendererTests.cpp b/libs/hwui/tests/unit/BakedOpRendererTests.cpp
new file mode 100644
index 000000000000..59bd75ef6f62
--- /dev/null
+++ b/libs/hwui/tests/unit/BakedOpRendererTests.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+
+#include <BakedOpRenderer.h>
+#include <tests/common/TestUtils.h>
+
+using namespace android::uirenderer;
+
+const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 };
+
+RENDERTHREAD_TEST(BakedOpRenderer, startRepaintLayer_clear) {
+ BakedOpRenderer renderer(Caches::getInstance(), renderThread.renderState(), true, sLightInfo);
+ OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200u, 200u);
+
+ layer.dirty(Rect(200, 200));
+ {
+ renderer.startRepaintLayer(&layer, Rect(200, 200));
+ EXPECT_TRUE(layer.region.isEmpty()) << "Repaint full layer should clear region";
+ renderer.endLayer();
+ }
+
+ layer.dirty(Rect(200, 200));
+ {
+ renderer.startRepaintLayer(&layer, Rect(100, 200)); // repainting left side
+ EXPECT_TRUE(layer.region.isRect());
+ //ALOGD("bounds %d %d %d %d", RECT_ARGS(layer.region.getBounds()));
+ EXPECT_EQ(android::Rect(100, 0, 200, 200), layer.region.getBounds())
+ << "Left side being repainted, so right side should be clear";
+ renderer.endLayer();
+ }
+
+ // right side is now only dirty portion
+ {
+ renderer.startRepaintLayer(&layer, Rect(100, 0, 200, 200)); // repainting right side
+ EXPECT_TRUE(layer.region.isEmpty())
+ << "Now right side being repainted, so region should be entirely clear";
+ renderer.endLayer();
+ }
+}
diff --git a/libs/hwui/tests/unit/BakedOpStateTests.cpp b/libs/hwui/tests/unit/BakedOpStateTests.cpp
new file mode 100644
index 000000000000..0f8e0471556f
--- /dev/null
+++ b/libs/hwui/tests/unit/BakedOpStateTests.cpp
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include <BakedOpState.h>
+#include <ClipArea.h>
+#include <RecordedOp.h>
+#include <tests/common/TestUtils.h>
+
+namespace android {
+namespace uirenderer {
+
+TEST(ResolvedRenderState, construct) {
+ LinearAllocator allocator;
+ Matrix4 translate10x20;
+ translate10x20.loadTranslate(10, 20, 0);
+
+ SkPaint paint;
+ ClipRect clip(Rect(100, 200));
+ RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, &clip, &paint);
+ {
+ // recorded with transform, no parent transform
+ auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
+ ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
+ EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20);
+ EXPECT_EQ(Rect(100, 200), state.clipRect());
+ EXPECT_EQ(Rect(40, 60, 100, 200), state.clippedBounds); // translated and also clipped
+ EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags);
+ }
+ {
+ // recorded with transform and parent transform
+ auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
+ ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
+
+ Matrix4 expectedTranslate;
+ expectedTranslate.loadTranslate(20, 40, 0);
+ EXPECT_MATRIX_APPROX_EQ(expectedTranslate, state.transform);
+
+ // intersection of parent & transformed child clip
+ EXPECT_EQ(Rect(10, 20, 100, 200), state.clipRect());
+
+ // translated and also clipped
+ EXPECT_EQ(Rect(50, 80, 100, 200), state.clippedBounds);
+ EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags);
+ }
+}
+
+TEST(ResolvedRenderState, computeLocalSpaceClip) {
+ LinearAllocator allocator;
+ Matrix4 translate10x20;
+ translate10x20.loadTranslate(10, 20, 0);
+
+ SkPaint paint;
+ ClipRect clip(Rect(100, 200));
+ RectOp recordedOp(Rect(1000, 1000), translate10x20, &clip, &paint);
+ {
+ // recorded with transform, no parent transform
+ auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
+ ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
+ EXPECT_EQ(Rect(-10, -20, 90, 180), state.computeLocalSpaceClip())
+ << "Local clip rect should be 100x200, offset by -10,-20";
+ }
+ {
+ // recorded with transform + parent transform
+ auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
+ ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
+ EXPECT_EQ(Rect(-10, -20, 80, 160), state.computeLocalSpaceClip())
+ << "Local clip rect should be 90x190, offset by -10,-20";
+ }
+}
+
+const float HAIRLINE = 0.0f;
+
+// Note: bounds will be conservative, but not precise for non-hairline
+// - use approx bounds checks for these
+const float SEMI_HAIRLINE = 0.3f;
+
+struct StrokeTestCase {
+ float scale;
+ float strokeWidth;
+ const std::function<void(const ResolvedRenderState&)> validator;
+};
+
+const static StrokeTestCase sStrokeTestCases[] = {
+ {
+ 1, HAIRLINE, [](const ResolvedRenderState& state) {
+ EXPECT_EQ(Rect(49.5f, 49.5f, 150.5f, 150.5f), state.clippedBounds);
+ }
+ },
+ {
+ 1, SEMI_HAIRLINE, [](const ResolvedRenderState& state) {
+ EXPECT_TRUE(state.clippedBounds.contains(49.5f, 49.5f, 150.5f, 150.5f));
+ EXPECT_TRUE(Rect(49, 49, 151, 151).contains(state.clippedBounds));
+ }
+ },
+ {
+ 1, 20, [](const ResolvedRenderState& state) {
+ EXPECT_EQ(Rect(40, 40, 160, 160), state.clippedBounds);
+ }
+ },
+
+ // 3x3 scale:
+ {
+ 3, HAIRLINE, [](const ResolvedRenderState& state) {
+ EXPECT_EQ(Rect(149.5f, 149.5f, 200, 200), state.clippedBounds);
+ EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags);
+ }
+ },
+ {
+ 3, SEMI_HAIRLINE, [](const ResolvedRenderState& state) {
+ EXPECT_TRUE(state.clippedBounds.contains(149.5f, 149.5f, 200, 200));
+ EXPECT_TRUE(Rect(149, 149, 200, 200).contains(state.clippedBounds));
+ }
+ },
+ {
+ 3, 20, [](const ResolvedRenderState& state) {
+ EXPECT_TRUE(state.clippedBounds.contains(120, 120, 200, 200));
+ EXPECT_TRUE(Rect(119, 119, 200, 200).contains(state.clippedBounds));
+ }
+ },
+
+ // 0.5f x 0.5f scale
+ {
+ 0.5f, HAIRLINE, [](const ResolvedRenderState& state) {
+ EXPECT_EQ(Rect(24.5f, 24.5f, 75.5f, 75.5f), state.clippedBounds);
+ }
+ },
+ {
+ 0.5f, SEMI_HAIRLINE, [](const ResolvedRenderState& state) {
+ EXPECT_TRUE(state.clippedBounds.contains(24.5f, 24.5f, 75.5f, 75.5f));
+ EXPECT_TRUE(Rect(24, 24, 76, 76).contains(state.clippedBounds));
+ }
+ },
+ {
+ 0.5f, 20, [](const ResolvedRenderState& state) {
+ EXPECT_TRUE(state.clippedBounds.contains(19.5f, 19.5f, 80.5f, 80.5f));
+ EXPECT_TRUE(Rect(19, 19, 81, 81).contains(state.clippedBounds));
+ }
+ }
+};
+
+TEST(ResolvedRenderState, construct_expandForStroke) {
+ LinearAllocator allocator;
+ // Loop over table of test cases and verify different combinations of stroke width and transform
+ for (auto&& testCase : sStrokeTestCases) {
+ SkPaint strokedPaint;
+ strokedPaint.setAntiAlias(true);
+ strokedPaint.setStyle(SkPaint::kStroke_Style);
+ strokedPaint.setStrokeWidth(testCase.strokeWidth);
+
+ ClipRect clip(Rect(200, 200));
+ RectOp recordedOp(Rect(50, 50, 150, 150),
+ Matrix4::identity(), &clip, &strokedPaint);
+
+ Matrix4 snapshotMatrix;
+ snapshotMatrix.loadScale(testCase.scale, testCase.scale, 1);
+ auto parentSnapshot = TestUtils::makeSnapshot(snapshotMatrix, Rect(200, 200));
+
+ ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, true);
+ testCase.validator(state);
+ }
+}
+
+TEST(BakedOpState, tryConstruct) {
+ Matrix4 translate100x0;
+ translate100x0.loadTranslate(100, 0, 0);
+
+ SkPaint paint;
+ ClipRect clip(Rect(100, 200));
+
+ LinearAllocator allocator;
+ RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), &clip, &paint);
+ auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
+ EXPECT_NE(nullptr, BakedOpState::tryConstruct(allocator, *snapshot, successOp))
+ << "successOp NOT rejected by clip, so should be constructed";
+ size_t successAllocSize = allocator.usedSize();
+ EXPECT_LE(64u, successAllocSize) << "relatively large alloc for non-rejected op";
+
+ RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, &clip, &paint);
+ EXPECT_EQ(nullptr, BakedOpState::tryConstruct(allocator, *snapshot, rejectOp))
+ << "rejectOp rejected by clip, so should not be constructed";
+
+ // NOTE: this relies on the clip having already been serialized by the op above
+ EXPECT_EQ(successAllocSize, allocator.usedSize()) << "no extra allocation used for rejected op";
+}
+
+TEST(BakedOpState, tryShadowOpConstruct) {
+ Matrix4 translate10x20;
+ translate10x20.loadTranslate(10, 20, 0);
+
+ LinearAllocator allocator;
+ {
+ auto snapshot = TestUtils::makeSnapshot(translate10x20, Rect()); // Note: empty clip
+ BakedOpState* bakedState = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
+
+ EXPECT_EQ(nullptr, bakedState) << "op should be rejected by clip, so not constructed";
+ EXPECT_EQ(0u, allocator.usedSize()) << "no serialization, even for clip,"
+ "since op is quick rejected based on snapshot clip";
+ }
+ {
+ auto snapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
+ BakedOpState* bakedState = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
+
+ ASSERT_NE(nullptr, bakedState) << "NOT rejected by clip, so op should be constructed";
+ EXPECT_LE(64u, allocator.usedSize()) << "relatively large alloc for non-rejected op";
+
+ EXPECT_MATRIX_APPROX_EQ(translate10x20, bakedState->computedState.transform);
+ EXPECT_EQ(Rect(100, 200), bakedState->computedState.clippedBounds);
+ }
+}
+
+TEST(BakedOpState, tryStrokeableOpConstruct) {
+ LinearAllocator allocator;
+ {
+ // check regular rejection
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStrokeAndFill_Style);
+ paint.setStrokeWidth(0.0f);
+ ClipRect clip(Rect(100, 200));
+ RectOp rejectOp(Rect(100, 200), Matrix4::identity(), &clip, &paint);
+ auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect()); // Note: empty clip
+ auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp,
+ BakedOpState::StrokeBehavior::StyleDefined);
+
+ EXPECT_EQ(nullptr, bakedState);
+ EXPECT_GT(8u, allocator.usedSize()); // no significant allocation space used for rejected op
+ }
+ {
+ // check simple unscaled expansion
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStrokeAndFill_Style);
+ paint.setStrokeWidth(10.0f);
+ ClipRect clip(Rect(200, 200));
+ RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint);
+ auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200));
+ auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp,
+ BakedOpState::StrokeBehavior::StyleDefined);
+
+ ASSERT_NE(nullptr, bakedState);
+ EXPECT_EQ(Rect(45, 45, 155, 155), bakedState->computedState.clippedBounds);
+ EXPECT_EQ(0, bakedState->computedState.clipSideFlags);
+ }
+ {
+ // check simple unscaled expansion, and fill style with stroke forced
+ SkPaint paint;
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setStrokeWidth(10.0f);
+ ClipRect clip(Rect(200, 200));
+ RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint);
+ auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200));
+ auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp,
+ BakedOpState::StrokeBehavior::Forced);
+
+ ASSERT_NE(nullptr, bakedState);
+ EXPECT_EQ(Rect(45, 45, 155, 155), bakedState->computedState.clippedBounds);
+ EXPECT_EQ(0, bakedState->computedState.clipSideFlags);
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/tests/unit/CanvasStateTests.cpp b/libs/hwui/tests/unit/CanvasStateTests.cpp
new file mode 100644
index 000000000000..0afabd83f5cc
--- /dev/null
+++ b/libs/hwui/tests/unit/CanvasStateTests.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2015 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 "CanvasState.h"
+
+#include "Matrix.h"
+#include "Rect.h"
+#include "hwui/Canvas.h"
+#include "utils/LinearAllocator.h"
+
+#include <gtest/gtest.h>
+#include <SkPath.h>
+#include <SkRegion.h>
+
+namespace android {
+namespace uirenderer {
+
+class NullClient: public CanvasStateClient {
+ void onViewportInitialized() override {}
+ void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+ GLuint getTargetFbo() const override { return 0; }
+};
+
+static NullClient sNullClient;
+
+static bool approxEqual(const Matrix4& a, const Matrix4& b) {
+ for (int i = 0; i < 16; i++) {
+ if (!MathUtils::areEqual(a[i], b[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+TEST(CanvasState, gettersAndSetters) {
+ CanvasState state(sNullClient);
+ state.initializeSaveStack(200, 200,
+ 0, 0, 200, 200, Vector3());
+
+ ASSERT_EQ(state.getWidth(), 200);
+ ASSERT_EQ(state.getHeight(), 200);
+
+ Matrix4 simpleTranslate;
+ simpleTranslate.loadTranslate(10, 20, 0);
+ state.setMatrix(simpleTranslate);
+
+ ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(200, 200));
+ ASSERT_EQ(state.getLocalClipBounds(), Rect(-10, -20, 190, 180));
+ EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate));
+ EXPECT_TRUE(state.clipIsSimple());
+}
+
+TEST(CanvasState, simpleClipping) {
+ CanvasState state(sNullClient);
+ state.initializeSaveStack(200, 200,
+ 0, 0, 200, 200, Vector3());
+
+ state.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op);
+ ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(100, 100));
+
+ state.clipRect(10, 10, 200, 200, SkRegion::kIntersect_Op);
+ ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10, 100, 100));
+
+ state.clipRect(50, 50, 150, 150, SkRegion::kReplace_Op);
+ ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(50, 50, 150, 150));
+}
+
+TEST(CanvasState, complexClipping) {
+ CanvasState state(sNullClient);
+ state.initializeSaveStack(200, 200,
+ 0, 0, 200, 200, Vector3());
+
+ state.save(SaveFlags::MatrixClip);
+ {
+ // rotated clip causes complex clip
+ state.rotate(10);
+ EXPECT_TRUE(state.clipIsSimple());
+ state.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
+ EXPECT_FALSE(state.clipIsSimple());
+ }
+ state.restore();
+
+ state.save(SaveFlags::MatrixClip);
+ {
+ // subtracted clip causes complex clip
+ EXPECT_TRUE(state.clipIsSimple());
+ state.clipRect(50, 50, 150, 150, SkRegion::kDifference_Op);
+ EXPECT_FALSE(state.clipIsSimple());
+ }
+ state.restore();
+
+ state.save(SaveFlags::MatrixClip);
+ {
+ // complex path causes complex clip
+ SkPath path;
+ path.addOval(SkRect::MakeWH(200, 200));
+ EXPECT_TRUE(state.clipIsSimple());
+ state.clipPath(&path, SkRegion::kDifference_Op);
+ EXPECT_FALSE(state.clipIsSimple());
+ }
+ state.restore();
+}
+
+TEST(CanvasState, saveAndRestore) {
+ CanvasState state(sNullClient);
+ state.initializeSaveStack(200, 200,
+ 0, 0, 200, 200, Vector3());
+
+ state.save(SaveFlags::Clip);
+ {
+ state.clipRect(0, 0, 10, 10, SkRegion::kIntersect_Op);
+ ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10));
+ }
+ state.restore();
+ ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(200, 200)); // verify restore
+
+ Matrix4 simpleTranslate;
+ simpleTranslate.loadTranslate(10, 10, 0);
+ state.save(SaveFlags::Matrix);
+ {
+ state.translate(10, 10, 0);
+ EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate));
+ }
+ state.restore();
+ EXPECT_FALSE(approxEqual(*state.currentTransform(), simpleTranslate));
+}
+
+TEST(CanvasState, saveAndRestoreButNotTooMuch) {
+ CanvasState state(sNullClient);
+ state.initializeSaveStack(200, 200,
+ 0, 0, 200, 200, Vector3());
+
+ state.save(SaveFlags::Matrix); // NOTE: clip not saved
+ {
+ state.clipRect(0, 0, 10, 10, SkRegion::kIntersect_Op);
+ ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10));
+ }
+ state.restore();
+ ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10)); // verify not restored
+
+ Matrix4 simpleTranslate;
+ simpleTranslate.loadTranslate(10, 10, 0);
+ state.save(SaveFlags::Clip); // NOTE: matrix not saved
+ {
+ state.translate(10, 10, 0);
+ EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate));
+ }
+ state.restore();
+ EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate)); // verify not restored
+}
+
+}
+}
diff --git a/libs/hwui/tests/unit/ClipAreaTests.cpp b/libs/hwui/tests/unit/ClipAreaTests.cpp
new file mode 100644
index 000000000000..54ca68d63dbe
--- /dev/null
+++ b/libs/hwui/tests/unit/ClipAreaTests.cpp
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+#include <SkPath.h>
+#include <SkRegion.h>
+
+#include "ClipArea.h"
+
+#include "Matrix.h"
+#include "Rect.h"
+#include "utils/LinearAllocator.h"
+
+namespace android {
+namespace uirenderer {
+
+static Rect kViewportBounds(2048, 2048);
+
+static ClipArea createClipArea() {
+ ClipArea area;
+ area.setViewportDimensions(kViewportBounds.getWidth(), kViewportBounds.getHeight());
+ return area;
+}
+
+TEST(TransformedRectangle, basics) {
+ Rect r(0, 0, 100, 100);
+ Matrix4 minus90;
+ minus90.loadRotate(-90);
+ minus90.mapRect(r);
+ Rect r2(20, 40, 120, 60);
+
+ Matrix4 m90;
+ m90.loadRotate(90);
+ TransformedRectangle tr(r, m90);
+ EXPECT_TRUE(tr.canSimplyIntersectWith(tr));
+
+ Matrix4 m0;
+ TransformedRectangle tr0(r2, m0);
+ EXPECT_FALSE(tr.canSimplyIntersectWith(tr0));
+
+ Matrix4 m45;
+ m45.loadRotate(45);
+ TransformedRectangle tr2(r, m45);
+ EXPECT_FALSE(tr2.canSimplyIntersectWith(tr));
+}
+
+TEST(RectangleList, basics) {
+ RectangleList list;
+ EXPECT_TRUE(list.isEmpty());
+
+ Rect r(0, 0, 100, 100);
+ Matrix4 m45;
+ m45.loadRotate(45);
+ list.set(r, m45);
+ EXPECT_FALSE(list.isEmpty());
+
+ Rect r2(20, 20, 200, 200);
+ list.intersectWith(r2, m45);
+ EXPECT_FALSE(list.isEmpty());
+ EXPECT_EQ(1, list.getTransformedRectanglesCount());
+
+ Rect r3(20, 20, 200, 200);
+ Matrix4 m30;
+ m30.loadRotate(30);
+ list.intersectWith(r2, m30);
+ EXPECT_FALSE(list.isEmpty());
+ EXPECT_EQ(2, list.getTransformedRectanglesCount());
+
+ SkRegion clip;
+ clip.setRect(0, 0, 2000, 2000);
+ SkRegion rgn(list.convertToRegion(clip));
+ EXPECT_FALSE(rgn.isEmpty());
+}
+
+TEST(ClipArea, basics) {
+ ClipArea area(createClipArea());
+ EXPECT_FALSE(area.isEmpty());
+}
+
+TEST(ClipArea, paths) {
+ ClipArea area(createClipArea());
+ SkPath path;
+ SkScalar r = 100;
+ path.addCircle(r, r, r);
+ area.clipPathWithTransform(path, &Matrix4::identity(), SkRegion::kIntersect_Op);
+ EXPECT_FALSE(area.isEmpty());
+ EXPECT_FALSE(area.isSimple());
+ EXPECT_FALSE(area.isRectangleList());
+
+ Rect clipRect(area.getClipRect());
+ Rect expected(0, 0, r * 2, r * 2);
+ EXPECT_EQ(expected, clipRect);
+ SkRegion clipRegion(area.getClipRegion());
+ auto skRect(clipRegion.getBounds());
+ Rect regionBounds;
+ regionBounds.set(skRect);
+ EXPECT_EQ(expected, regionBounds);
+}
+
+TEST(ClipArea, replaceNegative) {
+ ClipArea area(createClipArea());
+ area.setClip(0, 0, 100, 100);
+
+ Rect expected(-50, -50, 50, 50);
+ area.clipRectWithTransform(expected, &Matrix4::identity(), SkRegion::kReplace_Op);
+ EXPECT_EQ(expected, area.getClipRect());
+}
+
+TEST(ClipArea, serializeClip) {
+ ClipArea area(createClipArea());
+ LinearAllocator allocator;
+
+ // unset clip
+ EXPECT_EQ(nullptr, area.serializeClip(allocator));
+
+ // rect clip
+ area.setClip(0, 0, 200, 200);
+ {
+ auto serializedClip = area.serializeClip(allocator);
+ ASSERT_NE(nullptr, serializedClip);
+ ASSERT_EQ(ClipMode::Rectangle, serializedClip->mode);
+ ASSERT_FALSE(serializedClip->intersectWithRoot) << "No replace, so no intersectWithRoot";
+ EXPECT_EQ(Rect(200, 200), serializedClip->rect);
+ EXPECT_EQ(serializedClip, area.serializeClip(allocator))
+ << "Requery of clip on unmodified ClipArea must return same pointer.";
+ }
+
+ // rect list
+ Matrix4 rotate;
+ rotate.loadRotate(5.0f);
+ area.clipRectWithTransform(Rect(50, 50, 150, 150), &rotate, SkRegion::kIntersect_Op);
+ {
+ auto serializedClip = area.serializeClip(allocator);
+ ASSERT_NE(nullptr, serializedClip);
+ ASSERT_EQ(ClipMode::RectangleList, serializedClip->mode);
+ ASSERT_FALSE(serializedClip->intersectWithRoot) << "No replace, so no intersectWithRoot";
+ auto clipRectList = reinterpret_cast<const ClipRectList*>(serializedClip);
+ EXPECT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount());
+ EXPECT_EQ(Rect(37, 54, 145, 163), clipRectList->rect);
+ EXPECT_EQ(serializedClip, area.serializeClip(allocator))
+ << "Requery of clip on unmodified ClipArea must return same pointer.";
+ }
+
+ // region
+ SkPath circlePath;
+ circlePath.addCircle(100, 100, 100);
+ area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kReplace_Op);
+ {
+ auto serializedClip = area.serializeClip(allocator);
+ ASSERT_NE(nullptr, serializedClip);
+ ASSERT_EQ(ClipMode::Region, serializedClip->mode);
+ ASSERT_TRUE(serializedClip->intersectWithRoot) << "Replace op, so expect intersectWithRoot";
+ auto clipRegion = reinterpret_cast<const ClipRegion*>(serializedClip);
+ EXPECT_EQ(SkIRect::MakeWH(200, 200), clipRegion->region.getBounds())
+ << "Clip region should be 200x200";
+ EXPECT_EQ(Rect(200, 200), clipRegion->rect);
+ EXPECT_EQ(serializedClip, area.serializeClip(allocator))
+ << "Requery of clip on unmodified ClipArea must return same pointer.";
+ }
+}
+
+TEST(ClipArea, serializeClip_pathIntersectWithRoot) {
+ ClipArea area(createClipArea());
+ LinearAllocator allocator;
+ SkPath circlePath;
+ circlePath.addCircle(100, 100, 100);
+ area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kIntersect_Op);
+
+ auto serializedClip = area.serializeClip(allocator);
+ ASSERT_NE(nullptr, serializedClip);
+ EXPECT_FALSE(serializedClip->intersectWithRoot) << "No replace, so no intersectWithRoot";
+}
+
+TEST(ClipArea, serializeIntersectedClip) {
+ ClipArea area(createClipArea());
+ LinearAllocator allocator;
+
+ // simple state;
+ EXPECT_EQ(nullptr, area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity()));
+ area.setClip(0, 0, 200, 200);
+ {
+ auto origRectClip = area.serializeClip(allocator);
+ ASSERT_NE(nullptr, origRectClip);
+ EXPECT_EQ(origRectClip, area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity()));
+ }
+
+ // rect
+ {
+ ClipRect recordedClip(Rect(100, 100));
+ Matrix4 translateScale;
+ translateScale.loadTranslate(100, 100, 0);
+ translateScale.scale(2, 3, 1);
+ auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, translateScale);
+ ASSERT_NE(nullptr, resolvedClip);
+ ASSERT_EQ(ClipMode::Rectangle, resolvedClip->mode);
+ EXPECT_EQ(Rect(100, 100, 200, 200), resolvedClip->rect);
+
+ EXPECT_EQ(resolvedClip, area.serializeIntersectedClip(allocator, &recordedClip, translateScale))
+ << "Must return previous serialization, since input is same";
+
+ ClipRect recordedClip2(Rect(100, 100));
+ EXPECT_NE(resolvedClip, area.serializeIntersectedClip(allocator, &recordedClip2, translateScale))
+ << "Shouldn't return previous serialization, since matrix location is different";
+ }
+
+ // rect list
+ Matrix4 rotate;
+ rotate.loadRotate(2.0f);
+ area.clipRectWithTransform(Rect(200, 200), &rotate, SkRegion::kIntersect_Op);
+ {
+ ClipRect recordedClip(Rect(100, 100));
+ auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, Matrix4::identity());
+ ASSERT_NE(nullptr, resolvedClip);
+ ASSERT_EQ(ClipMode::RectangleList, resolvedClip->mode);
+ auto clipRectList = reinterpret_cast<const ClipRectList*>(resolvedClip);
+ EXPECT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount());
+ }
+
+ // region
+ SkPath circlePath;
+ circlePath.addCircle(100, 100, 100);
+ area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kReplace_Op);
+ {
+ SkPath ovalPath;
+ ovalPath.addOval(SkRect::MakeLTRB(50, 0, 150, 200));
+
+ ClipRegion recordedClip;
+ recordedClip.region.setPath(ovalPath, SkRegion(SkIRect::MakeWH(200, 200)));
+ recordedClip.rect = Rect(200, 200);
+
+ Matrix4 translate10x20;
+ translate10x20.loadTranslate(10, 20, 0);
+ auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip,
+ translate10x20); // Note: only translate for now, others not handled correctly
+ ASSERT_NE(nullptr, resolvedClip);
+ ASSERT_EQ(ClipMode::Region, resolvedClip->mode);
+ auto clipRegion = reinterpret_cast<const ClipRegion*>(resolvedClip);
+ EXPECT_EQ(SkIRect::MakeLTRB(60, 20, 160, 200), clipRegion->region.getBounds());
+ }
+}
+
+TEST(ClipArea, serializeIntersectedClip_snap) {
+ ClipArea area(createClipArea());
+ area.setClip(100.2, 100.4, 500.6, 500.8);
+ LinearAllocator allocator;
+
+ {
+ // no recorded clip case
+ auto resolvedClip = area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity());
+ EXPECT_EQ(Rect(100, 100, 501, 501), resolvedClip->rect);
+ }
+ {
+ // recorded clip case
+ ClipRect recordedClip(Rect(100.12, 100.74));
+ Matrix4 translateScale;
+ translateScale.loadTranslate(100, 100, 0);
+ translateScale.scale(2, 3, 1); // recorded clip will have non-int coords, even after transform
+ auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, translateScale);
+ ASSERT_NE(nullptr, resolvedClip);
+ EXPECT_EQ(ClipMode::Rectangle, resolvedClip->mode);
+ EXPECT_EQ(Rect(100, 100, 300, 402), resolvedClip->rect);
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/tests/unit/DamageAccumulatorTests.cpp b/libs/hwui/tests/unit/DamageAccumulatorTests.cpp
new file mode 100644
index 000000000000..77001382115a
--- /dev/null
+++ b/libs/hwui/tests/unit/DamageAccumulatorTests.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include <DamageAccumulator.h>
+#include <Matrix.h>
+#include <RenderNode.h>
+#include <utils/LinearAllocator.h>
+
+#include <SkRect.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+// Test that push & pop are propegating the dirty rect
+// There is no transformation of the dirty rect, the input is the same
+// as the output.
+TEST(DamageAccumulator, identity) {
+ DamageAccumulator da;
+ SkRect curDirty;
+ da.pushTransform(&Matrix4::identity());
+ da.dirty(50, 50, 100, 100);
+ {
+ da.pushTransform(&Matrix4::identity());
+ da.peekAtDirty(&curDirty);
+ ASSERT_EQ(SkRect(), curDirty);
+ da.popTransform();
+ }
+ da.peekAtDirty(&curDirty);
+ ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty);
+ da.popTransform();
+ da.finish(&curDirty);
+ ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty);
+}
+
+// Test that transformation is happening at the correct levels via
+// peekAtDirty & popTransform. Just uses a simple translate to test this
+TEST(DamageAccumulator, translate) {
+ DamageAccumulator da;
+ Matrix4 translate;
+ SkRect curDirty;
+ translate.loadTranslate(25, 25, 0);
+ da.pushTransform(&translate);
+ da.dirty(50, 50, 100, 100);
+ da.peekAtDirty(&curDirty);
+ ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty);
+ da.popTransform();
+ da.finish(&curDirty);
+ ASSERT_EQ(SkRect::MakeLTRB(75, 75, 125, 125), curDirty);
+}
+
+// Test that dirty rectangles are being unioned across "siblings
+TEST(DamageAccumulator, union) {
+ DamageAccumulator da;
+ SkRect curDirty;
+ da.pushTransform(&Matrix4::identity());
+ {
+ da.pushTransform(&Matrix4::identity());
+ da.dirty(50, 50, 100, 100);
+ da.popTransform();
+ da.pushTransform(&Matrix4::identity());
+ da.dirty(150, 50, 200, 125);
+ da.popTransform();
+ }
+ da.popTransform();
+ da.finish(&curDirty);
+ ASSERT_EQ(SkRect::MakeLTRB(50, 50, 200, 125), curDirty);
+}
+
+TEST(DamageAccumulator, basicRenderNode) {
+ DamageAccumulator da;
+ RenderNode node1;
+ node1.animatorProperties().setLeftTopRightBottom(50, 50, 500, 500);
+ node1.animatorProperties().updateMatrix();
+ da.pushTransform(&node1);
+ {
+ RenderNode node2;
+ node2.animatorProperties().setLeftTopRightBottom(50, 50, 100, 100);
+ node2.animatorProperties().updateMatrix();
+ da.pushTransform(&node2);
+ da.dirty(0, 0, 25, 25);
+ da.popTransform();
+ }
+ da.popTransform();
+ SkRect dirty;
+ da.finish(&dirty);
+ ASSERT_EQ(SkRect::MakeLTRB(100, 100, 125, 125), dirty);
+}
+
+TEST(DamageAccumulator, perspectiveTransform) {
+ DamageAccumulator da;
+ RenderNode node1;
+ node1.animatorProperties().setLeftTopRightBottom(50, 50, 500, 500);
+ node1.animatorProperties().setClipToBounds(true);
+ node1.animatorProperties().updateMatrix();
+ da.pushTransform(&node1);
+ {
+ RenderNode node2;
+ node2.animatorProperties().setLeftTopRightBottom(50, 50, 100, 100);
+ node2.animatorProperties().setClipToBounds(false);
+ node2.animatorProperties().setRotationX(1.0f);
+ node2.animatorProperties().setRotationY(1.0f);
+ node2.animatorProperties().setRotation(20.0f);
+ node2.animatorProperties().setCameraDistance(500.0f);
+ node2.animatorProperties().setTranslationZ(30.0f);
+ node2.animatorProperties().updateMatrix();
+ da.pushTransform(&node2);
+ da.dirty(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
+ da.popTransform();
+ }
+ da.popTransform();
+ SkRect dirty;
+ da.finish(&dirty);
+ ASSERT_EQ(SkRect::MakeLTRB(50, 50, 500, 500), dirty);
+}
diff --git a/libs/hwui/tests/unit/DeviceInfoTests.cpp b/libs/hwui/tests/unit/DeviceInfoTests.cpp
new file mode 100644
index 000000000000..17236bdf0bf7
--- /dev/null
+++ b/libs/hwui/tests/unit/DeviceInfoTests.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 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 <DeviceInfo.h>
+
+#include <gtest/gtest.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+TEST(DeviceInfo, basic) {
+ // can't assert state before init - another test may have initialized the singleton
+ DeviceInfo::initialize();
+ const DeviceInfo* di = DeviceInfo::get();
+ ASSERT_NE(nullptr, di) << "DeviceInfo initialization failed";
+ EXPECT_EQ(2048, di->maxTextureSize()) << "Max texture size didn't match";
+}
diff --git a/libs/hwui/tests/unit/FatVectorTests.cpp b/libs/hwui/tests/unit/FatVectorTests.cpp
new file mode 100644
index 000000000000..64b0ba13562d
--- /dev/null
+++ b/libs/hwui/tests/unit/FatVectorTests.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+#include <utils/FatVector.h>
+
+#include <tests/common/TestUtils.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+template<class VectorType>
+static bool allocationIsInternal(VectorType& v) {
+ // allocation array (from &v[0] to &v[0] + v.capacity) is
+ // located within the vector object itself
+ return (char*)(&v) <= (char*)(&v[0])
+ && (char*)(&v + 1) >= (char*)(&v[0] + v.capacity());
+}
+
+TEST(FatVector, baseline) {
+ // Verify allocation behavior FatVector contrasts against - allocations are always external
+ std::vector<int> v;
+ for (int i = 0; i < 50; i++) {
+ v.push_back(i);
+ EXPECT_FALSE(allocationIsInternal(v));
+ }
+}
+
+TEST(FatVector, simpleAllocate) {
+ FatVector<int, 4> v;
+ EXPECT_EQ(4u, v.capacity());
+
+ // can insert 4 items into internal buffer
+ for (int i = 0; i < 4; i++) {
+ v.push_back(i);
+ EXPECT_TRUE(allocationIsInternal(v));
+ }
+
+ // then will fall back to external allocation
+ for (int i = 5; i < 50; i++) {
+ v.push_back(i);
+ EXPECT_FALSE(allocationIsInternal(v));
+ }
+}
+
+TEST(FatVector, preSizeConstructor) {
+ {
+ FatVector<int, 4> v(32);
+ EXPECT_EQ(32u, v.capacity());
+ EXPECT_EQ(32u, v.size());
+ EXPECT_FALSE(allocationIsInternal(v));
+ }
+ {
+ FatVector<int, 4> v(4);
+ EXPECT_EQ(4u, v.capacity());
+ EXPECT_EQ(4u, v.size());
+ EXPECT_TRUE(allocationIsInternal(v));
+ }
+ {
+ FatVector<int, 4> v(2);
+ EXPECT_EQ(4u, v.capacity());
+ EXPECT_EQ(2u, v.size());
+ EXPECT_TRUE(allocationIsInternal(v));
+ }
+}
+
+TEST(FatVector, shrink) {
+ FatVector<int, 10> v;
+ EXPECT_TRUE(allocationIsInternal(v));
+
+ // push into external alloc
+ v.resize(11);
+ EXPECT_FALSE(allocationIsInternal(v));
+
+ // shrinking back to internal alloc succeeds
+ // note that shrinking further will succeed, but is a waste
+ v.resize(10);
+ v.shrink_to_fit();
+ EXPECT_TRUE(allocationIsInternal(v));
+}
+
+TEST(FatVector, destructorInternal) {
+ int count = 0;
+ {
+ // push 1 into external allocation, verify destruction happens once
+ FatVector<TestUtils::SignalingDtor, 0> v;
+ v.emplace_back(&count);
+ EXPECT_FALSE(allocationIsInternal(v));
+ EXPECT_EQ(0, count) << "Destruction shouldn't have happened yet";
+ }
+ EXPECT_EQ(1, count) << "Destruction should happen exactly once";
+}
+
+TEST(FatVector, destructorExternal) {
+ int count = 0;
+ {
+ // push 10 into internal allocation, verify 10 destructors called
+ FatVector<TestUtils::SignalingDtor, 10> v;
+ for (int i = 0; i < 10; i++) {
+ v.emplace_back(&count);
+ EXPECT_TRUE(allocationIsInternal(v));
+ }
+ EXPECT_EQ(0, count) << "Destruction shouldn't have happened yet";
+ }
+ EXPECT_EQ(10, count) << "Destruction should happen exactly once";
+}
diff --git a/libs/hwui/tests/unit/FontRendererTests.cpp b/libs/hwui/tests/unit/FontRendererTests.cpp
new file mode 100644
index 000000000000..99080ac938e7
--- /dev/null
+++ b/libs/hwui/tests/unit/FontRendererTests.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+
+#include "GammaFontRenderer.h"
+#include "tests/common/TestUtils.h"
+
+using namespace android::uirenderer;
+
+static bool isZero(uint8_t* data, int size) {
+ for (int i = 0; i < size; i++) {
+ if (data[i]) return false;
+ }
+ return true;
+}
+
+RENDERTHREAD_TEST(FontRenderer, renderDropShadow) {
+ SkPaint paint;
+ paint.setTextSize(10);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ GammaFontRenderer gammaFontRenderer;
+ FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer();
+ fontRenderer.setFont(&paint, SkMatrix::I());
+
+ std::vector<glyph_t> glyphs;
+ std::vector<float> positions;
+ float totalAdvance;
+ Rect bounds;
+ TestUtils::layoutTextUnscaled(paint, "This is a test",
+ &glyphs, &positions, &totalAdvance, &bounds);
+
+ for (int radius : {28, 20, 2}) {
+ auto result = fontRenderer.renderDropShadow(&paint, glyphs.data(), glyphs.size(),
+ radius, positions.data());
+ ASSERT_NE(nullptr, result.image);
+ EXPECT_FALSE(isZero(result.image, result.width * result.height));
+ EXPECT_LE(bounds.getWidth() + radius * 2, (int) result.width);
+ EXPECT_LE(bounds.getHeight() + radius * 2, (int) result.height);
+ delete result.image;
+ }
+}
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
new file mode 100644
index 000000000000..0f16b1512586
--- /dev/null
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -0,0 +1,2172 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+
+#include <BakedOpState.h>
+#include <DeferredLayerUpdater.h>
+#include <FrameBuilder.h>
+#include <LayerUpdateQueue.h>
+#include <RecordedOp.h>
+#include <RecordingCanvas.h>
+#include <tests/common/TestUtils.h>
+
+#include <unordered_map>
+
+namespace android {
+namespace uirenderer {
+
+const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50};
+
+/**
+ * Virtual class implemented by each test to redirect static operation / state transitions to
+ * virtual methods.
+ *
+ * Virtual dispatch allows for default behaviors to be specified (very common case in below tests),
+ * and allows Renderer vs Dispatching behavior to be merged.
+ *
+ * onXXXOp methods fail by default - tests should override ops they expect
+ * startRepaintLayer fails by default - tests should override if expected
+ * startFrame/endFrame do nothing by default - tests should override to intercept
+ */
+class TestRendererBase {
+public:
+ virtual ~TestRendererBase() {}
+ virtual OffscreenBuffer* startTemporaryLayer(uint32_t, uint32_t) {
+ ADD_FAILURE() << "Temporary layers not expected in this test";
+ return nullptr;
+ }
+ virtual void recycleTemporaryLayer(OffscreenBuffer*) {
+ ADD_FAILURE() << "Temporary layers not expected in this test";
+ }
+ virtual void startRepaintLayer(OffscreenBuffer*, const Rect& repaintRect) {
+ ADD_FAILURE() << "Layer repaint not expected in this test";
+ }
+ virtual void endLayer() {
+ ADD_FAILURE() << "Layer updates not expected in this test";
+ }
+ virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {}
+ virtual void endFrame(const Rect& repaintRect) {}
+
+ // define virtual defaults for single draw methods
+#define X(Type) \
+ virtual void on##Type(const Type&, const BakedOpState&) { \
+ ADD_FAILURE() << #Type " not expected in this test"; \
+ }
+ MAP_RENDERABLE_OPS(X)
+#undef X
+
+ // define virtual defaults for merged draw methods
+#define X(Type) \
+ virtual void onMerged##Type##s(const MergedBakedOpList& opList) { \
+ ADD_FAILURE() << "Merged " #Type "s not expected in this test"; \
+ }
+ MAP_MERGEABLE_OPS(X)
+#undef X
+
+ int getIndex() { return mIndex; }
+
+protected:
+ int mIndex = 0;
+};
+
+/**
+ * Dispatches all static methods to similar formed methods on renderer, which fail by default but
+ * are overridden by subclasses per test.
+ */
+class TestDispatcher {
+public:
+ // define single op methods, which redirect to TestRendererBase
+#define X(Type) \
+ static void on##Type(TestRendererBase& renderer, const Type& op, const BakedOpState& state) { \
+ renderer.on##Type(op, state); \
+ }
+ MAP_RENDERABLE_OPS(X);
+#undef X
+
+ // define merged op methods, which redirect to TestRendererBase
+#define X(Type) \
+ static void onMerged##Type##s(TestRendererBase& renderer, const MergedBakedOpList& opList) { \
+ renderer.onMerged##Type##s(opList); \
+ }
+ MAP_MERGEABLE_OPS(X);
+#undef X
+};
+
+class FailRenderer : public TestRendererBase {};
+
+RENDERTHREAD_TEST(FrameBuilder, simple) {
+ class SimpleTestRenderer : public TestRendererBase {
+ public:
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(100u, width);
+ EXPECT_EQ(200u, height);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ }
+ void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void endFrame(const Rect& repaintRect) override {
+ EXPECT_EQ(3, mIndex++);
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 100, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkBitmap bitmap = TestUtils::createSkBitmap(25, 25);
+ canvas.drawRect(0, 0, 100, 200, SkPaint());
+ canvas.drawBitmap(bitmap, 10, 10, nullptr);
+ });
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ SimpleTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
+}
+
+RENDERTHREAD_TEST(FrameBuilder, simpleStroke) {
+ class SimpleStrokeTestRenderer : public TestRendererBase {
+ public:
+ void onPointsOp(const PointsOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ // even though initial bounds are empty...
+ EXPECT_TRUE(op.unmappedBounds.isEmpty())
+ << "initial bounds should be empty, since they're unstroked";
+ EXPECT_EQ(Rect(45, 45, 55, 55), state.computedState.clippedBounds)
+ << "final bounds should account for stroke";
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 100, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkPaint strokedPaint;
+ strokedPaint.setStrokeWidth(10);
+ canvas.drawPoint(50, 50, strokedPaint);
+ });
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ SimpleStrokeTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(1, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, simpleRejection) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); // intersection should be empty
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ canvas.restore();
+ });
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ FailRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+}
+
+RENDERTHREAD_TEST(FrameBuilder, simpleBatching) {
+ const int LOOPS = 5;
+ class SimpleBatchingTestRenderer : public TestRendererBase {
+ public:
+ void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(mIndex++ >= LOOPS) << "Bitmaps should be above all rects";
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(mIndex++ < LOOPS) << "Rects should be below all bitmaps";
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkBitmap bitmap = TestUtils::createSkBitmap(10, 10,
+ kAlpha_8_SkColorType); // Disable merging by using alpha 8 bitmap
+
+ // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
+ // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
+ canvas.save(SaveFlags::MatrixClip);
+ for (int i = 0; i < LOOPS; i++) {
+ canvas.translate(0, 10);
+ canvas.drawRect(0, 0, 10, 10, SkPaint());
+ canvas.drawBitmap(bitmap, 5, 0, nullptr);
+ }
+ canvas.restore();
+ });
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ SimpleBatchingTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2 * LOOPS, renderer.getIndex())
+ << "Expect number of ops = 2 * loop count";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, deferRenderNode_translateClip) {
+ class DeferRenderNodeTranslateClipTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(Rect(5, 10, 55, 60), state.computedState.clippedBounds);
+ EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom,
+ state.computedState.clipSideFlags);
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(5, 10, Rect(50, 50), // translate + clip node
+ *TestUtils::getSyncedNode(node));
+
+ DeferRenderNodeTranslateClipTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(1, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, deferRenderNodeScene) {
+ class DeferRenderNodeSceneTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ const Rect& clippedBounds = state.computedState.clippedBounds;
+ Matrix4 expected;
+ switch (mIndex++) {
+ case 0:
+ // background - left side
+ EXPECT_EQ(Rect(600, 100, 700, 500), clippedBounds);
+ expected.loadTranslate(100, 100, 0);
+ break;
+ case 1:
+ // background - top side
+ EXPECT_EQ(Rect(100, 400, 600, 500), clippedBounds);
+ expected.loadTranslate(100, 100, 0);
+ break;
+ case 2:
+ // content
+ EXPECT_EQ(Rect(100, 100, 700, 500), clippedBounds);
+ expected.loadTranslate(-50, -50, 0);
+ break;
+ case 3:
+ // overlay
+ EXPECT_EQ(Rect(0, 0, 800, 200), clippedBounds);
+ break;
+ default:
+ ADD_FAILURE() << "Too many rects observed";
+ }
+ EXPECT_EQ(expected, state.computedState.transform);
+ }
+ };
+
+ std::vector<sp<RenderNode>> nodes;
+ SkPaint transparentPaint;
+ transparentPaint.setAlpha(128);
+
+ // backdrop
+ nodes.push_back(TestUtils::createNode(100, 100, 700, 500, // 600x400
+ [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 600, 400, transparentPaint);
+ }));
+
+ // content
+ Rect contentDrawBounds(150, 150, 650, 450); // 500x300
+ nodes.push_back(TestUtils::createNode(0, 0, 800, 600,
+ [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 800, 600, transparentPaint);
+ }));
+
+ // overlay
+ nodes.push_back(TestUtils::createNode(0, 0, 800, 600,
+ [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 800, 200, transparentPaint);
+ }));
+
+ for (auto& node : nodes) {
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+ }
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds);
+
+ DeferRenderNodeSceneTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, empty_noFbo0) {
+ class EmptyNoFbo0TestRenderer : public TestRendererBase {
+ public:
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ ADD_FAILURE() << "Primary frame draw not expected in this test";
+ }
+ void endFrame(const Rect& repaintRect) override {
+ ADD_FAILURE() << "Primary frame draw not expected in this test";
+ }
+ };
+
+ // Use layer update constructor, so no work is enqueued for Fbo0
+ LayerUpdateQueue emptyLayerUpdateQueue;
+ FrameBuilder frameBuilder(emptyLayerUpdateQueue, sLightGeometry, Caches::getInstance());
+ EmptyNoFbo0TestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+}
+
+RENDERTHREAD_TEST(FrameBuilder, empty_withFbo0) {
+ class EmptyWithFbo0TestRenderer : public TestRendererBase {
+ public:
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ EXPECT_EQ(0, mIndex++);
+ }
+ void endFrame(const Rect& repaintRect) override {
+ EXPECT_EQ(1, mIndex++);
+ }
+ };
+ auto node = TestUtils::createNode(10, 10, 110, 110,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ // no drawn content
+ });
+
+ // Draw, but pass node without draw content, so no work is done for primary frame
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ EmptyWithFbo0TestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex()) << "No drawing content produced,"
+ " but fbo0 update lifecycle should still be observed";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_rects) {
+ class AvoidOverdrawRectsTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(mIndex++, 0) << "Should be one rect";
+ EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds)
+ << "Last rect should occlude others.";
+ }
+ };
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ canvas.drawRect(10, 10, 190, 190, SkPaint());
+ });
+
+ // Damage (and therefore clip) is same as last draw, subset of renderable area.
+ // This means last op occludes other contents, and they'll be rejected to avoid overdraw.
+ FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 10, 190, 190), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ EXPECT_EQ(3u, node->getDisplayList()->getOps().size())
+ << "Recording must not have rejected ops, in order for this test to be valid";
+
+ AvoidOverdrawRectsTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(1, renderer.getIndex()) << "Expect exactly one op";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_bitmaps) {
+ static SkBitmap opaqueBitmap = TestUtils::createSkBitmap(50, 50,
+ SkColorType::kRGB_565_SkColorType);
+ static SkBitmap transpBitmap = TestUtils::createSkBitmap(50, 50,
+ SkColorType::kAlpha_8_SkColorType);
+ class AvoidOverdrawBitmapsTestRenderer : public TestRendererBase {
+ public:
+ void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+ switch(mIndex++) {
+ case 0:
+ EXPECT_EQ(opaqueBitmap.pixelRef(), op.bitmap->pixelRef());
+ break;
+ case 1:
+ EXPECT_EQ(transpBitmap.pixelRef(), op.bitmap->pixelRef());
+ break;
+ default:
+ ADD_FAILURE() << "Only two ops expected.";
+ }
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 50, 50,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 50, 50, SkPaint());
+ canvas.drawRect(0, 0, 50, 50, SkPaint());
+ canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
+
+ // only the below draws should remain, since they're
+ canvas.drawBitmap(opaqueBitmap, 0, 0, nullptr);
+ canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
+ });
+ FrameBuilder frameBuilder(SkRect::MakeWH(50, 50), 50, 50,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ EXPECT_EQ(5u, node->getDisplayList()->getOps().size())
+ << "Recording must not have rejected ops, in order for this test to be valid";
+
+ AvoidOverdrawBitmapsTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex()) << "Expect exactly two ops";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, clippedMerging) {
+ class ClippedMergingTestRenderer : public TestRendererBase {
+ public:
+ void onMergedBitmapOps(const MergedBakedOpList& opList) override {
+ EXPECT_EQ(0, mIndex);
+ mIndex += opList.count;
+ EXPECT_EQ(4u, opList.count);
+ EXPECT_EQ(Rect(10, 10, 90, 90), opList.clip);
+ EXPECT_EQ(OpClipSideFlags::Left | OpClipSideFlags::Top | OpClipSideFlags::Right,
+ opList.clipSideFlags);
+ }
+ };
+ auto node = TestUtils::createNode(0, 0, 100, 100,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ SkBitmap bitmap = TestUtils::createSkBitmap(20, 20);
+
+ // left side clipped (to inset left half)
+ canvas.clipRect(10, 0, 50, 100, SkRegion::kReplace_Op);
+ canvas.drawBitmap(bitmap, 0, 40, nullptr);
+
+ // top side clipped (to inset top half)
+ canvas.clipRect(0, 10, 100, 50, SkRegion::kReplace_Op);
+ canvas.drawBitmap(bitmap, 40, 0, nullptr);
+
+ // right side clipped (to inset right half)
+ canvas.clipRect(50, 0, 90, 100, SkRegion::kReplace_Op);
+ canvas.drawBitmap(bitmap, 80, 40, nullptr);
+
+ // bottom not clipped, just abutting (inset bottom half)
+ canvas.clipRect(0, 50, 100, 90, SkRegion::kReplace_Op);
+ canvas.drawBitmap(bitmap, 40, 70, nullptr);
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ ClippedMergingTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, textMerging) {
+ class TextMergingTestRenderer : public TestRendererBase {
+ public:
+ void onMergedTextOps(const MergedBakedOpList& opList) override {
+ EXPECT_EQ(0, mIndex);
+ mIndex += opList.count;
+ EXPECT_EQ(2u, opList.count);
+ EXPECT_EQ(OpClipSideFlags::Top, opList.clipSideFlags);
+ EXPECT_EQ(OpClipSideFlags::Top, opList.states[0]->computedState.clipSideFlags);
+ EXPECT_EQ(OpClipSideFlags::None, opList.states[1]->computedState.clipSideFlags);
+ }
+ };
+ auto node = TestUtils::createNode(0, 0, 400, 400,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ SkPaint paint;
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ paint.setAntiAlias(true);
+ paint.setTextSize(50);
+ TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped
+ TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped
+ });
+ FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ TextMergingTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, textStrikethrough) {
+ const int LOOPS = 5;
+ class TextStrikethroughTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(mIndex++ >= LOOPS) << "Strikethrough rects should be above all text";
+ }
+ void onMergedTextOps(const MergedBakedOpList& opList) override {
+ EXPECT_EQ(0, mIndex);
+ mIndex += opList.count;
+ EXPECT_EQ(5u, opList.count);
+ }
+ };
+ auto node = TestUtils::createNode(0, 0, 200, 2000,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkPaint textPaint;
+ textPaint.setAntiAlias(true);
+ textPaint.setTextSize(20);
+ textPaint.setStrikeThruText(true);
+ textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ for (int i = 0; i < LOOPS; i++) {
+ TestUtils::drawUtf8ToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
+ }
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 2000), 200, 2000,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ TextStrikethroughTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2 * LOOPS, renderer.getIndex())
+ << "Expect number of ops = 2 * loop count";
+}
+
+static auto styles = {
+ SkPaint::kFill_Style, SkPaint::kStroke_Style, SkPaint::kStrokeAndFill_Style };
+
+RENDERTHREAD_TEST(FrameBuilder, textStyle) {
+ class TextStyleTestRenderer : public TestRendererBase {
+ public:
+ void onMergedTextOps(const MergedBakedOpList& opList) override {
+ ASSERT_EQ(0, mIndex);
+ ASSERT_EQ(3u, opList.count);
+ mIndex += opList.count;
+
+ int index = 0;
+ for (auto style : styles) {
+ auto state = opList.states[index++];
+ ASSERT_EQ(style, state->op->paint->getStyle())
+ << "Remainder of validation relies upon stable merged order";
+ ASSERT_EQ(0, state->computedState.clipSideFlags)
+ << "Clipped bounds validation requires unclipped ops";
+ }
+
+ Rect fill = opList.states[0]->computedState.clippedBounds;
+ Rect stroke = opList.states[1]->computedState.clippedBounds;
+ EXPECT_EQ(stroke, opList.states[2]->computedState.clippedBounds)
+ << "Stroke+Fill should be same as stroke";
+
+ EXPECT_TRUE(stroke.contains(fill));
+ EXPECT_FALSE(fill.contains(stroke));
+
+ // outset by half the stroke width
+ Rect outsetFill(fill);
+ outsetFill.outset(5);
+ EXPECT_EQ(stroke, outsetFill);
+ }
+ };
+ auto node = TestUtils::createNode(0, 0, 400, 400,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ SkPaint paint;
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ paint.setAntiAlias(true);
+ paint.setTextSize(50);
+ paint.setStrokeWidth(10);
+
+ // draw 3 copies of the same text overlapping, each with a different style.
+ // They'll get merged, but with
+ for (auto style : styles) {
+ paint.setStyle(style);
+ TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100);
+ }
+ });
+ FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+ TextStyleTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(3, renderer.getIndex()) << "Expect 3 ops";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, textureLayer_clipLocalMatrix) {
+ class TextureLayerClipLocalMatrixTestRenderer : public TestRendererBase {
+ public:
+ void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipRect());
+ EXPECT_EQ(Rect(50, 50, 105, 105), state.computedState.clippedBounds);
+
+ Matrix4 expected;
+ expected.loadTranslate(5, 5, 0);
+ EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform);
+ }
+ };
+
+ auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100,
+ SkMatrix::MakeTrans(5, 5));
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.clipRect(50, 50, 150, 150, SkRegion::kIntersect_Op);
+ canvas.drawLayer(layerUpdater.get());
+ canvas.restore();
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ TextureLayerClipLocalMatrixTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(1, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, textureLayer_combineMatrices) {
+ class TextureLayerCombineMatricesTestRenderer : public TestRendererBase {
+ public:
+ void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+
+ Matrix4 expected;
+ expected.loadTranslate(35, 45, 0);
+ EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform);
+ }
+ };
+
+ auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100,
+ SkMatrix::MakeTrans(5, 5));
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.translate(30, 40);
+ canvas.drawLayer(layerUpdater.get());
+ canvas.restore();
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ TextureLayerCombineMatricesTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(1, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, textureLayer_reject) {
+ auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100,
+ SkMatrix::MakeTrans(5, 5));
+ layerUpdater->backingLayer()->setRenderTarget(GL_NONE); // Should be rejected
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.drawLayer(layerUpdater.get());
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ FailRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+}
+
+RENDERTHREAD_TEST(FrameBuilder, functor_reject) {
+ class FunctorTestRenderer : public TestRendererBase {
+ public:
+ void onFunctorOp(const FunctorOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ }
+ };
+ Functor noopFunctor;
+
+ // 1 million pixel tall view, scrolled down 80%
+ auto scrolledFunctorView = TestUtils::createNode(0, 0, 400, 1000000,
+ [&noopFunctor](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.translate(0, -800000);
+ canvas.callDrawGLFunction(&noopFunctor, nullptr);
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(scrolledFunctorView));
+
+ FunctorTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(1, renderer.getIndex()) << "Functor should not be rejected";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, deferColorOp_unbounded) {
+ class ColorTestRenderer : public TestRendererBase {
+ public:
+ void onColorOp(const ColorOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds)
+ << "Color op should be expanded to bounds of surrounding";
+ }
+ };
+
+ auto unclippedColorView = TestUtils::createNode(0, 0, 10, 10,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.setClipToBounds(false);
+ canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode);
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(unclippedColorView));
+
+ ColorTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(1, renderer.getIndex()) << "ColorOp should not be rejected";
+}
+
+TEST(FrameBuilder, renderNode) {
+ class RenderNodeTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ switch(mIndex++) {
+ case 0:
+ EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds);
+ EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+ break;
+ case 1:
+ EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds);
+ EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+ break;
+ default:
+ ADD_FAILURE();
+ }
+ }
+ };
+
+ auto child = TestUtils::createNode(10, 10, 110, 110,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [&child](RenderProperties& props, RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorDKGRAY);
+ canvas.drawRect(0, 0, 200, 200, paint);
+
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.translate(40, 40);
+ canvas.drawRenderNode(child.get());
+ canvas.restore();
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
+ RenderNodeTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, clipped) {
+ class ClippedTestRenderer : public TestRendererBase {
+ public:
+ void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
+ EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect());
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
+ canvas.drawBitmap(bitmap, 0, 0, nullptr);
+ });
+
+ // clip to small area, should see in receiver
+ FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 20, 30, 40), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ ClippedTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+}
+
+RENDERTHREAD_TEST(FrameBuilder, saveLayer_simple) {
+ class SaveLayerSimpleTestRenderer : public TestRendererBase {
+ public:
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(180u, width);
+ EXPECT_EQ(180u, height);
+ return nullptr;
+ }
+ void endLayer() override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
+ EXPECT_EQ(Rect(180, 180), state.computedState.clippedBounds);
+ EXPECT_EQ(Rect(180, 180), state.computedState.clipRect());
+
+ Matrix4 expectedTransform;
+ expectedTransform.loadTranslate(-10, -10, 0);
+ EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(3, mIndex++);
+ EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
+ EXPECT_EQ(Rect(200, 200), state.computedState.clipRect());
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
+ EXPECT_EQ(4, mIndex++);
+ EXPECT_EQ(nullptr, offscreenBuffer);
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(10, 10, 190, 190, 128, SaveFlags::ClipToLayer);
+ canvas.drawRect(10, 10, 190, 190, SkPaint());
+ canvas.restore();
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ SaveLayerSimpleTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(5, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, saveLayer_nested) {
+ /* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as:
+ * - startTemporaryLayer2, rect2 endLayer2
+ * - startTemporaryLayer1, rect1, drawLayer2, endLayer1
+ * - startFrame, layerOp1, endFrame
+ */
+ class SaveLayerNestedTestRenderer : public TestRendererBase {
+ public:
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+ const int index = mIndex++;
+ if (index == 0) {
+ EXPECT_EQ(400u, width);
+ EXPECT_EQ(400u, height);
+ return (OffscreenBuffer*) 0x400;
+ } else if (index == 3) {
+ EXPECT_EQ(800u, width);
+ EXPECT_EQ(800u, height);
+ return (OffscreenBuffer*) 0x800;
+ } else { ADD_FAILURE(); }
+ return (OffscreenBuffer*) nullptr;
+ }
+ void endLayer() override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 2 || index == 6);
+ }
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ EXPECT_EQ(7, mIndex++);
+ }
+ void endFrame(const Rect& repaintRect) override {
+ EXPECT_EQ(9, mIndex++);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ const int index = mIndex++;
+ if (index == 1) {
+ EXPECT_EQ(Rect(400, 400), op.unmappedBounds); // inner rect
+ } else if (index == 4) {
+ EXPECT_EQ(Rect(800, 800), op.unmappedBounds); // outer rect
+ } else { ADD_FAILURE(); }
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ const int index = mIndex++;
+ if (index == 5) {
+ EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle);
+ EXPECT_EQ(Rect(400, 400), op.unmappedBounds); // inner layer
+ } else if (index == 8) {
+ EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle);
+ EXPECT_EQ(Rect(800, 800), op.unmappedBounds); // outer layer
+ } else { ADD_FAILURE(); }
+ }
+ void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
+ const int index = mIndex++;
+ // order isn't important, but we need to see both
+ if (index == 10) {
+ EXPECT_EQ((OffscreenBuffer*)0x400, offscreenBuffer);
+ } else if (index == 11) {
+ EXPECT_EQ((OffscreenBuffer*)0x800, offscreenBuffer);
+ } else { ADD_FAILURE(); }
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 800, 800,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(0, 0, 800, 800, 128, SaveFlags::ClipToLayer);
+ {
+ canvas.drawRect(0, 0, 800, 800, SkPaint());
+ canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer);
+ {
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ }
+ canvas.restore();
+ }
+ canvas.restore();
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(800, 800), 800, 800,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ SaveLayerNestedTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(12, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, saveLayer_contentRejection) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op);
+ canvas.saveLayerAlpha(200, 200, 400, 400, 128, SaveFlags::ClipToLayer);
+
+ // draw within save layer may still be recorded, but shouldn't be drawn
+ canvas.drawRect(200, 200, 400, 400, SkPaint());
+
+ canvas.restore();
+ canvas.restore();
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ FailRenderer renderer;
+ // should see no ops, even within the layer, since the layer should be rejected
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+}
+
+RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_simple) {
+ class SaveLayerUnclippedSimpleTestRenderer : public TestRendererBase {
+ public:
+ void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
+ EXPECT_CLIP_RECT(Rect(200, 200), state.computedState.clipState);
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ ASSERT_NE(nullptr, op.paint);
+ ASSERT_EQ(SkXfermode::kClear_Mode, PaintUtils::getXfermodeDirect(op.paint));
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(2, mIndex++);
+ EXPECT_EQ(Rect(200, 200), op.unmappedBounds);
+ EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds);
+ EXPECT_EQ(Rect(200, 200), state.computedState.clipRect());
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(3, mIndex++);
+ EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
+ EXPECT_CLIP_RECT(Rect(200, 200), state.computedState.clipState);
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0));
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ canvas.restore();
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ SaveLayerUnclippedSimpleTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_mergedClears) {
+ class SaveLayerUnclippedMergedClearsTestRenderer : public TestRendererBase {
+ public:
+ void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_GT(4, index);
+ EXPECT_EQ(5, op.unmappedBounds.getWidth());
+ EXPECT_EQ(5, op.unmappedBounds.getHeight());
+ if (index == 0) {
+ EXPECT_EQ(Rect(10, 10), state.computedState.clippedBounds);
+ } else if (index == 1) {
+ EXPECT_EQ(Rect(190, 0, 200, 10), state.computedState.clippedBounds);
+ } else if (index == 2) {
+ EXPECT_EQ(Rect(0, 190, 10, 200), state.computedState.clippedBounds);
+ } else if (index == 3) {
+ EXPECT_EQ(Rect(190, 190, 200, 200), state.computedState.clippedBounds);
+ }
+ }
+ void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(4, mIndex++);
+ ASSERT_EQ(op.vertexCount, 16u);
+ for (size_t i = 0; i < op.vertexCount; i++) {
+ auto v = op.vertices[i];
+ EXPECT_TRUE(v.x == 0 || v.x == 10 || v.x == 190 || v.x == 200);
+ EXPECT_TRUE(v.y == 0 || v.y == 10 || v.y == 190 || v.y == 200);
+ }
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(5, mIndex++);
+ }
+ void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
+ EXPECT_LT(5, mIndex++);
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+
+ int restoreTo = canvas.save(SaveFlags::MatrixClip);
+ canvas.scale(2, 2);
+ canvas.saveLayerAlpha(0, 0, 5, 5, 128, SaveFlags::MatrixClip);
+ canvas.saveLayerAlpha(95, 0, 100, 5, 128, SaveFlags::MatrixClip);
+ canvas.saveLayerAlpha(0, 95, 5, 100, 128, SaveFlags::MatrixClip);
+ canvas.saveLayerAlpha(95, 95, 100, 100, 128, SaveFlags::MatrixClip);
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ canvas.restoreToCount(restoreTo);
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ SaveLayerUnclippedMergedClearsTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(10, renderer.getIndex())
+ << "Expect 4 copyTos, 4 copyFroms, 1 clear SimpleRects, and 1 rect.";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_clearClip) {
+ class SaveLayerUnclippedClearClipTestRenderer : public TestRendererBase {
+ public:
+ void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ }
+ void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ ASSERT_NE(nullptr, op.paint);
+ EXPECT_EQ(SkXfermode::kClear_Mode, PaintUtils::getXfermodeDirect(op.paint));
+ EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds)
+ << "Expect dirty rect as clip";
+ ASSERT_NE(nullptr, state.computedState.clipState);
+ EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipState->rect);
+ EXPECT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(3, mIndex++);
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ // save smaller than clip, so we get unclipped behavior
+ canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0));
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ canvas.restore();
+ });
+
+ // draw with partial screen dirty, and assert we see that rect later
+ FrameBuilder frameBuilder(SkRect::MakeLTRB(50, 50, 150, 150), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ SaveLayerUnclippedClearClipTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_reject) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ // unclipped savelayer + rect both in area that won't intersect with dirty
+ canvas.saveLayerAlpha(100, 100, 200, 200, 128, (SaveFlags::Flags)(0));
+ canvas.drawRect(100, 100, 200, 200, SkPaint());
+ canvas.restore();
+ });
+
+ // draw with partial screen dirty that doesn't intersect with savelayer
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ FailRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+}
+
+/* saveLayerUnclipped { saveLayer { saveLayerUnclipped { rect } } } will play back as:
+ * - startTemporaryLayer, onCopyToLayer, onSimpleRects, onRect, onCopyFromLayer, endLayer
+ * - startFrame, onCopyToLayer, onSimpleRects, drawLayer, onCopyFromLayer, endframe
+ */
+RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_complex) {
+ class SaveLayerUnclippedComplexTestRenderer : public TestRendererBase {
+ public:
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) {
+ EXPECT_EQ(0, mIndex++); // savelayer first
+ return (OffscreenBuffer*)0xabcd;
+ }
+ void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 1 || index == 7);
+ }
+ void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 2 || index == 8);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(3, mIndex++);
+ Matrix4 expected;
+ expected.loadTranslate(-100, -100, 0);
+ EXPECT_EQ(Rect(100, 100, 200, 200), state.computedState.clippedBounds);
+ EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform);
+ }
+ void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 4 || index == 10);
+ }
+ void endLayer() override {
+ EXPECT_EQ(5, mIndex++);
+ }
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ EXPECT_EQ(6, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(9, mIndex++);
+ EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
+ }
+ void endFrame(const Rect& repaintRect) override {
+ EXPECT_EQ(11, mIndex++);
+ }
+ void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
+ EXPECT_EQ(12, mIndex++);
+ EXPECT_EQ((OffscreenBuffer*)0xabcd, offscreenBuffer);
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 600, 600, // 500x500 triggers clipping
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(0, 0, 500, 500, 128, (SaveFlags::Flags)0); // unclipped
+ canvas.saveLayerAlpha(100, 100, 400, 400, 128, SaveFlags::ClipToLayer); // clipped
+ canvas.saveLayerAlpha(200, 200, 300, 300, 128, (SaveFlags::Flags)0); // unclipped
+ canvas.drawRect(200, 200, 300, 300, SkPaint());
+ canvas.restore();
+ canvas.restore();
+ canvas.restore();
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(600, 600), 600, 600,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ SaveLayerUnclippedComplexTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(13, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, hwLayer_simple) {
+ class HwLayerSimpleTestRenderer : public TestRendererBase {
+ public:
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+ EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
+ EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+
+ EXPECT_TRUE(state.computedState.transform.isIdentity())
+ << "Transform should be reset within layer";
+
+ EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect())
+ << "Damage rect should be used to clip layer content";
+ }
+ void endLayer() override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ EXPECT_EQ(3, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(4, mIndex++);
+ }
+ void endFrame(const Rect& repaintRect) override {
+ EXPECT_EQ(5, mIndex++);
+ }
+ };
+
+ auto node = TestUtils::createNode(10, 10, 110, 110,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+ OffscreenBuffer** layerHandle = node->getLayerHandle();
+
+ // create RenderNode's layer here in same way prepareTree would
+ OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+ *layerHandle = &layer;
+
+ auto syncedNode = TestUtils::getSyncedNode(node);
+
+ // only enqueue partial damage
+ LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+ layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferLayers(layerUpdateQueue);
+ frameBuilder.deferRenderNode(*syncedNode);
+
+ HwLayerSimpleTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(6, renderer.getIndex());
+
+ // clean up layer pointer, so we can safely destruct RenderNode
+ *layerHandle = nullptr;
+}
+
+RENDERTHREAD_TEST(FrameBuilder, hwLayer_complex) {
+ /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
+ * - startRepaintLayer(child), rect(grey), endLayer
+ * - startTemporaryLayer, drawLayer(child), endLayer
+ * - startRepaintLayer(parent), rect(white), drawLayer(saveLayer), endLayer
+ * - startFrame, drawLayer(parent), endLayerb
+ */
+ class HwLayerComplexTestRenderer : public TestRendererBase {
+ public:
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) {
+ EXPECT_EQ(3, mIndex++); // savelayer first
+ return (OffscreenBuffer*)0xabcd;
+ }
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+ int index = mIndex++;
+ if (index == 0) {
+ // starting inner layer
+ EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+ EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
+ } else if (index == 6) {
+ // starting outer layer
+ EXPECT_EQ(200u, offscreenBuffer->viewportWidth);
+ EXPECT_EQ(200u, offscreenBuffer->viewportHeight);
+ } else { ADD_FAILURE(); }
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ if (index == 1) {
+ // inner layer's rect (white)
+ EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+ } else if (index == 7) {
+ // outer layer's rect (grey)
+ EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+ } else { ADD_FAILURE(); }
+ }
+ void endLayer() override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 2 || index == 5 || index == 9);
+ }
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ EXPECT_EQ(10, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ OffscreenBuffer* layer = *op.layerHandle;
+ int index = mIndex++;
+ if (index == 4) {
+ EXPECT_EQ(100u, layer->viewportWidth);
+ EXPECT_EQ(100u, layer->viewportHeight);
+ } else if (index == 8) {
+ EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
+ } else if (index == 11) {
+ EXPECT_EQ(200u, layer->viewportWidth);
+ EXPECT_EQ(200u, layer->viewportHeight);
+ } else { ADD_FAILURE(); }
+ }
+ void endFrame(const Rect& repaintRect) override {
+ EXPECT_EQ(12, mIndex++);
+ }
+ void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
+ EXPECT_EQ(13, mIndex++);
+ }
+ };
+
+ auto child = TestUtils::createNode(50, 50, 150, 150,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+ OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+ *(child->getLayerHandle()) = &childLayer;
+
+ RenderNode* childPtr = child.get();
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [childPtr](RenderProperties& props, RecordingCanvas& canvas) {
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
+ SkPaint paint;
+ paint.setColor(SK_ColorDKGRAY);
+ canvas.drawRect(0, 0, 200, 200, paint);
+
+ canvas.saveLayerAlpha(50, 50, 150, 150, 128, SaveFlags::ClipToLayer);
+ canvas.drawRenderNode(childPtr);
+ canvas.restore();
+ });
+ OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
+ *(parent->getLayerHandle()) = &parentLayer;
+
+ auto syncedNode = TestUtils::getSyncedNode(parent);
+
+ LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+ layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
+ layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferLayers(layerUpdateQueue);
+ frameBuilder.deferRenderNode(*syncedNode);
+
+ HwLayerComplexTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(14, renderer.getIndex());
+
+ // clean up layer pointers, so we can safely destruct RenderNodes
+ *(child->getLayerHandle()) = nullptr;
+ *(parent->getLayerHandle()) = nullptr;
+}
+
+
+RENDERTHREAD_TEST(FrameBuilder, buildLayer) {
+ class BuildLayerTestRenderer : public TestRendererBase {
+ public:
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+ EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
+ EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect);
+ }
+ void onColorOp(const ColorOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+
+ EXPECT_TRUE(state.computedState.transform.isIdentity())
+ << "Transform should be reset within layer";
+
+ EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect())
+ << "Damage rect should be used to clip layer content";
+ }
+ void endLayer() override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ ADD_FAILURE() << "Primary frame draw not expected in this test";
+ }
+ void endFrame(const Rect& repaintRect) override {
+ ADD_FAILURE() << "Primary frame draw not expected in this test";
+ }
+ };
+
+ auto node = TestUtils::createNode(10, 10, 110, 110,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
+ canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode);
+ });
+ OffscreenBuffer** layerHandle = node->getLayerHandle();
+
+ // create RenderNode's layer here in same way prepareTree would
+ OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+ *layerHandle = &layer;
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+
+ // only enqueue partial damage
+ LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+ layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
+
+ // Draw, but pass empty node list, so no work is done for primary frame
+ FrameBuilder frameBuilder(layerUpdateQueue, sLightGeometry, Caches::getInstance());
+ BuildLayerTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(3, renderer.getIndex());
+
+ // clean up layer pointer, so we can safely destruct RenderNode
+ *layerHandle = nullptr;
+}
+
+static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) {
+ SkPaint paint;
+ paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel
+ canvas->drawRect(0, 0, 100, 100, paint);
+}
+static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) {
+ auto node = TestUtils::createNode(0, 0, 100, 100,
+ [expectedDrawOrder](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedRect(&canvas, expectedDrawOrder);
+ });
+ node->mutateStagingProperties().setTranslationZ(z);
+ node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z);
+ canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
+}
+RENDERTHREAD_TEST(FrameBuilder, zReorder) {
+ class ZReorderTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
+ EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+ }
+ };
+
+ auto parent = TestUtils::createNode(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
+ drawOrderedRect(&canvas, 1);
+ canvas.insertReorderBarrier(true);
+ drawOrderedNode(&canvas, 6, 2.0f);
+ drawOrderedRect(&canvas, 3);
+ drawOrderedNode(&canvas, 4, 0.0f);
+ drawOrderedRect(&canvas, 5);
+ drawOrderedNode(&canvas, 2, -2.0f);
+ drawOrderedNode(&canvas, 7, 2.0f);
+ canvas.insertReorderBarrier(false);
+ drawOrderedRect(&canvas, 8);
+ drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
+ });
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
+ ZReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(10, renderer.getIndex());
+};
+
+RENDERTHREAD_TEST(FrameBuilder, projectionReorder) {
+ static const int scrollX = 5;
+ static const int scrollY = 10;
+ class ProjectionReorderTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ const int index = mIndex++;
+
+ Matrix4 expectedMatrix;
+ switch (index) {
+ case 0:
+ EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
+ EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+ expectedMatrix.loadIdentity();
+ EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask);
+ break;
+ case 1:
+ EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds);
+ EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+ expectedMatrix.loadTranslate(50 - scrollX, 50 - scrollY, 0);
+ ASSERT_NE(nullptr, state.computedState.localProjectionPathMask);
+ EXPECT_EQ(Rect(-35, -30, 45, 50),
+ Rect(state.computedState.localProjectionPathMask->getBounds()));
+ break;
+ case 2:
+ EXPECT_EQ(Rect(100, 50), op.unmappedBounds);
+ EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
+ expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0);
+ EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask);
+ break;
+ default:
+ ADD_FAILURE();
+ }
+ EXPECT_EQ(expectedMatrix, state.computedState.transform);
+ }
+ };
+
+ /**
+ * Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C)
+ * with a projecting child (P) of its own. P would normally draw between B and C's "background"
+ * draw, but because it is projected backwards, it's drawn in between B and C.
+ *
+ * The parent is scrolled by scrollX/scrollY, but this does not affect the background
+ * (which isn't affected by scroll).
+ */
+ auto receiverBackground = TestUtils::createNode(0, 0, 100, 100,
+ [](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setProjectionReceiver(true);
+ // scroll doesn't apply to background, so undone via translationX/Y
+ // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+ properties.setTranslationX(scrollX);
+ properties.setTranslationY(scrollY);
+
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+ auto projectingRipple = TestUtils::createNode(50, 0, 100, 50,
+ [](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setProjectBackwards(true);
+ properties.setClipToBounds(false);
+ SkPaint paint;
+ paint.setColor(SK_ColorDKGRAY);
+ canvas.drawRect(-10, -10, 60, 60, paint);
+ });
+ auto child = TestUtils::createNode(0, 50, 100, 100,
+ [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorBLUE);
+ canvas.drawRect(0, 0, 100, 50, paint);
+ canvas.drawRenderNode(projectingRipple.get());
+ });
+ auto parent = TestUtils::createNode(0, 0, 100, 100,
+ [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
+ // Set a rect outline for the projecting ripple to be masked against.
+ properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f);
+
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
+ canvas.drawRenderNode(receiverBackground.get());
+ canvas.drawRenderNode(child.get());
+ canvas.restore();
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
+ ProjectionReorderTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(3, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, projectionHwLayer) {
+ static const int scrollX = 5;
+ static const int scrollY = 10;
+ class ProjectionHwLayerTestRenderer : public TestRendererBase {
+ public:
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+ EXPECT_EQ(0, mIndex++);
+ }
+ void onArcOp(const ArcOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
+ }
+ void endLayer() override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(3, mIndex++);
+ ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
+ }
+ void onOvalOp(const OvalOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(4, mIndex++);
+ ASSERT_NE(nullptr, state.computedState.localProjectionPathMask);
+ Matrix4 expected;
+ expected.loadTranslate(100 - scrollX, 100 - scrollY, 0);
+ EXPECT_EQ(expected, state.computedState.transform);
+ EXPECT_EQ(Rect(-85, -80, 295, 300),
+ Rect(state.computedState.localProjectionPathMask->getBounds()));
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(5, mIndex++);
+ ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
+ }
+ };
+ auto receiverBackground = TestUtils::createNode(0, 0, 400, 400,
+ [](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setProjectionReceiver(true);
+ // scroll doesn't apply to background, so undone via translationX/Y
+ // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+ properties.setTranslationX(scrollX);
+ properties.setTranslationY(scrollY);
+
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ });
+ auto projectingRipple = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setProjectBackwards(true);
+ properties.setClipToBounds(false);
+ canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds
+ });
+ auto child = TestUtils::createNode(100, 100, 300, 300,
+ [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.mutateLayerProperties().setType(LayerType::RenderLayer);
+ canvas.drawRenderNode(projectingRipple.get());
+ canvas.drawArc(0, 0, 200, 200, 0.0f, 280.0f, true, SkPaint());
+ });
+ auto parent = TestUtils::createNode(0, 0, 400, 400,
+ [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
+ // Set a rect outline for the projecting ripple to be masked against.
+ properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f);
+ canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
+ canvas.drawRenderNode(receiverBackground.get());
+ canvas.drawRenderNode(child.get());
+ });
+
+ OffscreenBuffer** layerHandle = child->getLayerHandle();
+
+ // create RenderNode's layer here in same way prepareTree would, setting windowTransform
+ OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200, 200);
+ Matrix4 windowTransform;
+ windowTransform.loadTranslate(100, 100, 0); // total transform of layer's origin
+ layer.setWindowTransform(windowTransform);
+ *layerHandle = &layer;
+
+ auto syncedNode = TestUtils::getSyncedNode(parent);
+
+ LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+ layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(200, 200));
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferLayers(layerUpdateQueue);
+ frameBuilder.deferRenderNode(*syncedNode);
+
+ ProjectionHwLayerTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(6, renderer.getIndex());
+
+ // clean up layer pointer, so we can safely destruct RenderNode
+ *layerHandle = nullptr;
+}
+
+RENDERTHREAD_TEST(FrameBuilder, projectionChildScroll) {
+ static const int scrollX = 500000;
+ static const int scrollY = 0;
+ class ProjectionChildScrollTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ void onOvalOp(const OvalOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ ASSERT_NE(nullptr, state.computedState.clipState);
+ ASSERT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode);
+ ASSERT_EQ(Rect(400, 400), state.computedState.clipState->rect);
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ };
+ auto receiverBackground = TestUtils::createNode(0, 0, 400, 400,
+ [](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setProjectionReceiver(true);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ });
+ auto projectingRipple = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& properties, RecordingCanvas& canvas) {
+ // scroll doesn't apply to background, so undone via translationX/Y
+ // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+ properties.setTranslationX(scrollX);
+ properties.setTranslationY(scrollY);
+ properties.setProjectBackwards(true);
+ properties.setClipToBounds(false);
+ canvas.drawOval(0, 0, 200, 200, SkPaint());
+ });
+ auto child = TestUtils::createNode(0, 0, 400, 400,
+ [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
+ // Record time clip will be ignored by projectee
+ canvas.clipRect(100, 100, 300, 300, SkRegion::kIntersect_Op);
+
+ canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
+ canvas.drawRenderNode(projectingRipple.get());
+ });
+ auto parent = TestUtils::createNode(0, 0, 400, 400,
+ [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
+ canvas.drawRenderNode(receiverBackground.get());
+ canvas.drawRenderNode(child.get());
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
+ ProjectionChildScrollTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex());
+}
+
+// creates a 100x100 shadow casting node with provided translationZ
+static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
+ return TestUtils::createNode(0, 0, 100, 100,
+ [translationZ](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setTranslationZ(translationZ);
+ properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+}
+
+RENDERTHREAD_TEST(FrameBuilder, shadow) {
+ class ShadowTestRenderer : public TestRendererBase {
+ public:
+ void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_FLOAT_EQ(1.0f, op.casterAlpha);
+ EXPECT_TRUE(op.shadowTask->casterPerimeter.isRect(nullptr));
+ EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.shadowTask->transformXY);
+
+ Matrix4 expectedZ;
+ expectedZ.loadTranslate(0, 0, 5);
+ EXPECT_MATRIX_APPROX_EQ(expectedZ, op.shadowTask->transformZ);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ }
+ };
+
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.insertReorderBarrier(true);
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
+ ShadowTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, shadowSaveLayer) {
+ class ShadowSaveLayerTestRenderer : public TestRendererBase {
+ public:
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(0, mIndex++);
+ return nullptr;
+ }
+ void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ EXPECT_FLOAT_EQ(50, op.shadowTask->lightCenter.x);
+ EXPECT_FLOAT_EQ(40, op.shadowTask->lightCenter.y);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void endLayer() override {
+ EXPECT_EQ(3, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(4, mIndex++);
+ }
+ void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
+ EXPECT_EQ(5, mIndex++);
+ }
+ };
+
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ // save/restore outside of reorderBarrier, so they don't get moved out of place
+ canvas.translate(20, 10);
+ int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SaveFlags::ClipToLayer);
+ canvas.insertReorderBarrier(true);
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+ canvas.insertReorderBarrier(false);
+ canvas.restoreToCount(count);
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
+ ShadowSaveLayerTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(6, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, shadowHwLayer) {
+ class ShadowHwLayerTestRenderer : public TestRendererBase {
+ public:
+ void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+ EXPECT_EQ(0, mIndex++);
+ }
+ void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ EXPECT_FLOAT_EQ(50, op.shadowTask->lightCenter.x);
+ EXPECT_FLOAT_EQ(40, op.shadowTask->lightCenter.y);
+ EXPECT_FLOAT_EQ(30, op.shadowTask->lightRadius);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void endLayer() override {
+ EXPECT_EQ(3, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(4, mIndex++);
+ }
+ };
+
+ auto parent = TestUtils::createNode(50, 60, 150, 160,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
+ canvas.insertReorderBarrier(true);
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.translate(20, 10);
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+ canvas.restore();
+ });
+ OffscreenBuffer** layerHandle = parent->getLayerHandle();
+
+ // create RenderNode's layer here in same way prepareTree would, setting windowTransform
+ OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+ Matrix4 windowTransform;
+ windowTransform.loadTranslate(50, 60, 0); // total transform of layer's origin
+ layer.setWindowTransform(windowTransform);
+ *layerHandle = &layer;
+
+ auto syncedNode = TestUtils::getSyncedNode(parent);
+ LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+ layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100));
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 30}, Caches::getInstance());
+ frameBuilder.deferLayers(layerUpdateQueue);
+ frameBuilder.deferRenderNode(*syncedNode);
+
+ ShadowHwLayerTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(5, renderer.getIndex());
+
+ // clean up layer pointer, so we can safely destruct RenderNode
+ *layerHandle = nullptr;
+}
+
+RENDERTHREAD_TEST(FrameBuilder, shadowLayering) {
+ class ShadowLayeringTestRenderer : public TestRendererBase {
+ public:
+ void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 0 || index == 1);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 2 || index == 3);
+ }
+ };
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.insertReorderBarrier(true);
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get());
+ });
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
+ ShadowLayeringTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(FrameBuilder, shadowClipping) {
+ class ShadowClippingTestRenderer : public TestRendererBase {
+ public:
+ void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipState->rect)
+ << "Shadow must respect pre-barrier canvas clip value.";
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ }
+ };
+ auto parent = TestUtils::createNode(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ // Apply a clip before the reorder barrier/shadow casting child is drawn.
+ // This clip must be applied to the shadow cast by the child.
+ canvas.clipRect(25, 25, 75, 75, SkRegion::kIntersect_Op);
+ canvas.insertReorderBarrier(true);
+ canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
+
+ ShadowClippingTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex());
+}
+
+static void testProperty(std::function<void(RenderProperties&)> propSetupCallback,
+ std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
+ class PropertyTestRenderer : public TestRendererBase {
+ public:
+ PropertyTestRenderer(std::function<void(const RectOp&, const BakedOpState&)> callback)
+ : mCallback(callback) {}
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(mIndex++, 0);
+ mCallback(op, state);
+ }
+ std::function<void(const RectOp&, const BakedOpState&)> mCallback;
+ };
+
+ auto node = TestUtils::createNode(0, 0, 100, 100,
+ [propSetupCallback](RenderProperties& props, RecordingCanvas& canvas) {
+ propSetupCallback(props);
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ PropertyTestRenderer renderer(opValidateCallback);
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, renderPropOverlappingRenderingAlpha) {
+ testProperty([](RenderProperties& properties) {
+ properties.setAlpha(0.5f);
+ properties.setHasOverlappingRendering(false);
+ }, [](const RectOp& op, const BakedOpState& state) {
+ EXPECT_EQ(0.5f, state.alpha) << "Alpha should be applied directly to op";
+ });
+}
+
+RENDERTHREAD_TEST(FrameBuilder, renderPropClipping) {
+ testProperty([](RenderProperties& properties) {
+ properties.setClipToBounds(true);
+ properties.setClipBounds(Rect(10, 20, 300, 400));
+ }, [](const RectOp& op, const BakedOpState& state) {
+ EXPECT_EQ(Rect(10, 20, 100, 100), state.computedState.clippedBounds)
+ << "Clip rect should be intersection of node bounds and clip bounds";
+ });
+}
+
+RENDERTHREAD_TEST(FrameBuilder, renderPropRevealClip) {
+ testProperty([](RenderProperties& properties) {
+ properties.mutableRevealClip().set(true, 50, 50, 25);
+ }, [](const RectOp& op, const BakedOpState& state) {
+ ASSERT_NE(nullptr, state.roundRectClipState);
+ EXPECT_TRUE(state.roundRectClipState->highPriority);
+ EXPECT_EQ(25, state.roundRectClipState->radius);
+ EXPECT_EQ(Rect(50, 50, 50, 50), state.roundRectClipState->innerRect);
+ });
+}
+
+RENDERTHREAD_TEST(FrameBuilder, renderPropOutlineClip) {
+ testProperty([](RenderProperties& properties) {
+ properties.mutableOutline().setShouldClip(true);
+ properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f);
+ }, [](const RectOp& op, const BakedOpState& state) {
+ ASSERT_NE(nullptr, state.roundRectClipState);
+ EXPECT_FALSE(state.roundRectClipState->highPriority);
+ EXPECT_EQ(5, state.roundRectClipState->radius);
+ EXPECT_EQ(Rect(15, 25, 25, 35), state.roundRectClipState->innerRect);
+ });
+}
+
+RENDERTHREAD_TEST(FrameBuilder, renderPropTransform) {
+ testProperty([](RenderProperties& properties) {
+ properties.setLeftTopRightBottom(10, 10, 110, 110);
+
+ SkMatrix staticMatrix = SkMatrix::MakeScale(1.2f, 1.2f);
+ properties.setStaticMatrix(&staticMatrix);
+
+ // ignored, since static overrides animation
+ SkMatrix animationMatrix = SkMatrix::MakeTrans(15, 15);
+ properties.setAnimationMatrix(&animationMatrix);
+
+ properties.setTranslationX(10);
+ properties.setTranslationY(20);
+ properties.setScaleX(0.5f);
+ properties.setScaleY(0.7f);
+ }, [](const RectOp& op, const BakedOpState& state) {
+ Matrix4 matrix;
+ matrix.loadTranslate(10, 10, 0); // left, top
+ matrix.scale(1.2f, 1.2f, 1); // static matrix
+ // ignore animation matrix, since static overrides it
+
+ // translation xy
+ matrix.translate(10, 20);
+
+ // scale xy (from default pivot - center)
+ matrix.translate(50, 50);
+ matrix.scale(0.5f, 0.7f, 1);
+ matrix.translate(-50, -50);
+ EXPECT_MATRIX_APPROX_EQ(matrix, state.computedState.transform)
+ << "Op draw matrix must match expected combination of transformation properties";
+ });
+}
+
+struct SaveLayerAlphaData {
+ uint32_t layerWidth = 0;
+ uint32_t layerHeight = 0;
+ Rect rectClippedBounds;
+ Matrix4 rectMatrix;
+};
+/**
+ * Constructs a view to hit the temporary layer alpha property implementation:
+ * a) 0 < alpha < 1
+ * b) too big for layer (larger than maxTextureSize)
+ * c) overlapping rendering content
+ * returning observed data about layer size and content clip/transform.
+ *
+ * Used to validate clipping behavior of temporary layer, where requested layer size is reduced
+ * (for efficiency, and to fit in layer size constraints) based on parent clip.
+ */
+void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData,
+ std::function<void(RenderProperties&)> propSetupCallback) {
+ class SaveLayerAlphaClipTestRenderer : public TestRendererBase {
+ public:
+ SaveLayerAlphaClipTestRenderer(SaveLayerAlphaData* outData)
+ : mOutData(outData) {}
+
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(0, mIndex++);
+ mOutData->layerWidth = width;
+ mOutData->layerHeight = height;
+ return nullptr;
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+
+ mOutData->rectClippedBounds = state.computedState.clippedBounds;
+ mOutData->rectMatrix = state.computedState.transform;
+ }
+ void endLayer() override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(3, mIndex++);
+ }
+ void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
+ EXPECT_EQ(4, mIndex++);
+ }
+ private:
+ SaveLayerAlphaData* mOutData;
+ };
+
+ ASSERT_GT(10000, DeviceInfo::get()->maxTextureSize())
+ << "Node must be bigger than max texture size to exercise saveLayer codepath";
+ auto node = TestUtils::createNode(0, 0, 10000, 10000,
+ [&propSetupCallback](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setHasOverlappingRendering(true);
+ properties.setAlpha(0.5f); // force saveLayer, since too big for HW layer
+ // apply other properties
+ propSetupCallback(properties);
+
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 10000, 10000, paint);
+ });
+ auto syncedNode = TestUtils::getSyncedNode(node); // sync before querying height
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*syncedNode);
+
+ SaveLayerAlphaClipTestRenderer renderer(outObservedData);
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+
+ // assert, since output won't be valid if we haven't seen a save layer triggered
+ ASSERT_EQ(5, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior.";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaClipBig) {
+ SaveLayerAlphaData observedData;
+ testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
+ properties.setTranslationX(10); // offset rendering content
+ properties.setTranslationY(-2000); // offset rendering content
+ });
+ EXPECT_EQ(190u, observedData.layerWidth);
+ EXPECT_EQ(200u, observedData.layerHeight);
+ EXPECT_EQ(Rect(190, 200), observedData.rectClippedBounds)
+ << "expect content to be clipped to screen area";
+ Matrix4 expected;
+ expected.loadTranslate(0, -2000, 0);
+ EXPECT_MATRIX_APPROX_EQ(expected, observedData.rectMatrix)
+ << "expect content to be translated as part of being clipped";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaRotate) {
+ SaveLayerAlphaData observedData;
+ testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
+ // Translate and rotate the view so that the only visible part is the top left corner of
+ // the view. It will form an isosceles right triangle with a long side length of 200 at the
+ // bottom of the viewport.
+ properties.setTranslationX(100);
+ properties.setTranslationY(100);
+ properties.setPivotX(0);
+ properties.setPivotY(0);
+ properties.setRotation(45);
+ });
+ // ceil(sqrt(2) / 2 * 200) = 142
+ EXPECT_EQ(142u, observedData.layerWidth);
+ EXPECT_EQ(142u, observedData.layerHeight);
+ EXPECT_EQ(Rect(142, 142), observedData.rectClippedBounds);
+ EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix);
+}
+
+RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaScale) {
+ SaveLayerAlphaData observedData;
+ testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
+ properties.setPivotX(0);
+ properties.setPivotY(0);
+ properties.setScaleX(2);
+ properties.setScaleY(0.5f);
+ });
+ EXPECT_EQ(100u, observedData.layerWidth);
+ EXPECT_EQ(400u, observedData.layerHeight);
+ EXPECT_EQ(Rect(100, 400), observedData.rectClippedBounds);
+ EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix);
+}
+
+RENDERTHREAD_TEST(FrameBuilder, clip_replace) {
+ class ClipReplaceTestRenderer : public TestRendererBase {
+ public:
+ void onColorOp(const ColorOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_TRUE(op.localClip->intersectWithRoot);
+ EXPECT_EQ(Rect(20, 10, 30, 40), state.computedState.clipState->rect)
+ << "Expect resolved clip to be intersection of viewport clip and clip op";
+ }
+ };
+ auto node = TestUtils::createNode(20, 20, 30, 30,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.clipRect(0, -20, 10, 30, SkRegion::kReplace_Op);
+ canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode);
+ });
+
+ FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 10, 40, 40), 50, 50,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ ClipReplaceTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(1, renderer.getIndex());
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/tests/unit/GlopBuilderTests.cpp b/libs/hwui/tests/unit/GlopBuilderTests.cpp
new file mode 100644
index 000000000000..95543d33b1ef
--- /dev/null
+++ b/libs/hwui/tests/unit/GlopBuilderTests.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+
+#include "Glop.h"
+#include "GlopBuilder.h"
+#include "Rect.h"
+#include "tests/common/TestUtils.h"
+#include "utils/Color.h"
+
+#include <SkPaint.h>
+
+using namespace android::uirenderer;
+
+static void expectFillEq(Glop::Fill& expectedFill, Glop::Fill& builtFill) {
+ EXPECT_EQ(expectedFill.colorEnabled, builtFill.colorEnabled);
+ if (expectedFill.colorEnabled)
+ EXPECT_EQ(expectedFill.color, builtFill.color);
+
+ EXPECT_EQ(expectedFill.filterMode, builtFill.filterMode);
+ if (expectedFill.filterMode == ProgramDescription::ColorFilterMode::Blend) {
+ EXPECT_EQ(expectedFill.filter.color, builtFill.filter.color);
+ } else if (expectedFill.filterMode == ProgramDescription::ColorFilterMode::Matrix) {
+ Glop::Fill::Filter::Matrix& expectedMatrix = expectedFill.filter.matrix;
+ Glop::Fill::Filter::Matrix& builtMatrix = expectedFill.filter.matrix;
+ EXPECT_TRUE(std::memcmp(expectedMatrix.matrix, builtMatrix.matrix,
+ sizeof(Glop::Fill::Filter::Matrix::matrix)));
+ EXPECT_TRUE(std::memcmp(expectedMatrix.vector, builtMatrix.vector,
+ sizeof(Glop::Fill::Filter::Matrix::vector)));
+ }
+ EXPECT_EQ(expectedFill.skiaShaderData.skiaShaderType, builtFill.skiaShaderData.skiaShaderType);
+ EXPECT_EQ(expectedFill.texture.clamp, builtFill.texture.clamp);
+ EXPECT_EQ(expectedFill.texture.filter, builtFill.texture.filter);
+ EXPECT_EQ(expectedFill.texture.target, builtFill.texture.target);
+ EXPECT_EQ(expectedFill.texture.textureTransform, builtFill.texture.textureTransform);
+}
+
+static void expectBlendEq(Glop::Blend& expectedBlend, Glop::Blend& builtBlend) {
+ EXPECT_EQ(expectedBlend.src, builtBlend.src);
+ EXPECT_EQ(expectedBlend.dst, builtBlend.dst);
+}
+
+static void expectMeshEq(Glop::Mesh& expectedMesh, Glop::Mesh& builtMesh) {
+ EXPECT_EQ(expectedMesh.elementCount, builtMesh.elementCount);
+ EXPECT_EQ(expectedMesh.primitiveMode, builtMesh.primitiveMode);
+ EXPECT_EQ(expectedMesh.indices.indices, builtMesh.indices.indices);
+ EXPECT_EQ(expectedMesh.indices.bufferObject, builtMesh.indices.bufferObject);
+ EXPECT_EQ(expectedMesh.vertices.attribFlags, builtMesh.vertices.attribFlags);
+ EXPECT_EQ(expectedMesh.vertices.bufferObject, builtMesh.vertices.bufferObject);
+ EXPECT_EQ(expectedMesh.vertices.color, builtMesh.vertices.color);
+ EXPECT_EQ(expectedMesh.vertices.position, builtMesh.vertices.position);
+ EXPECT_EQ(expectedMesh.vertices.stride, builtMesh.vertices.stride);
+ EXPECT_EQ(expectedMesh.vertices.texCoord, builtMesh.vertices.texCoord);
+
+ if (builtMesh.vertices.position) {
+ for (int i = 0; i < 4; i++) {
+ TextureVertex& expectedVertex = expectedMesh.mappedVertices[i];
+ TextureVertex& builtVertex = builtMesh.mappedVertices[i];
+ EXPECT_EQ(expectedVertex.u, builtVertex.u);
+ EXPECT_EQ(expectedVertex.v, builtVertex.v);
+ EXPECT_EQ(expectedVertex.x, builtVertex.x);
+ EXPECT_EQ(expectedVertex.y, builtVertex.y);
+ }
+ }
+}
+
+static void expectTransformEq(Glop::Transform& expectedTransform, Glop::Transform& builtTransform) {
+ EXPECT_EQ(expectedTransform.canvas, builtTransform.canvas);
+ EXPECT_EQ(expectedTransform.modelView, builtTransform.modelView);
+ EXPECT_EQ(expectedTransform.transformFlags, expectedTransform.transformFlags);
+}
+
+static void expectGlopEq(Glop& expectedGlop, Glop& builtGlop) {
+#if !HWUI_NEW_OPS
+ EXPECT_EQ(expectedGlop.bounds, builtGlop.bounds);
+#endif
+ expectBlendEq(expectedGlop.blend, builtGlop.blend);
+ expectFillEq(expectedGlop.fill, builtGlop.fill);
+ expectMeshEq(expectedGlop.mesh, builtGlop.mesh);
+ expectTransformEq(expectedGlop.transform, builtGlop.transform);
+}
+
+static std::unique_ptr<Glop> blackUnitQuadGlop(RenderState& renderState) {
+ std::unique_ptr<Glop> glop(new Glop());
+ glop->blend = { GL_ZERO, GL_ZERO };
+ glop->mesh.elementCount = 4;
+ glop->mesh.primitiveMode = GL_TRIANGLE_STRIP;
+ glop->mesh.indices.indices = nullptr;
+ glop->mesh.indices.bufferObject = GL_ZERO;
+ glop->mesh.vertices = {
+ renderState.meshState().getUnitQuadVBO(),
+ VertexAttribFlags::None,
+ nullptr, nullptr, nullptr,
+ kTextureVertexStride };
+ glop->transform.modelView.loadIdentity();
+ glop->fill.colorEnabled = true;
+ glop->fill.color.set(Color::Black);
+ glop->fill.skiaShaderData.skiaShaderType = kNone_SkiaShaderType;
+ glop->fill.filterMode = ProgramDescription::ColorFilterMode::None;
+ glop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
+ return glop;
+}
+
+RENDERTHREAD_TEST(GlopBuilder, rectSnapTest) {
+ RenderState& renderState = renderThread.renderState();
+ Caches& caches = Caches::getInstance();
+ SkPaint paint;
+ Rect dest(1, 1, 100, 100);
+ Matrix4 simpleTranslate;
+ simpleTranslate.loadTranslate(0.7, 0.7, 0);
+ Glop glop;
+ GlopBuilder(renderState, caches, &glop)
+ .setRoundRectClipState(nullptr)
+ .setMeshUnitQuad()
+ .setFillPaint(paint, 1.0f)
+ .setTransform(simpleTranslate, TransformFlags::None)
+ .setModelViewMapUnitToRectSnap(dest)
+ .build();
+
+ std::unique_ptr<Glop> goldenGlop(blackUnitQuadGlop(renderState));
+ // Rect(1,1,100,100) is the set destination,
+ // so unit quad should be translated by (1,1) and scaled by (99, 99)
+ // Tricky part: because translate (0.7, 0.7) and snapping were set in glopBuilder,
+ // unit quad also should be translate by additional (0.3, 0.3) to snap to exact pixels.
+ goldenGlop->transform.modelView.loadTranslate(1.3, 1.3, 0);
+ goldenGlop->transform.modelView.scale(99, 99, 1);
+#if !HWUI_NEW_OPS
+ goldenGlop->bounds = android::uirenderer::Rect(1.70, 1.70, 100.70, 100.70);
+#endif
+ goldenGlop->transform.canvas = simpleTranslate;
+ goldenGlop->fill.texture.filter = GL_NEAREST;
+ expectGlopEq(*goldenGlop, glop);
+}
diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
new file mode 100644
index 000000000000..aa1dcb2ea51b
--- /dev/null
+++ b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+#include <GpuMemoryTracker.h>
+
+#include "renderthread/EglManager.h"
+#include "renderthread/RenderThread.h"
+#include "tests/common/TestUtils.h"
+
+#include <utils/StrongPointer.h>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+
+class TestGPUObject : public GpuMemoryTracker {
+public:
+ TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {}
+
+ void changeSize(int newSize) {
+ notifySizeChanged(newSize);
+ }
+};
+
+// Other tests may have created a renderthread and EGL context.
+// This will destroy the EGLContext on RenderThread if it exists so that the
+// current thread can spoof being a GPU thread
+static void destroyEglContext() {
+ if (TestUtils::isRenderThreadRunning()) {
+ TestUtils::runOnRenderThread([](RenderThread& thread) {
+ thread.eglManager().destroy();
+ });
+ }
+}
+
+TEST(GpuMemoryTracker, sizeCheck) {
+ destroyEglContext();
+
+ GpuMemoryTracker::onGLContextCreated();
+ ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+ ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
+ {
+ TestGPUObject myObj;
+ ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
+ myObj.changeSize(500);
+ ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+ myObj.changeSize(1000);
+ ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+ myObj.changeSize(300);
+ ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+ }
+ ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+ ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
+ GpuMemoryTracker::onGLContextDestroyed();
+}
diff --git a/libs/hwui/tests/unit/GradientCacheTests.cpp b/libs/hwui/tests/unit/GradientCacheTests.cpp
new file mode 100644
index 000000000000..0ee96470fc57
--- /dev/null
+++ b/libs/hwui/tests/unit/GradientCacheTests.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+
+#include "Extensions.h"
+#include "GradientCache.h"
+#include "tests/common/TestUtils.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+RENDERTHREAD_TEST(GradientCache, addRemove) {
+ Extensions extensions;
+ GradientCache cache(extensions);
+ ASSERT_LT(1000u, cache.getMaxSize()) << "Expect non-trivial size";
+
+ SkColor colors[] = { 0xFF00FF00, 0xFFFF0000, 0xFF0000FF };
+ float positions[] = { 1, 2, 3 };
+ Texture* texture = cache.get(colors, positions, 3);
+ ASSERT_TRUE(texture);
+ ASSERT_FALSE(texture->cleanup);
+ ASSERT_EQ((uint32_t) texture->objectSize(), cache.getSize());
+ ASSERT_TRUE(cache.getSize());
+ cache.clear();
+ ASSERT_EQ(cache.getSize(), 0u);
+}
diff --git a/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp b/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp
new file mode 100644
index 000000000000..8b0e91c77396
--- /dev/null
+++ b/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include <LayerUpdateQueue.h>
+#include <RenderNode.h>
+
+#include <tests/common/TestUtils.h>
+
+namespace android {
+namespace uirenderer {
+
+TEST(LayerUpdateQueue, construct) {
+ LayerUpdateQueue queue;
+ EXPECT_TRUE(queue.entries().empty());
+}
+
+// sync node properties, so properties() reflects correct width and height
+static sp<RenderNode> createSyncedNode(uint32_t width, uint32_t height) {
+ sp<RenderNode> node = TestUtils::createNode(0, 0, width, height, nullptr);
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+ return node;
+}
+
+TEST(LayerUpdateQueue, enqueueSimple) {
+ sp<RenderNode> a = createSyncedNode(100, 100);
+ sp<RenderNode> b = createSyncedNode(200, 200);
+
+ LayerUpdateQueue queue;
+ queue.enqueueLayerWithDamage(a.get(), Rect(25, 25, 75, 75));
+ queue.enqueueLayerWithDamage(b.get(), Rect(100, 100, 300, 300));
+
+ EXPECT_EQ(2u, queue.entries().size());
+
+ EXPECT_EQ(a.get(), queue.entries()[0].renderNode);
+ EXPECT_EQ(Rect(25, 25, 75, 75), queue.entries()[0].damage);
+ EXPECT_EQ(b.get(), queue.entries()[1].renderNode);
+ EXPECT_EQ(Rect(100, 100, 200, 200), queue.entries()[1].damage); // clipped to bounds
+}
+
+TEST(LayerUpdateQueue, enqueueUnion) {
+ sp<RenderNode> a = createSyncedNode(100, 100);
+
+ LayerUpdateQueue queue;
+ queue.enqueueLayerWithDamage(a.get(), Rect(10, 10, 20, 20));
+ queue.enqueueLayerWithDamage(a.get(), Rect(30, 30, 40, 40));
+
+ EXPECT_EQ(1u, queue.entries().size());
+
+ EXPECT_EQ(a.get(), queue.entries()[0].renderNode);
+ EXPECT_EQ(Rect(10, 10, 40, 40), queue.entries()[0].damage);
+}
+
+TEST(LayerUpdateQueue, clear) {
+ sp<RenderNode> a = createSyncedNode(100, 100);
+
+ LayerUpdateQueue queue;
+ queue.enqueueLayerWithDamage(a.get(), Rect(100, 100));
+
+ EXPECT_FALSE(queue.entries().empty());
+
+ queue.clear();
+
+ EXPECT_TRUE(queue.entries().empty());
+}
+
+};
+};
diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp
new file mode 100644
index 000000000000..6148b33eceb8
--- /dev/null
+++ b/libs/hwui/tests/unit/LeakCheckTests.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 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 "BakedOpRenderer.h"
+#include "BakedOpDispatcher.h"
+#include "FrameBuilder.h"
+#include "LayerUpdateQueue.h"
+#include "RecordingCanvas.h"
+#include "tests/common/TestUtils.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+const FrameBuilder::LightGeometry sLightGeometery = { {100, 100, 100}, 50};
+const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 };
+
+RENDERTHREAD_TEST(LeakCheck, saveLayer_overdrawRejection) {
+ auto node = TestUtils::createNode(0, 0, 100, 100,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer);
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ canvas.restore();
+
+ // opaque draw, rejects saveLayer beneath
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ });
+ RenderState& renderState = renderThread.renderState();
+ Caches& caches = Caches::getInstance();
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometery, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+ BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
+ frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
+}
+
+RENDERTHREAD_TEST(LeakCheck, saveLayerUnclipped_simple) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0));
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ canvas.restore();
+ });
+ RenderState& renderState = renderThread.renderState();
+ Caches& caches = Caches::getInstance();
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
+ sLightGeometery, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+ BakedOpRenderer renderer(caches, renderState, true, sLightInfo);
+ frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
+}
diff --git a/libs/hwui/tests/unit/LinearAllocatorTests.cpp b/libs/hwui/tests/unit/LinearAllocatorTests.cpp
new file mode 100644
index 000000000000..ffcbf128fc3a
--- /dev/null
+++ b/libs/hwui/tests/unit/LinearAllocatorTests.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+#include <utils/LinearAllocator.h>
+
+#include <tests/common/TestUtils.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+struct SimplePair {
+ int one = 1;
+ int two = 2;
+};
+
+TEST(LinearAllocator, create) {
+ LinearAllocator la;
+ EXPECT_EQ(0u, la.usedSize());
+ la.alloc<char>(64);
+ // There's some internal tracking as well as padding
+ // so the usedSize isn't strictly defined
+ EXPECT_LE(64u, la.usedSize());
+ EXPECT_GT(80u, la.usedSize());
+ auto pair = la.create<SimplePair>();
+ EXPECT_LE(64u + sizeof(SimplePair), la.usedSize());
+ EXPECT_GT(80u + sizeof(SimplePair), la.usedSize());
+ EXPECT_EQ(1, pair->one);
+ EXPECT_EQ(2, pair->two);
+}
+
+TEST(LinearAllocator, dtor) {
+ int destroyed[10] = { 0 };
+ {
+ LinearAllocator la;
+ for (int i = 0; i < 5; i++) {
+ la.create<TestUtils::SignalingDtor>()->setSignal(destroyed + i);
+ la.create<SimplePair>();
+ }
+ la.alloc<char>(100);
+ for (int i = 0; i < 5; i++) {
+ la.create<TestUtils::SignalingDtor>(destroyed + 5 + i);
+ la.create_trivial<SimplePair>();
+ }
+ la.alloc<char>(100);
+ for (int i = 0; i < 10; i++) {
+ EXPECT_EQ(0, destroyed[i]);
+ }
+ }
+ for (int i = 0; i < 10; i++) {
+ EXPECT_EQ(1, destroyed[i]);
+ }
+}
+
+TEST(LinearAllocator, rewind) {
+ int destroyed = 0;
+ {
+ LinearAllocator la;
+ auto addr = la.alloc<char>(100);
+ EXPECT_LE(100u, la.usedSize());
+ la.rewindIfLastAlloc(addr, 100);
+ EXPECT_GT(16u, la.usedSize());
+ size_t emptySize = la.usedSize();
+ auto sigdtor = la.create<TestUtils::SignalingDtor>();
+ sigdtor->setSignal(&destroyed);
+ EXPECT_EQ(0, destroyed);
+ EXPECT_LE(emptySize, la.usedSize());
+ la.rewindIfLastAlloc(sigdtor);
+ EXPECT_EQ(1, destroyed);
+ EXPECT_EQ(emptySize, la.usedSize());
+ }
+ // Checking for a double-destroy case
+ EXPECT_EQ(1, destroyed);
+}
+
+TEST(LinearStdAllocator, simpleAllocate) {
+ LinearAllocator la;
+ LinearStdAllocator<void*> stdAllocator(la);
+
+ std::vector<char, LinearStdAllocator<char> > v(stdAllocator);
+ v.push_back(0);
+ char* initialLocation = &v[0];
+ v.push_back(10);
+ v.push_back(20);
+ v.push_back(30);
+
+ // expect to have allocated (since no space reserved), so [0] will have moved to
+ // slightly further down in the same LinearAllocator page
+ EXPECT_LT(initialLocation, &v[0]);
+ EXPECT_GT(initialLocation + 20, &v[0]);
+
+ // expect to have allocated again inserting 4 more entries
+ char* lastLocation = &v[0];
+ v.push_back(40);
+ v.push_back(50);
+ v.push_back(60);
+ v.push_back(70);
+
+ EXPECT_LT(lastLocation, &v[0]);
+ EXPECT_GT(lastLocation + 20, &v[0]);
+
+}
+
+TEST(LsaVector, dtorCheck) {
+ LinearAllocator allocator;
+ LinearStdAllocator<void*> stdAllocator(allocator);
+
+ for (int size : {1, 2, 3, 500}) {
+ int destroyed = 0;
+ {
+ LsaVector<std::unique_ptr<TestUtils::SignalingDtor> > vector(stdAllocator);
+ for (int i = 0; i < size; i++) {
+ vector.emplace_back(new TestUtils::SignalingDtor(&destroyed));
+ }
+ EXPECT_EQ(0, destroyed);
+ EXPECT_EQ(size, (int) vector.size());
+ }
+ EXPECT_EQ(size, destroyed);
+ }
+}
diff --git a/libs/hwui/tests/unit/MatrixTests.cpp b/libs/hwui/tests/unit/MatrixTests.cpp
new file mode 100644
index 000000000000..eddab878a49d
--- /dev/null
+++ b/libs/hwui/tests/unit/MatrixTests.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+
+#include "Matrix.h"
+#include "Rect.h"
+
+using namespace android::uirenderer;
+
+TEST(Matrix, mapRect_emptyScaleSkew) {
+ // Skew, so we don't hit identity/translate/simple fast paths
+ Matrix4 scaleMatrix;
+ scaleMatrix.loadScale(10, 10, 1);
+ scaleMatrix.skew(0.1f, 0.1f);
+
+ // non-zero empty rect, so sorting x/y would make rect non-empty
+ Rect empty(15, 20, 15, 100);
+ ASSERT_TRUE(empty.isEmpty());
+ scaleMatrix.mapRect(empty);
+ EXPECT_EQ(Rect(170, 215, 250, 1015), empty);
+ EXPECT_FALSE(empty.isEmpty())
+ << "Empty 'line' rect doesn't remain empty when skewed.";
+}
+
+TEST(Matrix, mapRect_emptyRotate) {
+ // Skew, so we don't hit identity/translate/simple fast paths
+ Matrix4 skewMatrix;
+ skewMatrix.loadRotate(45);
+
+ // non-zero empty rect, so sorting x/y would make rect non-empty
+ Rect lineRect(0, 100);
+ ASSERT_TRUE(lineRect.isEmpty());
+ skewMatrix.mapRect(lineRect);
+ EXPECT_FALSE(lineRect.isEmpty())
+ << "Empty 'line' rect doesn't remain empty when rotated.";
+}
diff --git a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
new file mode 100644
index 000000000000..b7950aab5662
--- /dev/null
+++ b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+#include <Rect.h>
+#include <renderstate/OffscreenBufferPool.h>
+
+#include <tests/common/TestUtils.h>
+
+using namespace android::uirenderer;
+
+TEST(OffscreenBuffer, computeIdealDimension) {
+ EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(1));
+ EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(31));
+ EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(33));
+ EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(64));
+ EXPECT_EQ(1024u, OffscreenBuffer::computeIdealDimension(1000));
+}
+
+RENDERTHREAD_TEST(OffscreenBuffer, construct) {
+ OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 49u, 149u);
+ EXPECT_EQ(49u, layer.viewportWidth);
+ EXPECT_EQ(149u, layer.viewportHeight);
+
+ EXPECT_EQ(64u, layer.texture.width());
+ EXPECT_EQ(192u, layer.texture.height());
+
+ EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
+}
+
+RENDERTHREAD_TEST(OffscreenBuffer, getTextureCoordinates) {
+ OffscreenBuffer layerAligned(renderThread.renderState(), Caches::getInstance(), 256u, 256u);
+ EXPECT_EQ(Rect(0, 1, 1, 0),
+ layerAligned.getTextureCoordinates());
+
+ OffscreenBuffer layerUnaligned(renderThread.renderState(), Caches::getInstance(), 200u, 225u);
+ EXPECT_EQ(Rect(0, 225.0f / 256.0f, 200.0f / 256.0f, 0),
+ layerUnaligned.getTextureCoordinates());
+}
+
+RENDERTHREAD_TEST(OffscreenBuffer, dirty) {
+ OffscreenBuffer buffer(renderThread.renderState(), Caches::getInstance(), 256u, 256u);
+ buffer.dirty(Rect(-100, -100, 100, 100));
+ EXPECT_EQ(android::Rect(100, 100), buffer.region.getBounds());
+}
+
+RENDERTHREAD_TEST(OffscreenBufferPool, construct) {
+ OffscreenBufferPool pool;
+ EXPECT_EQ(0u, pool.getCount()) << "pool must be created empty";
+ EXPECT_EQ(0u, pool.getSize()) << "pool must be created empty";
+ EXPECT_EQ((uint32_t) Properties::layerPoolSize, pool.getMaxSize())
+ << "pool must read size from Properties";
+}
+
+RENDERTHREAD_TEST(OffscreenBufferPool, getPutClear) {
+ OffscreenBufferPool pool;
+
+ auto layer = pool.get(renderThread.renderState(), 100u, 200u);
+ EXPECT_EQ(100u, layer->viewportWidth);
+ EXPECT_EQ(200u, layer->viewportHeight);
+
+ ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize());
+
+ pool.putOrDelete(layer);
+ ASSERT_EQ(layer->getSizeInBytes(), pool.getSize());
+
+ auto layer2 = pool.get(renderThread.renderState(), 102u, 202u);
+ EXPECT_EQ(layer, layer2) << "layer should be recycled";
+ ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer";
+
+ pool.putOrDelete(layer);
+ EXPECT_EQ(1u, pool.getCount());
+ pool.clear();
+ EXPECT_EQ(0u, pool.getSize());
+ EXPECT_EQ(0u, pool.getCount());
+}
+
+RENDERTHREAD_TEST(OffscreenBufferPool, resize) {
+ OffscreenBufferPool pool;
+
+ auto layer = pool.get(renderThread.renderState(), 64u, 64u);
+ layer->dirty(Rect(64, 64));
+
+ // resize in place
+ ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
+ EXPECT_TRUE(layer->region.isEmpty()) << "In place resize should clear usage region";
+ EXPECT_EQ(60u, layer->viewportWidth);
+ EXPECT_EQ(55u, layer->viewportHeight);
+ EXPECT_EQ(64u, layer->texture.width());
+ EXPECT_EQ(64u, layer->texture.height());
+
+ // resized to use different object in pool
+ auto layer2 = pool.get(renderThread.renderState(), 128u, 128u);
+ layer2->dirty(Rect(128, 128));
+ EXPECT_FALSE(layer2->region.isEmpty());
+ pool.putOrDelete(layer2);
+ ASSERT_EQ(1u, pool.getCount());
+
+ ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
+ EXPECT_TRUE(layer2->region.isEmpty()) << "Swap resize should clear usage region";
+ EXPECT_EQ(120u, layer2->viewportWidth);
+ EXPECT_EQ(125u, layer2->viewportHeight);
+ EXPECT_EQ(128u, layer2->texture.width());
+ EXPECT_EQ(128u, layer2->texture.height());
+
+ // original allocation now only thing in pool
+ EXPECT_EQ(1u, pool.getCount());
+ EXPECT_EQ(layer->getSizeInBytes(), pool.getSize());
+
+ pool.putOrDelete(layer2);
+}
+
+RENDERTHREAD_TEST(OffscreenBufferPool, putAndDestroy) {
+ OffscreenBufferPool pool;
+ // layer too big to return to the pool
+ // Note: this relies on the fact that the pool won't reject based on max texture size
+ auto hugeLayer = pool.get(renderThread.renderState(), pool.getMaxSize() / 64, 64);
+ EXPECT_GT(hugeLayer->getSizeInBytes(), pool.getMaxSize());
+ pool.putOrDelete(hugeLayer);
+ EXPECT_EQ(0u, pool.getCount()); // failed to put (so was destroyed instead)
+}
+
+RENDERTHREAD_TEST(OffscreenBufferPool, clear) {
+ EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer));
+ OffscreenBufferPool pool;
+
+ // Create many buffers, with several at each size
+ std::vector<OffscreenBuffer*> buffers;
+ for (int size = 32; size <= 128; size += 32) {
+ for (int i = 0; i < 10; i++) {
+ buffers.push_back(pool.get(renderThread.renderState(), size, size));
+ }
+ }
+ EXPECT_EQ(0u, pool.getCount()) << "Expect nothing inside";
+ for (auto& buffer : buffers) pool.putOrDelete(buffer);
+ EXPECT_EQ(40u, pool.getCount()) << "Expect all items added";
+ EXPECT_EQ(40, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer));
+ pool.clear();
+ EXPECT_EQ(0u, pool.getCount()) << "Expect all items cleared";
+
+ EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer));
+}
diff --git a/libs/hwui/tests/unit/OpDumperTests.cpp b/libs/hwui/tests/unit/OpDumperTests.cpp
new file mode 100644
index 000000000000..01840d72b766
--- /dev/null
+++ b/libs/hwui/tests/unit/OpDumperTests.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+
+#include "tests/common/TestUtils.h"
+#include "OpDumper.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+TEST(OpDumper, dump) {
+ SkPaint paint;
+ RectOp op(uirenderer::Rect(100, 100), Matrix4::identity(), nullptr, &paint);
+
+ std::stringstream stream;
+ OpDumper::dump(op, stream);
+ EXPECT_STREQ("RectOp [100 x 100]", stream.str().c_str());
+
+ stream.str("");
+ OpDumper::dump(op, stream, 2);
+ EXPECT_STREQ(" RectOp [100 x 100]", stream.str().c_str());
+
+ ClipRect clipRect(uirenderer::Rect(50, 50));
+ op.localClip = &clipRect;
+
+ stream.str("");
+ OpDumper::dump(op, stream, 2);
+ EXPECT_STREQ(" RectOp [100 x 100] clip=[50 x 50] mode=0", stream.str().c_str());
+}
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
new file mode 100644
index 000000000000..18171de250d0
--- /dev/null
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -0,0 +1,762 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include <DeferredLayerUpdater.h>
+#include <RecordedOp.h>
+#include <RecordingCanvas.h>
+#include <hwui/Paint.h>
+#include <minikin/Layout.h>
+#include <tests/common/TestUtils.h>
+#include <utils/Color.h>
+
+#include <SkGradientShader.h>
+#include <SkShader.h>
+
+namespace android {
+namespace uirenderer {
+
+static void playbackOps(const DisplayList& displayList,
+ std::function<void(const RecordedOp&)> opReceiver) {
+ for (auto& chunk : displayList.getChunks()) {
+ for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
+ RecordedOp* op = displayList.getOps()[opIndex];
+ opReceiver(*op);
+ }
+ }
+}
+
+static void validateSingleOp(std::unique_ptr<DisplayList>& dl,
+ std::function<void(const RecordedOp& op)> opValidator) {
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+ opValidator(*(dl->getOps()[0]));
+}
+
+TEST(RecordingCanvas, emptyPlayback) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.restore();
+ });
+ playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
+}
+
+TEST(RecordingCanvas, clipRect) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op);
+ canvas.drawRect(0, 0, 50, 50, SkPaint());
+ canvas.drawRect(50, 50, 100, 100, SkPaint());
+ canvas.restore();
+ });
+
+ ASSERT_EQ(2u, dl->getOps().size()) << "Must be exactly two ops";
+ EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[0]->localClip);
+ EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[1]->localClip);
+ EXPECT_EQ(dl->getOps()[0]->localClip, dl->getOps()[1]->localClip)
+ << "Clip should be serialized once";
+}
+
+TEST(RecordingCanvas, emptyClipRect) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op);
+ canvas.clipRect(100, 100, 200, 200, SkRegion::kIntersect_Op);
+ canvas.drawRect(0, 0, 50, 50, SkPaint()); // rejected at record time
+ canvas.restore();
+ });
+ ASSERT_EQ(0u, dl->getOps().size()) << "Must be zero ops. Rect should be rejected.";
+}
+
+TEST(RecordingCanvas, drawArc) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.drawArc(0, 0, 200, 200, 0, 180, true, SkPaint());
+ canvas.drawArc(0, 0, 100, 100, 0, 360, true, SkPaint());
+ });
+
+ auto&& ops = dl->getOps();
+ ASSERT_EQ(2u, ops.size()) << "Must be exactly two ops";
+ EXPECT_EQ(RecordedOpId::ArcOp, ops[0]->opId);
+ EXPECT_EQ(Rect(200, 200), ops[0]->unmappedBounds);
+
+ EXPECT_EQ(RecordedOpId::OvalOp, ops[1]->opId)
+ << "Circular arcs should be converted to ovals";
+ EXPECT_EQ(Rect(100, 100), ops[1]->unmappedBounds);
+}
+
+TEST(RecordingCanvas, drawLines) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setStrokeWidth(20); // doesn't affect recorded bounds - would be resolved at bake time
+ float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line
+ canvas.drawLines(&points[0], 7, paint);
+ });
+
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+ auto op = dl->getOps()[0];
+ ASSERT_EQ(RecordedOpId::LinesOp, op->opId);
+ EXPECT_EQ(4, ((LinesOp*)op)->floatCount)
+ << "float count must be rounded down to closest multiple of 4";
+ EXPECT_EQ(Rect(20, 10), op->unmappedBounds)
+ << "unmapped bounds must be size of line, and not outset for stroke width";
+}
+
+TEST(RecordingCanvas, drawRect) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+ canvas.drawRect(10, 20, 90, 180, SkPaint());
+ });
+
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+ auto op = *(dl->getOps()[0]);
+ ASSERT_EQ(RecordedOpId::RectOp, op.opId);
+ EXPECT_EQ(nullptr, op.localClip);
+ EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
+}
+
+TEST(RecordingCanvas, drawRoundRect) {
+ // Round case - stays rounded
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+ canvas.drawRoundRect(0, 0, 100, 100, 10, 10, SkPaint());
+ });
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+ ASSERT_EQ(RecordedOpId::RoundRectOp, dl->getOps()[0]->opId);
+
+ // Non-rounded case - turned into drawRect
+ dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+ canvas.drawRoundRect(0, 0, 100, 100, 0, -1, SkPaint());
+ });
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+ ASSERT_EQ(RecordedOpId::RectOp, dl->getOps()[0]->opId)
+ << "Non-rounded rects should be converted";
+}
+
+TEST(RecordingCanvas, drawGlyphs) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
+ });
+
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ count++;
+ ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+ EXPECT_EQ(nullptr, op.localClip);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
+ EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25))
+ << "Op expected to be 25+ pixels wide, 10+ pixels tall";
+ });
+ ASSERT_EQ(1, count);
+}
+
+TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 2; j++) {
+ paint.setUnderlineText(i != 0);
+ paint.setStrikeThruText(j != 0);
+ TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
+ }
+ }
+ });
+
+ auto ops = dl->getOps();
+ ASSERT_EQ(8u, ops.size());
+
+ int index = 0;
+ EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough
+
+ EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+ EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only
+
+ EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+ EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only
+
+ EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+ EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline
+ EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough
+}
+
+TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ paint.setTextAlign(SkPaint::kLeft_Align);
+ TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
+ paint.setTextAlign(SkPaint::kCenter_Align);
+ TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
+ paint.setTextAlign(SkPaint::kRight_Align);
+ TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
+ });
+
+ int count = 0;
+ float lastX = FLT_MAX;
+ playbackOps(*dl, [&count, &lastX](const RecordedOp& op) {
+ count++;
+ ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+ EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign())
+ << "recorded drawText commands must force kLeft_Align on their paint";
+
+ // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class)
+ EXPECT_GT(lastX, ((const TextOp&)op).x)
+ << "x coordinate should reduce across each of the draw commands, from alignment";
+ lastX = ((const TextOp&)op).x;
+ });
+ ASSERT_EQ(3, count);
+}
+
+TEST(RecordingCanvas, drawColor) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.drawColor(Color::Black, SkXfermode::kSrcOver_Mode);
+ });
+
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+ auto op = *(dl->getOps()[0]);
+ EXPECT_EQ(RecordedOpId::ColorOp, op.opId);
+ EXPECT_EQ(nullptr, op.localClip);
+ EXPECT_TRUE(op.unmappedBounds.isEmpty()) << "Expect undefined recorded bounds";
+}
+
+TEST(RecordingCanvas, backgroundAndImage) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+ SkBitmap bitmap;
+ bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25));
+ SkPaint paint;
+ paint.setColor(SK_ColorBLUE);
+
+ canvas.save(SaveFlags::MatrixClip);
+ {
+ // a background!
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.drawRect(0, 0, 100, 200, paint);
+ canvas.restore();
+ }
+ {
+ // an image!
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.translate(25, 25);
+ canvas.scale(2, 2);
+ canvas.drawBitmap(bitmap, 0, 0, nullptr);
+ canvas.restore();
+ }
+ canvas.restore();
+ });
+
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count == 0) {
+ ASSERT_EQ(RecordedOpId::RectOp, op.opId);
+ ASSERT_NE(nullptr, op.paint);
+ EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
+ EXPECT_EQ(Rect(100, 200), op.unmappedBounds);
+ EXPECT_EQ(nullptr, op.localClip);
+
+ Matrix4 expectedMatrix;
+ expectedMatrix.loadIdentity();
+ EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+ } else {
+ ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
+ EXPECT_EQ(nullptr, op.paint);
+ EXPECT_EQ(Rect(25, 25), op.unmappedBounds);
+ EXPECT_EQ(nullptr, op.localClip);
+
+ Matrix4 expectedMatrix;
+ expectedMatrix.loadTranslate(25, 25, 0);
+ expectedMatrix.scale(2, 2, 1);
+ EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+ }
+ count++;
+ });
+ ASSERT_EQ(2, count);
+}
+
+RENDERTHREAD_TEST(RecordingCanvas, textureLayer) {
+ auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100,
+ SkMatrix::MakeTrans(5, 5));
+
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
+ [&layerUpdater](RecordingCanvas& canvas) {
+ canvas.drawLayer(layerUpdater.get());
+ });
+
+ validateSingleOp(dl, [] (const RecordedOp& op) {
+ ASSERT_EQ(RecordedOpId::TextureLayerOp, op.opId);
+ ASSERT_TRUE(op.localMatrix.isIdentity()) << "Op must not apply matrix at record time.";
+ });
+}
+
+TEST(RecordingCanvas, saveLayer_simple) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, SaveFlags::ClipToLayer);
+ canvas.drawRect(10, 20, 190, 180, SkPaint());
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ Matrix4 expectedMatrix;
+ switch(count++) {
+ case 0:
+ EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
+ EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+ EXPECT_EQ(nullptr, op.localClip);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
+ break;
+ case 1:
+ EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+ EXPECT_CLIP_RECT(Rect(180, 160), op.localClip);
+ EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+ expectedMatrix.loadTranslate(-10, -20, 0);
+ EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+ break;
+ case 2:
+ EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
+ // Don't bother asserting recording state data - it's not used
+ break;
+ default:
+ ADD_FAILURE();
+ }
+ });
+ EXPECT_EQ(3, count);
+}
+
+TEST(RecordingCanvas, saveLayer_missingRestore) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer);
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ // Note: restore omitted, shouldn't result in unmatched save
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count++ == 2) {
+ EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
+ }
+ });
+ EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer";
+}
+
+TEST(RecordingCanvas, saveLayer_simpleUnclipped) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
+ canvas.drawRect(10, 20, 190, 180, SkPaint());
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ switch(count++) {
+ case 0:
+ EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, op.opId);
+ EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+ EXPECT_EQ(nullptr, op.localClip);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
+ break;
+ case 1:
+ EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+ EXPECT_EQ(nullptr, op.localClip);
+ EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
+ break;
+ case 2:
+ EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId);
+ // Don't bother asserting recording state data - it's not used
+ break;
+ default:
+ ADD_FAILURE();
+ }
+ });
+ EXPECT_EQ(3, count);
+}
+
+TEST(RecordingCanvas, saveLayer_addClipFlag) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.clipRect(10, 20, 190, 180, SkRegion::kIntersect_Op);
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
+ canvas.drawRect(10, 20, 190, 180, SkPaint());
+ canvas.restore();
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count++ == 0) {
+ EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId)
+ << "Clip + unclipped saveLayer should result in a clipped layer";
+ }
+ });
+ EXPECT_EQ(3, count);
+}
+
+TEST(RecordingCanvas, saveLayer_viewportCrop) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ // shouldn't matter, since saveLayer will clip to its bounds
+ canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op);
+
+ canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count++ == 1) {
+ Matrix4 expectedMatrix;
+ EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+ EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be
+ // intersection of viewport and saveLayer bounds, in layer space;
+ EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
+ expectedMatrix.loadTranslate(-100, -100, 0);
+ EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+ }
+ });
+ EXPECT_EQ(3, count);
+}
+
+TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.translate(100, 100);
+ canvas.rotate(45);
+ canvas.translate(-50, -50);
+
+ canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer);
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ canvas.restore();
+
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count++ == 1) {
+ EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+ EXPECT_CLIP_RECT(Rect(100, 100), op.localClip);
+ EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
+ EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix)
+ << "Recorded op shouldn't see any canvas transform before the saveLayer";
+ }
+ });
+ EXPECT_EQ(3, count);
+}
+
+TEST(RecordingCanvas, saveLayer_rotateClipped) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.translate(100, 100);
+ canvas.rotate(45);
+ canvas.translate(-200, -200);
+
+ // area of saveLayer will be clipped to parent viewport, so we ask for 400x400...
+ canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ canvas.restore();
+
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count++ == 1) {
+ Matrix4 expectedMatrix;
+ EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+
+ // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by
+ // the parent 200x200 viewport, but prior to rotation
+ ASSERT_NE(nullptr, op.localClip);
+ ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode);
+ // NOTE: this check relies on saveLayer altering the clip post-viewport init. This
+ // causes the clip to be recorded by contained draw commands, though it's not necessary
+ // since the same clip will be computed at draw time. If such a change is made, this
+ // check could be done at record time by querying the clip, or the clip could be altered
+ // slightly so that it is serialized.
+ EXPECT_EQ(Rect(59, 59, 341, 341), op.localClip->rect);
+ EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
+ expectedMatrix.loadIdentity();
+ EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+ }
+ });
+ EXPECT_EQ(3, count);
+}
+
+TEST(RecordingCanvas, drawRenderNode_rejection) {
+ auto child = TestUtils::createNode(50, 50, 150, 150,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&child](RecordingCanvas& canvas) {
+ canvas.clipRect(0, 0, 0, 0, SkRegion::kIntersect_Op); // empty clip, reject node
+ canvas.drawRenderNode(child.get()); // shouldn't crash when rejecting node...
+ });
+ ASSERT_TRUE(dl->isEmpty());
+}
+
+TEST(RecordingCanvas, drawRenderNode_projection) {
+ sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+ {
+ background->mutateStagingProperties().setProjectionReceiver(false);
+
+ // NO RECEIVER PRESENT
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
+ [&background](RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ canvas.drawRenderNode(background.get());
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ });
+ EXPECT_EQ(-1, dl->projectionReceiveIndex)
+ << "no projection receiver should have been observed";
+ }
+ {
+ background->mutateStagingProperties().setProjectionReceiver(true);
+
+ // RECEIVER PRESENT
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
+ [&background](RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ canvas.drawRenderNode(background.get());
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ });
+
+ ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops";
+ auto op = dl->getOps()[1];
+ EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId);
+ EXPECT_EQ(1, dl->projectionReceiveIndex)
+ << "correct projection receiver not identified";
+
+ // verify the behavior works even though projection receiver hasn't been sync'd yet
+ EXPECT_TRUE(background->stagingProperties().isProjectionReceiver());
+ EXPECT_FALSE(background->properties().isProjectionReceiver());
+ }
+}
+
+TEST(RecordingCanvas, firstClipWillReplace) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ // since no explicit clip set on canvas, this should be the one observed on op:
+ canvas.clipRect(-100, -100, 300, 300, SkRegion::kIntersect_Op);
+
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+
+ canvas.restore();
+ });
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
+ // first clip must be preserved, even if it extends beyond canvas bounds
+ EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip);
+}
+
+TEST(RecordingCanvas, replaceClipIntersectWithRoot) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
+ canvas.save(SaveFlags::MatrixClip);
+ canvas.clipRect(-10, -10, 110, 110, SkRegion::kReplace_Op);
+ canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode);
+ canvas.restore();
+ });
+ ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
+ // first clip must be preserved, even if it extends beyond canvas bounds
+ EXPECT_CLIP_RECT(Rect(-10, -10, 110, 110), dl->getOps()[0]->localClip);
+ EXPECT_TRUE(dl->getOps()[0]->localClip->intersectWithRoot);
+}
+
+TEST(RecordingCanvas, insertReorderBarrier) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ canvas.insertReorderBarrier(true);
+ canvas.insertReorderBarrier(false);
+ canvas.insertReorderBarrier(false);
+ canvas.insertReorderBarrier(true);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ canvas.insertReorderBarrier(false);
+ });
+
+ auto chunks = dl->getChunks();
+ EXPECT_EQ(0u, chunks[0].beginOpIndex);
+ EXPECT_EQ(1u, chunks[0].endOpIndex);
+ EXPECT_FALSE(chunks[0].reorderChildren);
+
+ EXPECT_EQ(1u, chunks[1].beginOpIndex);
+ EXPECT_EQ(2u, chunks[1].endOpIndex);
+ EXPECT_TRUE(chunks[1].reorderChildren);
+}
+
+TEST(RecordingCanvas, insertReorderBarrier_clip) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ // first chunk: no recorded clip
+ canvas.insertReorderBarrier(true);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+
+ // second chunk: no recorded clip, since inorder region
+ canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
+ canvas.insertReorderBarrier(false);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+
+ // third chunk: recorded clip
+ canvas.insertReorderBarrier(true);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ });
+
+ auto chunks = dl->getChunks();
+ ASSERT_EQ(3u, chunks.size());
+
+ EXPECT_TRUE(chunks[0].reorderChildren);
+ EXPECT_EQ(nullptr, chunks[0].reorderClip);
+
+ EXPECT_FALSE(chunks[1].reorderChildren);
+ EXPECT_EQ(nullptr, chunks[1].reorderClip);
+
+ EXPECT_TRUE(chunks[2].reorderChildren);
+ ASSERT_NE(nullptr, chunks[2].reorderClip);
+ EXPECT_EQ(Rect(200, 200), chunks[2].reorderClip->rect);
+}
+
+TEST(RecordingCanvas, refPaint) {
+ SkPaint paint;
+
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) {
+ paint.setColor(SK_ColorBLUE);
+ // first two should use same paint
+ canvas.drawRect(0, 0, 200, 10, paint);
+ SkPaint paintCopy(paint);
+ canvas.drawRect(0, 10, 200, 20, paintCopy);
+
+ // only here do we use different paint ptr
+ paint.setColor(SK_ColorRED);
+ canvas.drawRect(0, 20, 200, 30, paint);
+ });
+ auto ops = dl->getOps();
+ ASSERT_EQ(3u, ops.size());
+
+ // first two are the same
+ EXPECT_NE(nullptr, ops[0]->paint);
+ EXPECT_NE(&paint, ops[0]->paint);
+ EXPECT_EQ(ops[0]->paint, ops[1]->paint);
+
+ // last is different, but still copied / non-null
+ EXPECT_NE(nullptr, ops[2]->paint);
+ EXPECT_NE(ops[0]->paint, ops[2]->paint);
+ EXPECT_NE(&paint, ops[2]->paint);
+}
+
+TEST(RecordingCanvas, refBitmap) {
+ SkBitmap bitmap = TestUtils::createSkBitmap(100, 100);
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
+ canvas.drawBitmap(bitmap, 0, 0, nullptr);
+ });
+ auto& bitmaps = dl->getBitmapResources();
+ EXPECT_EQ(1u, bitmaps.size());
+}
+
+TEST(RecordingCanvas, refBitmapInShader_bitmapShader) {
+ SkBitmap bitmap = TestUtils::createSkBitmap(100, 100);
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
+ SkPaint paint;
+ SkAutoTUnref<SkShader> shader(SkShader::CreateBitmapShader(bitmap,
+ SkShader::TileMode::kClamp_TileMode,
+ SkShader::TileMode::kClamp_TileMode));
+ paint.setShader(shader);
+ canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
+ });
+ auto& bitmaps = dl->getBitmapResources();
+ EXPECT_EQ(1u, bitmaps.size());
+}
+
+TEST(RecordingCanvas, refBitmapInShader_composeShader) {
+ SkBitmap bitmap = TestUtils::createSkBitmap(100, 100);
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) {
+ SkPaint paint;
+ SkAutoTUnref<SkShader> shader1(SkShader::CreateBitmapShader(bitmap,
+ SkShader::TileMode::kClamp_TileMode,
+ SkShader::TileMode::kClamp_TileMode));
+
+ SkPoint center;
+ center.set(50, 50);
+ SkColor colors[2];
+ colors[0] = Color::Black;
+ colors[1] = Color::White;
+ SkAutoTUnref<SkShader> shader2(SkGradientShader::CreateRadial(center, 50, colors, nullptr, 2,
+ SkShader::TileMode::kRepeat_TileMode));
+
+ SkAutoTUnref<SkShader> composeShader(SkShader::CreateComposeShader(shader1, shader2,
+ SkXfermode::Mode::kMultiply_Mode));
+ paint.setShader(composeShader);
+ canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
+ });
+ auto& bitmaps = dl->getBitmapResources();
+ EXPECT_EQ(1u, bitmaps.size());
+}
+
+TEST(RecordingCanvas, drawText) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ Paint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
+ canvas.drawText(dst.get(), 0, 5, 5, 25, 25, kBidi_Force_LTR, paint, NULL);
+ });
+
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ count++;
+ ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+ EXPECT_EQ(nullptr, op.localClip);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
+ EXPECT_TRUE(op.unmappedBounds.getHeight() >= 10);
+ EXPECT_TRUE(op.unmappedBounds.getWidth() >= 25);
+ });
+ ASSERT_EQ(1, count);
+}
+
+TEST(RecordingCanvas, drawTextInHighContrast) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.setHighContrastText(true);
+ Paint paint;
+ paint.setColor(SK_ColorWHITE);
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
+ canvas.drawText(dst.get(), 0, 5, 5, 25, 25, kBidi_Force_LTR, paint, NULL);
+ });
+
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+ if (count++ == 0) {
+ EXPECT_EQ(SK_ColorBLACK, op.paint->getColor());
+ EXPECT_EQ(SkPaint::kStrokeAndFill_Style, op.paint->getStyle());
+ } else {
+ EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+ EXPECT_EQ(SkPaint::kFill_Style, op.paint->getStyle());
+ }
+
+ });
+ ASSERT_EQ(2, count);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
new file mode 100644
index 000000000000..b2997dfb357f
--- /dev/null
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+
+#include "RenderNode.h"
+#include "TreeInfo.h"
+#include "tests/common/TestUtils.h"
+#include "utils/Color.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+TEST(RenderNode, hasParents) {
+ auto child = TestUtils::createNode(0, 0, 200, 400,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode);
+ });
+ auto parent = TestUtils::createNode(0, 0, 200, 400,
+ [&child](RenderProperties& props, TestCanvas& canvas) {
+ canvas.drawRenderNode(child.get());
+ });
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+ EXPECT_TRUE(child->hasParents()) << "Child node has no parent";
+ EXPECT_FALSE(parent->hasParents()) << "Root node shouldn't have any parents";
+
+ TestUtils::recordNode(*parent, [](TestCanvas& canvas) {
+ canvas.drawColor(Color::Amber_500, SkXfermode::kSrcOver_Mode);
+ });
+
+ EXPECT_TRUE(child->hasParents()) << "Child should still have a parent";
+ EXPECT_FALSE(parent->hasParents()) << "Root node shouldn't have any parents";
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+ EXPECT_FALSE(child->hasParents()) << "Child should be removed";
+ EXPECT_FALSE(parent->hasParents()) << "Root node shouldn't have any parents";
+}
+
+TEST(RenderNode, releasedCallback) {
+ class DecRefOnReleased : public GlFunctorLifecycleListener {
+ public:
+ DecRefOnReleased(int* refcnt) : mRefCnt(refcnt) {}
+ void onGlFunctorReleased(Functor* functor) override {
+ *mRefCnt -= 1;
+ }
+ private:
+ int* mRefCnt;
+ };
+
+ int refcnt = 0;
+ sp<DecRefOnReleased> listener(new DecRefOnReleased(&refcnt));
+ Functor noopFunctor;
+
+ auto node = TestUtils::createNode(0, 0, 200, 400,
+ [&](RenderProperties& props, TestCanvas& canvas) {
+ refcnt++;
+ canvas.callDrawGLFunction(&noopFunctor, listener.get());
+ });
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+ EXPECT_EQ(1, refcnt);
+
+ TestUtils::recordNode(*node, [&](TestCanvas& canvas) {
+ refcnt++;
+ canvas.callDrawGLFunction(&noopFunctor, listener.get());
+ });
+ EXPECT_EQ(2, refcnt);
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+ EXPECT_EQ(1, refcnt);
+
+ TestUtils::recordNode(*node, [](TestCanvas& canvas) {});
+ EXPECT_EQ(1, refcnt);
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+ EXPECT_EQ(0, refcnt);
+}
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
new file mode 100644
index 000000000000..875e260c84cf
--- /dev/null
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 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 "tests/common/TestUtils.h"
+
+#include <gtest/gtest.h>
+#include <SkShader.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+/**
+ * 1x1 bitmaps must not be optimized into solid color shaders, since HWUI can't
+ * compose/render color shaders
+ */
+TEST(SkiaBehavior, CreateBitmapShader1x1) {
+ SkBitmap origBitmap = TestUtils::createSkBitmap(1, 1);
+ std::unique_ptr<SkShader> s(SkShader::CreateBitmapShader(
+ origBitmap,
+ SkShader::kClamp_TileMode,
+ SkShader::kRepeat_TileMode));
+
+ SkBitmap bitmap;
+ SkShader::TileMode xy[2];
+ ASSERT_TRUE(s->isABitmap(&bitmap, nullptr, xy))
+ << "1x1 bitmap shader must query as bitmap shader";
+ EXPECT_EQ(SkShader::kClamp_TileMode, xy[0]);
+ EXPECT_EQ(SkShader::kRepeat_TileMode, xy[1]);
+ EXPECT_EQ(origBitmap.pixelRef(), bitmap.pixelRef());
+}
+
+TEST(SkiaBehavior, genIds) {
+ SkBitmap bitmap = TestUtils::createSkBitmap(100, 100);
+ uint32_t genId = bitmap.getGenerationID();
+ bitmap.notifyPixelsChanged();
+ EXPECT_NE(genId, bitmap.getGenerationID());
+}
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
new file mode 100644
index 000000000000..5a011938e2bb
--- /dev/null
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 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 "tests/common/TestUtils.h"
+
+#include <gtest/gtest.h>
+#include <RecordingCanvas.h>
+#include <SkPicture.h>
+#include <SkPictureRecorder.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+/**
+ * Verify that we get the same culling bounds for text for (1) drawing glyphs
+ * directly to a Canvas or (2) going through a SkPicture as an intermediate step.
+ */
+TEST(SkiaCanvasProxy, drawGlyphsViaPicture) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ // setup test variables
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ static const char* text = "testing text bounds";
+
+ // draw text directly into Recording canvas
+ TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 25, 25);
+
+ // record the same text draw into a SkPicture and replay it into a Recording canvas
+ SkPictureRecorder recorder;
+ SkCanvas* skCanvas = recorder.beginRecording(200, 200, NULL, 0);
+ std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas));
+ TestUtils::drawUtf8ToCanvas(pictCanvas.get(), text, paint, 25, 25);
+ SkAutoTUnref<const SkPicture> picture(recorder.endRecording());
+
+ canvas.asSkCanvas()->drawPicture(picture);
+ });
+
+ // verify that the text bounds and matrices match
+ ASSERT_EQ(2U, dl->getOps().size());
+ auto directOp = dl->getOps()[0];
+ auto pictureOp = dl->getOps()[1];
+ ASSERT_EQ(RecordedOpId::TextOp, directOp->opId);
+ EXPECT_EQ(directOp->opId, pictureOp->opId);
+ EXPECT_EQ(directOp->unmappedBounds, pictureOp->unmappedBounds);
+ EXPECT_EQ(directOp->localMatrix, pictureOp->localMatrix);
+}
diff --git a/libs/hwui/tests/unit/SnapshotTests.cpp b/libs/hwui/tests/unit/SnapshotTests.cpp
new file mode 100644
index 000000000000..11797a8fc1db
--- /dev/null
+++ b/libs/hwui/tests/unit/SnapshotTests.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+
+#include <Snapshot.h>
+
+#include <tests/common/TestUtils.h>
+
+using namespace android::uirenderer;
+
+TEST(Snapshot, serializeIntersectedClip) {
+ auto actualRoot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 100));
+ auto root = TestUtils::makeSnapshot(Matrix4::identity(), Rect(10, 10, 90, 90));
+ auto child = TestUtils::makeSnapshot(Matrix4::identity(), Rect(50, 50, 90, 90));
+ root->previous = actualRoot.get();
+ child->previous = root.get();
+
+ LinearAllocator allocator;
+ ClipRect rect(Rect(0, 0, 75, 75));
+ {
+ auto intersectWithChild = child->serializeIntersectedClip(allocator,
+ &rect, Matrix4::identity());
+ ASSERT_NE(nullptr, intersectWithChild);
+ EXPECT_EQ(Rect(50, 50, 75, 75), intersectWithChild->rect) << "Expect intersect with child";
+ }
+
+ rect.intersectWithRoot = true;
+ {
+ auto intersectWithRoot = child->serializeIntersectedClip(allocator,
+ &rect, Matrix4::identity());
+ ASSERT_NE(nullptr, intersectWithRoot);
+ EXPECT_EQ(Rect(10, 10, 75, 75), intersectWithRoot->rect) << "Expect intersect with root";
+ }
+}
+
+TEST(Snapshot, applyClip) {
+ auto actualRoot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 100));
+ auto root = TestUtils::makeSnapshot(Matrix4::identity(), Rect(10, 10, 90, 90));
+ root->previous = actualRoot.get();
+
+ ClipRect rect(Rect(0, 0, 75, 75));
+ {
+ auto child = TestUtils::makeSnapshot(Matrix4::identity(), Rect(50, 50, 90, 90));
+ child->previous = root.get();
+ child->applyClip(&rect, Matrix4::identity());
+
+ EXPECT_TRUE(child->getClipArea().isSimple());
+ EXPECT_EQ(Rect(50, 50, 75, 75), child->getRenderTargetClip());
+ }
+
+ {
+ rect.intersectWithRoot = true;
+ auto child = TestUtils::makeSnapshot(Matrix4::identity(), Rect(50, 50, 90, 90));
+ child->previous = root.get();
+ child->applyClip(&rect, Matrix4::identity());
+
+ EXPECT_TRUE(child->getClipArea().isSimple());
+ EXPECT_EQ(Rect(10, 10, 75, 75), child->getRenderTargetClip());
+ }
+}
diff --git a/libs/hwui/tests/unit/StringUtilsTests.cpp b/libs/hwui/tests/unit/StringUtilsTests.cpp
new file mode 100644
index 000000000000..b60e96c756ec
--- /dev/null
+++ b/libs/hwui/tests/unit/StringUtilsTests.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include <utils/StringUtils.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+TEST(StringUtils, simpleBuildSet) {
+ auto collection = StringUtils::split("a b c");
+
+ EXPECT_TRUE(collection.has("a"));
+ EXPECT_TRUE(collection.has("b"));
+ EXPECT_TRUE(collection.has("c"));
+ EXPECT_FALSE(collection.has("d"));
+}
+
+TEST(StringUtils, advancedBuildSet) {
+ auto collection = StringUtils::split("GL_ext1 GL_ext2 GL_ext3");
+
+ EXPECT_TRUE(collection.has("GL_ext1"));
+ EXPECT_FALSE(collection.has("GL_ext")); // string present, but not in list
+}
+
+TEST(StringUtils, sizePrinter) {
+ std::stringstream os;
+ os << SizePrinter{500};
+ EXPECT_EQ("500.00B", os.str());
+ os.str("");
+ os << SizePrinter{46080};
+ EXPECT_EQ("45.00KiB", os.str());
+ os.str("");
+ os << SizePrinter{5 * 1024 * 1024 + 520 * 1024};
+ EXPECT_EQ("5.51MiB", os.str());
+ os.str("");
+ os << SizePrinter{2147483647};
+ EXPECT_EQ("2048.00MiB", os.str());
+}
diff --git a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
new file mode 100644
index 000000000000..0d26df203f02
--- /dev/null
+++ b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+
+#include "GammaFontRenderer.h"
+#include "TextDropShadowCache.h"
+#include "utils/Blur.h"
+#include "tests/common/TestUtils.h"
+
+#include <SkPaint.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+RENDERTHREAD_TEST(TextDropShadowCache, addRemove) {
+ SkPaint paint;
+ paint.setTextSize(20);
+
+ GammaFontRenderer gammaFontRenderer;
+ FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer();
+ fontRenderer.setFont(&paint, SkMatrix::I());
+ TextDropShadowCache cache(MB(5));
+ cache.setFontRenderer(fontRenderer);
+
+ std::vector<glyph_t> glyphs;
+ std::vector<float> positions;
+ float totalAdvance;
+ uirenderer::Rect bounds;
+ TestUtils::layoutTextUnscaled(paint, "This is a test",
+ &glyphs, &positions, &totalAdvance, &bounds);
+ EXPECT_TRUE(bounds.contains(5, -10, 100, 0)) << "Expect input to be nontrivially sized";
+
+ ShadowTexture* texture = cache.get(&paint, glyphs.data(), glyphs.size(), 10, positions.data());
+
+ ASSERT_TRUE(texture);
+ ASSERT_FALSE(texture->cleanup);
+ ASSERT_EQ((uint32_t) texture->objectSize(), cache.getSize());
+ ASSERT_TRUE(cache.getSize());
+ cache.clear();
+ ASSERT_EQ(cache.getSize(), 0u);
+}
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
new file mode 100644
index 000000000000..83b485fa705e
--- /dev/null
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include "PathParser.h"
+#include "VectorDrawable.h"
+#include "utils/MathUtils.h"
+#include "utils/VectorDrawableUtils.h"
+
+#include <functional>
+
+namespace android {
+namespace uirenderer {
+
+struct TestData {
+ const char* pathString;
+ const PathData pathData;
+ const std::function<void(SkPath*)> skPathLamda;
+};
+
+const static TestData sTestDataSet[] = {
+ // TestData with scientific notation -2e3 etc.
+ {
+ // Path
+ "M2.000000,22.000000l20.000000,0.000000 1e0-2e3z",
+ {
+ // Verbs
+ {'M', 'l', 'z'},
+ // Verb sizes
+ {2, 4, 0},
+ // Points
+ {2, 22, 20, 0, 1, -2000},
+ },
+ [](SkPath* outPath) {
+ outPath->moveTo(2, 22);
+ outPath->rLineTo(20, 0);
+ outPath->rLineTo(1, -2000);
+ outPath->close();
+ outPath->moveTo(2, 22);
+ }
+ },
+
+ // Comprehensive data, containing all the verbs possible.
+ {
+ // Path
+ "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10",
+ {
+ // Verbs
+ {'M', 'm', 'l', 'L', 'H', 'h', 'V', 'v', 'Q', 'q', 't', 'T', 'C', 'c', 'S', 's', 'A', 'a'},
+ // VerbSizes
+ {2, 2, 2, 2, 1, 1, 1, 1, 4, 4, 2, 2, 6, 6, 4, 4, 7, 7},
+ // Points
+ {1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 7.0, 7.0, 7.0, 7.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, }
+ },
+ [](SkPath* outPath) {
+ outPath->moveTo(1.0, 1.0);
+ outPath->rMoveTo(2.0, 2.0);
+ outPath->rLineTo(3.0, 3.0);
+ outPath->lineTo(3.0, 3.0);
+ outPath->lineTo(4.0, 3.0);
+ outPath->rLineTo(4.0, 0);
+ outPath->lineTo(8.0, 5.0);
+ outPath->rLineTo(0, 5.0);
+ outPath->quadTo(6.0, 6.0, 6.0, 6.0);
+ outPath->rQuadTo(6.0, 6.0, 6.0, 6.0);
+ outPath->rQuadTo(0.0, 0.0, 7.0, 7.0);
+ outPath->quadTo(26.0, 26.0, 7.0, 7.0);
+ outPath->cubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
+ outPath->rCubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0);
+ outPath->cubicTo(16.0, 16.0, 9.0, 9.0, 9.0, 9.0);
+ outPath->rCubicTo(0.0, 0.0, 9.0, 9.0, 9.0, 9.0);
+ outPath->cubicTo(18.447775037328352, 20.404243860300607, 17.998389141249767, 22.8911717921705, 16.737515350332117, 24.986664170401575);
+ outPath->cubicTo(15.476641559414468, 27.08215654863265, 13.489843598291483, 28.644011882390082, 11.155893964798905, 29.37447073281729);
+ outPath->cubicTo(8.821944331306327, 30.1049295832445, 6.299226382436471, 29.954422532383525, 4.0686829203897235, 28.951642951534332);
+ outPath->cubicTo(1.838139458342976, 27.94886337068514, 0.05113662931485696, 26.161860541657013, -0.9516429515343354, 23.931317079610267);
+ outPath->cubicTo(-1.9544225323835278, 21.70077361756352, -2.1049295832444987, 19.178055668693663, -1.37447073281729, 16.844106035201087);
+ outPath->cubicTo(-0.6440118823900814, 14.51015640170851, 0.9178434513673546, 12.523358440585524, 3.0133358295984305, 11.262484649667876);
+ outPath->cubicTo(5.108828207829506, 10.001610858750228, 7.5957561396993984, 9.552224962671648, 10.000000000000005, 10.0);
+ outPath->cubicTo(10.0, 7.348852265086975, 11.054287646850167, 4.803576729418881, 12.928932188134523, 2.9289321881345254);
+ outPath->cubicTo(14.803576729418879, 1.0542876468501696, 17.348852265086972, 4.870079381441987E-16, 19.999999999999996, 0.0);
+ outPath->cubicTo(22.65114773491302, -4.870079381441987E-16, 25.19642327058112, 1.0542876468501678, 27.071067811865476, 2.9289321881345227);
+ outPath->cubicTo(28.94571235314983, 4.803576729418878, 30.0, 7.348852265086974, 30.0, 9.999999999999998);
+ outPath->cubicTo(30.0, 12.651147734913023, 28.94571235314983, 15.19642327058112, 27.071067811865476, 17.071067811865476);
+ outPath->cubicTo(25.19642327058112, 18.94571235314983, 22.651147734913028, 20.0, 20.000000000000004, 20.0);
+ }
+ },
+
+ // Check box VectorDrawable path data
+ {
+ // Path
+ "M 0.0,-1.0 l 0.0,0.0 c 0.5522847498,0.0 1.0,0.4477152502 1.0,1.0 l 0.0,0.0 c 0.0,0.5522847498 -0.4477152502,1.0 -1.0,1.0 l 0.0,0.0 c -0.5522847498,0.0 -1.0,-0.4477152502 -1.0,-1.0 l 0.0,0.0 c 0.0,-0.5522847498 0.4477152502,-1.0 1.0,-1.0 Z M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z",
+ {
+ {'M', 'l', 'c', 'l', 'c', 'l', 'c', 'l', 'c', 'Z', 'M', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'Z'},
+ {2, 2, 6, 2, 6, 2, 6, 2, 6, 0, 2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0},
+ {0.0, -1.0, 0.0, 0.0, 0.5522848, 0.0, 1.0, 0.44771525, 1.0, 1.0, 0.0, 0.0, 0.0, 0.5522848, -0.44771525, 1.0, -1.0, 1.0, 0.0, 0.0, -0.5522848, 0.0, -1.0, -0.44771525, -1.0, -1.0, 0.0, 0.0, 0.0, -0.5522848, 0.44771525, -1.0, 1.0, -1.0, 7.0, -9.0, 0.0, 0.0, -14.0, 0.0, -14.0, 0.0, -1.1044922, 0.0, -2.0, 0.8955078, -2.0, 2.0, 0.0, 0.0, 0.0, 14.0, 0.0, 14.0, 0.0, 1.1044922, 0.8955078, 2.0, 2.0, 2.0, 0.0, 0.0, 14.0, 0.0, 14.0, 0.0, 1.1044922, 0.0, 2.0, -0.8955078, 2.0, -2.0, 0.0, 0.0, 0.0, -14.0, 0.0, -14.0, 0.0, -1.1044922, -0.8955078, -2.0, -2.0, -2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
+ },
+ [](SkPath* outPath) {
+ outPath->moveTo(0.0, -1.0);
+ outPath->rLineTo(0.0, 0.0);
+ outPath->rCubicTo(0.5522848, 0.0, 1.0, 0.44771525, 1.0, 1.0);
+ outPath->rLineTo(0.0, 0.0);
+ outPath->rCubicTo(0.0, 0.5522848, -0.44771525, 1.0, -1.0, 1.0);
+ outPath->rLineTo(0.0, 0.0);
+ outPath->rCubicTo(-0.5522848, 0.0, -1.0, -0.44771525, -1.0, -1.0);
+ outPath->rLineTo(0.0, 0.0);
+ outPath->rCubicTo(0.0, -0.5522848, 0.44771525, -1.0, 1.0, -1.0);
+ outPath->close();
+ outPath->moveTo(0.0, -1.0);
+ outPath->moveTo(7.0, -9.0);
+ outPath->rCubicTo(0.0, 0.0, -14.0, 0.0, -14.0, 0.0);
+ outPath->rCubicTo(-1.1044922, 0.0, -2.0, 0.8955078, -2.0, 2.0);
+ outPath->rCubicTo(0.0, 0.0, 0.0, 14.0, 0.0, 14.0);
+ outPath->rCubicTo(0.0, 1.1044922, 0.8955078, 2.0, 2.0, 2.0);
+ outPath->rCubicTo(0.0, 0.0, 14.0, 0.0, 14.0, 0.0);
+ outPath->rCubicTo(1.1044922, 0.0, 2.0, -0.8955078, 2.0, -2.0);
+ outPath->rCubicTo(0.0, 0.0, 0.0, -14.0, 0.0, -14.0);
+ outPath->rCubicTo(0.0, -1.1044922, -0.8955078, -2.0, -2.0, -2.0);
+ outPath->rCubicTo(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ outPath->close();
+ outPath->moveTo(7.0, -9.0);
+ }
+ },
+
+ // pie1 in progress bar
+ {
+ "M300,70 a230,230 0 1,0 1,0 z",
+ {
+ {'M', 'a', 'z', },
+ {2, 7, 0, },
+ {300.0, 70.0, 230.0, 230.0, 0.0, 1.0, 0.0, 1.0, 0.0, },
+ },
+ [](SkPath* outPath) {
+ outPath->moveTo(300.0, 70.0);
+ outPath->cubicTo(239.06697794203706, 70.13246340443499, 180.6164396449267, 94.47383115953485, 137.6004913602211, 137.6302781499585);
+ outPath->cubicTo(94.58454307551551, 180.78672514038215, 70.43390412842275, 239.3163266242308, 70.50013586976587, 300.2494566687817);
+ outPath->cubicTo(70.56636761110899, 361.1825867133326, 94.84418775550249, 419.65954850554147, 137.9538527586204, 462.72238058830936);
+ outPath->cubicTo(181.06351776173827, 505.7852126710772, 239.5668339599056, 529.999456521097, 300.49999999999994, 529.999456521097);
+ outPath->cubicTo(361.43316604009436, 529.999456521097, 419.93648223826176, 505.78521267107726, 463.0461472413797, 462.7223805883093);
+ outPath->cubicTo(506.1558122444976, 419.65954850554135, 530.433632388891, 361.1825867133324, 530.4998641302341, 300.2494566687815);
+ outPath->cubicTo(530.5660958715771, 239.31632662423056, 506.4154569244844, 180.7867251403819, 463.3995086397787, 137.6302781499583);
+ outPath->cubicTo(420.383560355073, 94.47383115953468, 361.93302205796255, 70.13246340443492, 300.9999999999996, 70.00000000000003);
+ outPath->close();
+ outPath->moveTo(300.0, 70.0);
+ }
+ },
+
+ // Random long data
+ {
+ // Path
+ "M5.3,13.2c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,-0.2 -0.4,-0.7 -0.2,-1.0c1.3,-1.9 2.9,-3.4 4.9,-4.5c4.1,-2.2 9.3,-2.2 13.4,0.0c1.9,1.1 3.6,2.5 4.9,4.4c0.2,0.3 0.1,0.8 -0.2,1.0c-0.3,0.2 -0.8,0.1 -1.0,-0.2c-1.2,-1.7 -2.6,-3.0 -4.3,-4.0c-3.7,-2.0 -8.3,-2.0 -12.0,0.0c-1.7,0.9 -3.2,2.3 -4.3,4.0C5.7,13.1 5.5,13.2 5.3,13.2z",
+ {
+ // Verbs
+ {'M', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'C', 'z'},
+ // Verb sizes
+ {2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0},
+ // Points
+ {5.3, 13.2, -0.1, 0, -0.3, 0, -0.4, -0.1, -0.3, -0.2, -0.4, -0.7, -0.2, -1, 1.3, -1.9, 2.9, -3.4, 4.9, -4.5, 4.1, -2.2, 9.3, -2.2, 13.4, 0, 1.9, 1.1, 3.6, 2.5, 4.9, 4.4, 0.2, 0.3, 0.1, 0.8, -0.2, 1, -0.3, 0.2, -0.8, 0.1, -1, -0.2, -1.2, -1.7, -2.6, -3, -4.3, -4, -3.7, -2, -8.3, -2, -12, 0, -1.7, 0.9, -3.2, 2.3, -4.3, 4, 5.7, 13.1, 5.5, 13.2, 5.3, 13.2},
+ },
+ [](SkPath* outPath) {
+ outPath->moveTo(5.3, 13.2);
+ outPath->rCubicTo(-0.1, 0.0, -0.3, 0.0, -0.4, -0.1);
+ outPath->rCubicTo(-0.3, -0.2, -0.4, -0.7, -0.2, -1.0);
+ outPath->rCubicTo(1.3, -1.9, 2.9, -3.4, 4.9, -4.5);
+ outPath->rCubicTo(4.1, -2.2, 9.3, -2.2, 13.4, 0.0);
+ outPath->rCubicTo(1.9, 1.1, 3.6, 2.5, 4.9, 4.4);
+ outPath->rCubicTo(0.2, 0.3, 0.1, 0.8, -0.2, 1.0);
+ outPath->rCubicTo(-0.3, 0.2, -0.8, 0.1, -1.0, -0.2);
+ outPath->rCubicTo(-1.2, -1.7, -2.6, -3.0, -4.3, -4.0);
+ outPath->rCubicTo(-3.7, -2.0, -8.3, -2.0, -12.0, 0.0);
+ outPath->rCubicTo(-1.7, 0.9, -3.2, 2.3, -4.3, 4.0);
+ outPath->cubicTo(5.7, 13.1, 5.5, 13.2, 5.3, 13.2);
+ outPath->close();
+ outPath->moveTo(5.3, 13.2);
+ }
+ },
+
+ // Extreme case with numbers and decimal points crunched together
+ {
+ // Path
+ "l0.0.0.5.0.0.5-0.5.0.0-.5z",
+ {
+ // Verbs
+ {'l', 'z'},
+ // Verb sizes
+ {10, 0},
+ // Points
+ {0, 0, 0.5, 0, 0, 0.5, -0.5, 0, 0, -0.5},
+ },
+ [](SkPath* outPath) {
+ outPath->rLineTo(0.0, 0.0);
+ outPath->rLineTo(0.5, 0.0);
+ outPath->rLineTo(0.0, 0.5);
+ outPath->rLineTo(-0.5, 0.0);
+ outPath->rLineTo(0.0, -0.5);
+ outPath->close();
+ outPath->moveTo(0.0, 0.0);
+ }
+ },
+
+ // Empty test data
+ {
+ "",
+ {
+ // Verbs
+ {},
+ {},
+ {},
+ },
+ [](SkPath* outPath) {}
+ }
+
+};
+
+struct StringPath {
+ const char* stringPath;
+ bool isValid;
+};
+
+const StringPath sStringPaths[] = {
+ {"3e...3", false}, // Not starting with a verb and ill-formatted float
+ {"L.M.F.A.O", false}, // No floats following verbs
+ {"m 1 1", true}, // Valid path data
+ {"\n \t z", true}, // Valid path data with leading spaces
+ {"1-2e34567", false}, // Not starting with a verb and ill-formatted float
+ {"f 4 5", false}, // Invalid verb
+ {"\r ", false} // Empty string
+};
+
+
+static bool hasSameVerbs(const PathData& from, const PathData& to) {
+ return from.verbs == to.verbs && from.verbSizes == to.verbSizes;
+}
+
+TEST(PathParser, parseStringForData) {
+ for (TestData testData: sTestDataSet) {
+ PathParser::ParseResult result;
+ // Test generated path data against the given data.
+ PathData pathData;
+ size_t length = strlen(testData.pathString);
+ PathParser::getPathDataFromAsciiString(&pathData, &result, testData.pathString, length);
+ EXPECT_EQ(testData.pathData, pathData);
+ }
+
+ for (StringPath stringPath : sStringPaths) {
+ PathParser::ParseResult result;
+ PathData pathData;
+ SkPath skPath;
+ PathParser::getPathDataFromAsciiString(&pathData, &result,
+ stringPath.stringPath, strlen(stringPath.stringPath));
+ EXPECT_EQ(stringPath.isValid, !result.failureOccurred);
+ }
+}
+
+TEST(VectorDrawableUtils, createSkPathFromPathData) {
+ for (TestData testData: sTestDataSet) {
+ SkPath expectedPath;
+ testData.skPathLamda(&expectedPath);
+ SkPath actualPath;
+ VectorDrawableUtils::verbsToPath(&actualPath, testData.pathData);
+ EXPECT_EQ(expectedPath, actualPath);
+ }
+}
+
+TEST(PathParser, parseAsciiStringForSkPath) {
+ for (TestData testData: sTestDataSet) {
+ PathParser::ParseResult result;
+ size_t length = strlen(testData.pathString);
+ // Check the return value as well as the SkPath generated.
+ SkPath actualPath;
+ PathParser::parseAsciiStringForSkPath(&actualPath, &result, testData.pathString, length);
+ bool hasValidData = !result.failureOccurred;
+ EXPECT_EQ(hasValidData, testData.pathData.verbs.size() > 0);
+ SkPath expectedPath;
+ testData.skPathLamda(&expectedPath);
+ EXPECT_EQ(expectedPath, actualPath);
+ }
+
+ for (StringPath stringPath : sStringPaths) {
+ PathParser::ParseResult result;
+ SkPath skPath;
+ PathParser::parseAsciiStringForSkPath(&skPath, &result, stringPath.stringPath,
+ strlen(stringPath.stringPath));
+ EXPECT_EQ(stringPath.isValid, !result.failureOccurred);
+ }
+}
+
+TEST(VectorDrawableUtils, morphPathData) {
+ for (TestData fromData: sTestDataSet) {
+ for (TestData toData: sTestDataSet) {
+ bool canMorph = VectorDrawableUtils::canMorph(fromData.pathData, toData.pathData);
+ if (fromData.pathData == toData.pathData) {
+ EXPECT_TRUE(canMorph);
+ } else {
+ bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData);
+ EXPECT_EQ(expectedToMorph, canMorph);
+ }
+ }
+ }
+}
+
+TEST(VectorDrawableUtils, interpolatePathData) {
+ // Interpolate path data with itself and every other path data
+ for (TestData fromData: sTestDataSet) {
+ for (TestData toData: sTestDataSet) {
+ PathData outData;
+ bool success = VectorDrawableUtils::interpolatePathData(&outData, fromData.pathData,
+ toData.pathData, 0.5);
+ bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData);
+ EXPECT_EQ(expectedToMorph, success);
+ }
+ }
+
+ float fractions[] = {0, 0.00001, 0.28, 0.5, 0.7777, 0.9999999, 1};
+ // Now try to interpolate with a slightly modified version of self and expect success
+ for (TestData fromData : sTestDataSet) {
+ PathData toPathData = fromData.pathData;
+ for (size_t i = 0; i < toPathData.points.size(); i++) {
+ toPathData.points[i]++;
+ }
+ const PathData& fromPathData = fromData.pathData;
+ PathData outData;
+ // Interpolate the two path data with different fractions
+ for (float fraction : fractions) {
+ bool success = VectorDrawableUtils::interpolatePathData(
+ &outData, fromPathData, toPathData, fraction);
+ EXPECT_TRUE(success);
+ for (size_t i = 0; i < outData.points.size(); i++) {
+ float expectedResult = fromPathData.points[i] * (1.0 - fraction) +
+ toPathData.points[i] * fraction;
+ EXPECT_TRUE(MathUtils::areEqual(expectedResult, outData.points[i]));
+ }
+ }
+ }
+}
+
+TEST(VectorDrawable, matrixScale) {
+ struct MatrixAndScale {
+ float buffer[9];
+ float matrixScale;
+ };
+
+ const MatrixAndScale sMatrixAndScales[] {
+ {
+ {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f},
+ 1.0
+ },
+ {
+ {1.0f, 0.0f, 240.0f, 0.0f, 1.0f, 240.0f, 0.0f, 0.0f, 1.0f},
+ 1.0f,
+ },
+ {
+ {1.5f, 0.0f, 24.0f, 0.0f, 1.5f, 24.0f, 0.0f, 0.0f, 1.0f},
+ 1.5f,
+ },
+ {
+ {0.99999994f, 0.0f, 300.0f, 0.0f, 0.99999994f, 158.57864f, 0.0f, 0.0f, 1.0f},
+ 0.99999994f,
+ },
+ {
+ {0.7071067f, 0.7071067f, 402.5305f, -0.7071067f, 0.7071067f, 169.18524f, 0.0f, 0.0f, 1.0f},
+ 0.99999994f,
+ },
+ {
+ {0.0f, 0.9999999f, 482.5305f, -0.9999999f, 0.0f, 104.18525f, 0.0f, 0.0f, 1.0f},
+ 0.9999999f,
+ },
+ {
+ {-0.35810637f, -0.93368083f, 76.55821f, 0.93368083f, -0.35810637f, 89.538506f, 0.0f, 0.0f, 1.0f},
+ 1.0000001f,
+ },
+ };
+
+ for (MatrixAndScale matrixAndScale : sMatrixAndScales) {
+ SkMatrix matrix;
+ matrix.set9(matrixAndScale.buffer);
+ float actualMatrixScale = VectorDrawable::Path::getMatrixScale(matrix);
+ EXPECT_EQ(matrixAndScale.matrixScale, actualMatrixScale);
+ }
+}
+
+TEST(VectorDrawable, groupProperties) {
+ //TODO: Also need to test property sync and dirty flag when properties change.
+ VectorDrawable::Group group;
+ VectorDrawable::Group::GroupProperties* properties = group.mutateProperties();
+ // Test default values, change values through setters and verify the change through getters.
+ EXPECT_EQ(0.0f, properties->getTranslateX());
+ properties->setTranslateX(1.0f);
+ EXPECT_EQ(1.0f, properties->getTranslateX());
+
+ EXPECT_EQ(0.0f, properties->getTranslateY());
+ properties->setTranslateY(1.0f);
+ EXPECT_EQ(1.0f, properties->getTranslateY());
+
+ EXPECT_EQ(0.0f, properties->getRotation());
+ properties->setRotation(1.0f);
+ EXPECT_EQ(1.0f, properties->getRotation());
+
+ EXPECT_EQ(1.0f, properties->getScaleX());
+ properties->setScaleX(0.0f);
+ EXPECT_EQ(0.0f, properties->getScaleX());
+
+ EXPECT_EQ(1.0f, properties->getScaleY());
+ properties->setScaleY(0.0f);
+ EXPECT_EQ(0.0f, properties->getScaleY());
+
+ EXPECT_EQ(0.0f, properties->getPivotX());
+ properties->setPivotX(1.0f);
+ EXPECT_EQ(1.0f, properties->getPivotX());
+
+ EXPECT_EQ(0.0f, properties->getPivotY());
+ properties->setPivotY(1.0f);
+ EXPECT_EQ(1.0f, properties->getPivotY());
+
+}
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/unit_tests/how_to_run.txt b/libs/hwui/tests/unit/how_to_run.txt
index a2d6a34726df..c11d6eb33358 100755
--- a/libs/hwui/unit_tests/how_to_run.txt
+++ b/libs/hwui/tests/unit/how_to_run.txt
@@ -1,4 +1,4 @@
-mmm -j8 $ANDROID_BUILD_TOP/frameworks/base/libs/hwui/unit_tests &&
+mmm -j8 frameworks/base/libs/hwui &&
adb push $ANDROID_PRODUCT_OUT/data/nativetest/hwui_unit_tests/hwui_unit_tests \
/data/nativetest/hwui_unit_tests/hwui_unit_tests &&
adb shell /data/nativetest/hwui_unit_tests/hwui_unit_tests
diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp
new file mode 100644
index 000000000000..409a12d37693
--- /dev/null
+++ b/libs/hwui/tests/unit/main.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 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 "gtest/gtest.h"
+
+#include "Caches.h"
+#include "thread/TaskManager.h"
+#include "tests/common/TestUtils.h"
+
+#include <memunreachable/memunreachable.h>
+
+#include <cstdio>
+#include <iostream>
+#include <map>
+#include <unordered_set>
+#include <signal.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace android;
+using namespace android::uirenderer;
+
+static auto CRASH_SIGNALS = {
+ SIGABRT,
+ SIGSEGV,
+ SIGBUS,
+};
+
+static map<int, struct sigaction> gSigChain;
+
+static void gtestSigHandler(int sig, siginfo_t* siginfo, void* context) {
+ auto testinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+ printf("[ FAILED ] %s.%s\n", testinfo->test_case_name(),
+ testinfo->name());
+ printf("[ FATAL! ] Process crashed, aborting tests!\n");
+ fflush(stdout);
+
+ // restore the default sighandler and re-raise
+ struct sigaction sa = gSigChain[sig];
+ sigaction(sig, &sa, nullptr);
+ raise(sig);
+}
+
+static void logUnreachable(initializer_list<UnreachableMemoryInfo> infolist) {
+ // merge them all
+ UnreachableMemoryInfo merged;
+ unordered_set<uintptr_t> addrs;
+ merged.allocation_bytes = 0;
+ merged.leak_bytes = 0;
+ merged.num_allocations = 0;
+ merged.num_leaks = 0;
+ for (auto& info : infolist) {
+ // We'll be a little hazzy about these ones and just hope the biggest
+ // is the most accurate
+ merged.allocation_bytes = max(merged.allocation_bytes, info.allocation_bytes);
+ merged.num_allocations = max(merged.num_allocations, info.num_allocations);
+ for (auto& leak : info.leaks) {
+ if (addrs.find(leak.begin) == addrs.end()) {
+ merged.leaks.push_back(leak);
+ merged.num_leaks++;
+ merged.leak_bytes += leak.size;
+ addrs.insert(leak.begin);
+ }
+ }
+ }
+
+ // Now log the result
+ if (merged.num_leaks) {
+ cout << endl << "Leaked memory!" << endl;
+ if (!merged.leaks[0].backtrace.num_frames) {
+ cout << "Re-run with 'setprop libc.debug.malloc.program hwui_unit_test'"
+ << endl << "and 'setprop libc.debug.malloc.options backtrace=8'"
+ << " to get backtraces" << endl;
+ }
+ cout << merged.ToString(false);
+ }
+}
+
+static void checkForLeaks() {
+ // TODO: Until we can shutdown the RT thread we need to do this in
+ // two passes as GetUnreachableMemory has limited insight into
+ // thread-local caches so some leaks will not be properly tagged as leaks
+ nsecs_t before = systemTime();
+ UnreachableMemoryInfo rtMemInfo;
+ TestUtils::runOnRenderThread([&rtMemInfo](renderthread::RenderThread& thread) {
+ if (Caches::hasInstance()) {
+ Caches::getInstance().tasks.stop();
+ }
+ // Check for leaks
+ if (!GetUnreachableMemory(rtMemInfo)) {
+ cerr << "Failed to get unreachable memory!" << endl;
+ return;
+ }
+ });
+ UnreachableMemoryInfo uiMemInfo;
+ if (!GetUnreachableMemory(uiMemInfo)) {
+ cerr << "Failed to get unreachable memory!" << endl;
+ return;
+ }
+ logUnreachable({rtMemInfo, uiMemInfo});
+ nsecs_t after = systemTime();
+ cout << "Leak check took " << ns2ms(after - before) << "ms" << endl;
+}
+
+int main(int argc, char* argv[]) {
+ // Register a crash handler
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_sigaction = &gtestSigHandler;
+ sa.sa_flags = SA_SIGINFO;
+ for (auto sig : CRASH_SIGNALS) {
+ struct sigaction old_sa;
+ sigaction(sig, &sa, &old_sa);
+ gSigChain.insert(pair<int, struct sigaction>(sig, old_sa));
+ }
+
+ // Run the tests
+ testing::InitGoogleTest(&argc, argv);
+ int ret = RUN_ALL_TESTS();
+ checkForLeaks();
+ return ret;
+}
+
diff --git a/libs/hwui/thread/Barrier.h b/libs/hwui/thread/Barrier.h
index 6cb23e54943b..0a7acb0fbbfd 100644
--- a/libs/hwui/thread/Barrier.h
+++ b/libs/hwui/thread/Barrier.h
@@ -33,11 +33,6 @@ public:
mCondition.signal(mType);
}
- void close() {
- Mutex::Autolock l(mLock);
- mOpened = false;
- }
-
void wait() const {
Mutex::Autolock l(mLock);
while (!mOpened) {
diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp
index e9dde294b2aa..d346b859526e 100644
--- a/libs/hwui/thread/TaskManager.cpp
+++ b/libs/hwui/thread/TaskManager.cpp
@@ -39,7 +39,7 @@ TaskManager::TaskManager() {
for (int i = 0; i < workerCount; i++) {
String8 name;
name.appendFormat("hwuiTask%d", i + 1);
- mThreads.add(new WorkerThread(name));
+ mThreads.push_back(new WorkerThread(name));
}
}
@@ -89,36 +89,34 @@ status_t TaskManager::WorkerThread::readyToRun() {
bool TaskManager::WorkerThread::threadLoop() {
mSignal.wait();
- Vector<TaskWrapper> tasks;
+ std::vector<TaskWrapper> tasks;
{
Mutex::Autolock l(mLock);
- tasks = mTasks;
- mTasks.clear();
+ tasks.swap(mTasks);
}
for (size_t i = 0; i < tasks.size(); i++) {
- const TaskWrapper& task = tasks.itemAt(i);
+ const TaskWrapper& task = tasks[i];
task.mProcessor->process(task.mTask);
}
return true;
}
-bool TaskManager::WorkerThread::addTask(TaskWrapper task) {
+bool TaskManager::WorkerThread::addTask(const TaskWrapper& task) {
if (!isRunning()) {
run(mName.string(), PRIORITY_DEFAULT);
} else if (exitPending()) {
return false;
}
- ssize_t index;
{
Mutex::Autolock l(mLock);
- index = mTasks.add(task);
+ mTasks.push_back(task);
}
mSignal.signal();
- return index >= 0;
+ return true;
}
size_t TaskManager::WorkerThread::getTaskCount() const {
diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h
index 10e8b9e0bead..e4808f7b7181 100644
--- a/libs/hwui/thread/TaskManager.h
+++ b/libs/hwui/thread/TaskManager.h
@@ -20,10 +20,11 @@
#include <utils/Mutex.h>
#include <utils/String8.h>
#include <utils/Thread.h>
-#include <utils/Vector.h>
#include "Signal.h"
+#include <vector>
+
namespace android {
namespace uirenderer {
@@ -79,7 +80,7 @@ private:
public:
WorkerThread(const String8 name): mSignal(Condition::WAKE_UP_ONE), mName(name) { }
- bool addTask(TaskWrapper task);
+ bool addTask(const TaskWrapper& task);
size_t getTaskCount() const;
void exit();
@@ -89,7 +90,7 @@ private:
// Lock for the list of tasks
mutable Mutex mLock;
- Vector<TaskWrapper> mTasks;
+ std::vector<TaskWrapper> mTasks;
// Signal used to wake up the thread when a new
// task is available in the list
@@ -98,7 +99,7 @@ private:
const String8 mName;
};
- Vector<sp<WorkerThread> > mThreads;
+ std::vector<sp<WorkerThread> > mThreads;
};
}; // namespace uirenderer
diff --git a/libs/hwui/unit_tests/Android.mk b/libs/hwui/unit_tests/Android.mk
deleted file mode 100644
index 917e646e2303..000000000000
--- a/libs/hwui/unit_tests/Android.mk
+++ /dev/null
@@ -1,35 +0,0 @@
-#
-# Copyright (C) 2014 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.
-#
-
-local_target_dir := $(TARGET_OUT_DATA)/local/tmp
-LOCAL_PATH:= $(call my-dir)/..
-
-include $(CLEAR_VARS)
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.common.mk
-LOCAL_MODULE := hwui_unit_tests
-LOCAL_MODULE_TAGS := tests
-
-include $(LOCAL_PATH)/Android.common.mk
-
-LOCAL_SRC_FILES += \
- unit_tests/ClipAreaTests.cpp \
- unit_tests/DamageAccumulatorTests.cpp \
- unit_tests/LinearAllocatorTests.cpp \
- unit_tests/main.cpp
-
-
-include $(BUILD_NATIVE_TEST)
diff --git a/libs/hwui/unit_tests/ClipAreaTests.cpp b/libs/hwui/unit_tests/ClipAreaTests.cpp
deleted file mode 100644
index 0c5e5e715dea..000000000000
--- a/libs/hwui/unit_tests/ClipAreaTests.cpp
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2015 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 <gtest/gtest.h>
-#include <SkPath.h>
-#include <SkRegion.h>
-
-#include "ClipArea.h"
-
-#include "Matrix.h"
-#include "Rect.h"
-#include "utils/LinearAllocator.h"
-
-namespace android {
-namespace uirenderer {
-
-static Rect kViewportBounds(0, 0, 2048, 2048);
-
-static ClipArea createClipArea() {
- ClipArea area;
- area.setViewportDimensions(kViewportBounds.getWidth(), kViewportBounds.getHeight());
- return area;
-}
-
-TEST(TransformedRectangle, basics) {
- Rect r(0, 0, 100, 100);
- Matrix4 minus90;
- minus90.loadRotate(-90);
- minus90.mapRect(r);
- Rect r2(20, 40, 120, 60);
-
- Matrix4 m90;
- m90.loadRotate(90);
- TransformedRectangle tr(r, m90);
- EXPECT_TRUE(tr.canSimplyIntersectWith(tr));
-
- Matrix4 m0;
- TransformedRectangle tr0(r2, m0);
- EXPECT_FALSE(tr.canSimplyIntersectWith(tr0));
-
- Matrix4 m45;
- m45.loadRotate(45);
- TransformedRectangle tr2(r, m45);
- EXPECT_FALSE(tr2.canSimplyIntersectWith(tr));
-}
-
-TEST(RectangleList, basics) {
- RectangleList list;
- EXPECT_TRUE(list.isEmpty());
-
- Rect r(0, 0, 100, 100);
- Matrix4 m45;
- m45.loadRotate(45);
- list.set(r, m45);
- EXPECT_FALSE(list.isEmpty());
-
- Rect r2(20, 20, 200, 200);
- list.intersectWith(r2, m45);
- EXPECT_FALSE(list.isEmpty());
- EXPECT_EQ(1, list.getTransformedRectanglesCount());
-
- Rect r3(20, 20, 200, 200);
- Matrix4 m30;
- m30.loadRotate(30);
- list.intersectWith(r2, m30);
- EXPECT_FALSE(list.isEmpty());
- EXPECT_EQ(2, list.getTransformedRectanglesCount());
-
- SkRegion clip;
- clip.setRect(0, 0, 2000, 2000);
- SkRegion rgn(list.convertToRegion(clip));
- EXPECT_FALSE(rgn.isEmpty());
-}
-
-TEST(ClipArea, basics) {
- ClipArea area(createClipArea());
- EXPECT_FALSE(area.isEmpty());
-}
-
-TEST(ClipArea, paths) {
- ClipArea area(createClipArea());
- Matrix4 transform;
- transform.loadIdentity();
- SkPath path;
- SkScalar r = 100;
- path.addCircle(r, r, r);
- area.clipPathWithTransform(path, &transform, SkRegion::kIntersect_Op);
- EXPECT_FALSE(area.isEmpty());
- EXPECT_FALSE(area.isSimple());
- EXPECT_FALSE(area.isRectangleList());
- Rect clipRect(area.getClipRect());
- clipRect.dump("clipRect");
- Rect expected(0, 0, r * 2, r * 2);
- expected.dump("expected");
- EXPECT_EQ(expected, clipRect);
- SkRegion clipRegion(area.getClipRegion());
- auto skRect(clipRegion.getBounds());
- Rect regionBounds;
- regionBounds.set(skRect);
- EXPECT_EQ(expected, regionBounds);
-}
-
-TEST(ClipArea, replaceNegative) {
- ClipArea area(createClipArea());
- area.setClip(0, 0, 100, 100);
-
- Matrix4 transform;
- transform.loadIdentity();
- Rect expected(-50, -50, 50, 50);
- area.clipRectWithTransform(expected, &transform, SkRegion::kReplace_Op);
- EXPECT_EQ(expected, area.getClipRect());
-}
-}
-}
diff --git a/libs/hwui/unit_tests/DamageAccumulatorTests.cpp b/libs/hwui/unit_tests/DamageAccumulatorTests.cpp
deleted file mode 100644
index c8d6004e11ec..000000000000
--- a/libs/hwui/unit_tests/DamageAccumulatorTests.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2015 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 <gtest/gtest.h>
-
-#include <DamageAccumulator.h>
-#include <Matrix.h>
-#include <utils/LinearAllocator.h>
-
-#include <SkRect.h>
-
-using namespace android;
-using namespace android::uirenderer;
-
-// Test that push & pop are propegating the dirty rect
-// There is no transformation of the dirty rect, the input is the same
-// as the output.
-TEST(DamageAccumulator, identity) {
- DamageAccumulator da;
- Matrix4 identity;
- SkRect curDirty;
- identity.loadIdentity();
- da.pushTransform(&identity);
- da.dirty(50, 50, 100, 100);
- da.pushTransform(&identity);
- da.peekAtDirty(&curDirty);
- ASSERT_EQ(SkRect(), curDirty);
- da.popTransform();
- da.peekAtDirty(&curDirty);
- ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty);
- da.popTransform();
- da.finish(&curDirty);
- ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty);
-}
-
-// Test that transformation is happening at the correct levels via
-// peekAtDirty & popTransform. Just uses a simple translate to test this
-TEST(DamageAccumulator, translate) {
- DamageAccumulator da;
- Matrix4 translate;
- SkRect curDirty;
- translate.loadTranslate(25, 25, 0);
- da.pushTransform(&translate);
- da.dirty(50, 50, 100, 100);
- da.peekAtDirty(&curDirty);
- ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty);
- da.popTransform();
- da.finish(&curDirty);
- ASSERT_EQ(SkRect::MakeLTRB(75, 75, 125, 125), curDirty);
-}
-
-// Test that dirty rectangles are being unioned across "siblings
-TEST(DamageAccumulator, union) {
- DamageAccumulator da;
- Matrix4 identity;
- SkRect curDirty;
- identity.loadIdentity();
- da.pushTransform(&identity);
- da.pushTransform(&identity);
- da.dirty(50, 50, 100, 100);
- da.popTransform();
- da.pushTransform(&identity);
- da.dirty(150, 50, 200, 125);
- da.popTransform();
- da.popTransform();
- da.finish(&curDirty);
- ASSERT_EQ(SkRect::MakeLTRB(50, 50, 200, 125), curDirty);
-}
diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
deleted file mode 100644
index b3959d169e1d..000000000000
--- a/libs/hwui/unit_tests/LinearAllocatorTests.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2015 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 <gtest/gtest.h>
-#include <utils/LinearAllocator.h>
-
-using namespace android;
-using namespace android::uirenderer;
-
-struct SimplePair {
- int one = 1;
- int two = 2;
-};
-
-class SignalingDtor {
-public:
- SignalingDtor() {
- mDestroyed = nullptr;
- }
- SignalingDtor(bool* destroyedSignal) {
- mDestroyed = destroyedSignal;
- *mDestroyed = false;
- }
- virtual ~SignalingDtor() {
- if (mDestroyed) {
- *mDestroyed = true;
- }
- }
- void setSignal(bool* destroyedSignal) {
- mDestroyed = destroyedSignal;
- }
-private:
- bool* mDestroyed;
-};
-
-TEST(LinearAllocator, alloc) {
- LinearAllocator la;
- EXPECT_EQ(0u, la.usedSize());
- la.alloc(64);
- // There's some internal tracking as well as padding
- // so the usedSize isn't strictly defined
- EXPECT_LE(64u, la.usedSize());
- EXPECT_GT(80u, la.usedSize());
- auto pair = la.alloc<SimplePair>();
- EXPECT_LE(64u + sizeof(SimplePair), la.usedSize());
- EXPECT_GT(80u + sizeof(SimplePair), la.usedSize());
- EXPECT_EQ(1, pair->one);
- EXPECT_EQ(2, pair->two);
-}
-
-TEST(LinearAllocator, dtor) {
- bool destroyed[10];
- {
- LinearAllocator la;
- for (int i = 0; i < 5; i++) {
- la.alloc<SignalingDtor>()->setSignal(destroyed + i);
- la.alloc<SimplePair>();
- }
- la.alloc(100);
- for (int i = 0; i < 5; i++) {
- auto sd = new (la) SignalingDtor(destroyed + 5 + i);
- la.autoDestroy(sd);
- new (la) SimplePair();
- }
- la.alloc(100);
- for (int i = 0; i < 10; i++) {
- EXPECT_FALSE(destroyed[i]);
- }
- }
- for (int i = 0; i < 10; i++) {
- EXPECT_TRUE(destroyed[i]);
- }
-}
-
-TEST(LinearAllocator, rewind) {
- bool destroyed;
- {
- LinearAllocator la;
- auto addr = la.alloc(100);
- EXPECT_LE(100u, la.usedSize());
- la.rewindIfLastAlloc(addr, 100);
- EXPECT_GT(16u, la.usedSize());
- size_t emptySize = la.usedSize();
- auto sigdtor = la.alloc<SignalingDtor>();
- sigdtor->setSignal(&destroyed);
- EXPECT_FALSE(destroyed);
- EXPECT_LE(emptySize, la.usedSize());
- la.rewindIfLastAlloc(sigdtor);
- EXPECT_TRUE(destroyed);
- EXPECT_EQ(emptySize, la.usedSize());
- destroyed = false;
- }
- // Checking for a double-destroy case
- EXPECT_EQ(destroyed, false);
-}
diff --git a/libs/hwui/utils/Blur.cpp b/libs/hwui/utils/Blur.cpp
index 877a42216c27..9b70765ee8ad 100644
--- a/libs/hwui/utils/Blur.cpp
+++ b/libs/hwui/utils/Blur.cpp
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "OpenGLRenderer"
-
#include <math.h>
#include "Blur.h"
@@ -60,7 +58,9 @@ static float legacyConvertRadiusToSigma(float radius) {
return radius > 0 ? 0.3f * radius + 0.6f : 0.0f;
}
-void Blur::generateGaussianWeights(float* weights, int32_t radius) {
+void Blur::generateGaussianWeights(float* weights, float radius) {
+ int32_t intRadius = convertRadiusToInt(radius);
+
// Compute gaussian weights for the blur
// e is the euler's number
static float e = 2.718281828459045f;
@@ -68,7 +68,7 @@ void Blur::generateGaussianWeights(float* weights, int32_t radius) {
// g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
// x is of the form [-radius .. 0 .. radius]
// and sigma varies with radius.
- float sigma = legacyConvertRadiusToSigma((float) radius);
+ float sigma = legacyConvertRadiusToSigma(radius);
// Now compute the coefficints
// We will store some redundant values to save some math during
@@ -78,16 +78,16 @@ void Blur::generateGaussianWeights(float* weights, int32_t radius) {
float coeff2 = - 1.0f / (2.0f * sigma * sigma);
float normalizeFactor = 0.0f;
- for (int32_t r = -radius; r <= radius; r ++) {
+ for (int32_t r = -intRadius; r <= intRadius; r ++) {
float floatR = (float) r;
- weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
- normalizeFactor += weights[r + radius];
+ weights[r + intRadius] = coeff1 * pow(e, floatR * floatR * coeff2);
+ normalizeFactor += weights[r + intRadius];
}
//Now we need to normalize the weights because all our coefficients need to add up to one
normalizeFactor = 1.0f / normalizeFactor;
- for (int32_t r = -radius; r <= radius; r ++) {
- weights[r + radius] *= normalizeFactor;
+ for (int32_t r = -intRadius; r <= intRadius; r ++) {
+ weights[r + intRadius] *= normalizeFactor;
}
}
diff --git a/libs/hwui/utils/Blur.h b/libs/hwui/utils/Blur.h
index b14533312719..3f21832bf2b5 100644
--- a/libs/hwui/utils/Blur.h
+++ b/libs/hwui/utils/Blur.h
@@ -34,7 +34,7 @@ public:
// accounts for that error and snaps to the appropriate integer boundary.
static uint32_t convertRadiusToInt(float radius);
- static void generateGaussianWeights(float* weights, int32_t radius);
+ static void generateGaussianWeights(float* weights, float radius);
static void horizontal(float* weights, int32_t radius, const uint8_t* source,
uint8_t* dest, int32_t width, int32_t height);
static void vertical(float* weights, int32_t radius, const uint8_t* source,
diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h
new file mode 100644
index 000000000000..b5157f401438
--- /dev/null
+++ b/libs/hwui/utils/Color.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 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 COLOR_H
+#define COLOR_H
+
+#include <SkColor.h>
+
+namespace android {
+namespace uirenderer {
+ namespace Color {
+ enum Color {
+ Red_500 = 0xFFF44336,
+ Pink_500 = 0xFFE91E63,
+ Purple_500 = 0xFF9C27B0,
+ DeepPurple_500 = 0xFF673AB7,
+ Indigo_500 = 0xFF3F51B5,
+ Blue_500 = 0xFF2196F3,
+ LightBlue_300 = 0xFF4FC3F7,
+ LightBlue_500 = 0xFF03A9F4,
+ Cyan_500 = 0xFF00BCD4,
+ Teal_500 = 0xFF009688,
+ Teal_700 = 0xFF00796B,
+ Green_500 = 0xFF4CAF50,
+ Green_700 = 0xFF388E3C,
+ LightGreen_500 = 0xFF8BC34A,
+ LightGreen_700 = 0xFF689F38,
+ Lime_500 = 0xFFCDDC39,
+ Yellow_500 = 0xFFFFEB3B,
+ Amber_500 = 0xFFFFC107,
+ Orange_500 = 0xFFFF9800,
+ DeepOrange_500 = 0xFFFF5722,
+ Brown_500 = 0xFF795548,
+ Grey_200 = 0xFFEEEEEE,
+ Grey_500 = 0xFF9E9E9E,
+ Grey_700 = 0xFF616161,
+ BlueGrey_500 = 0xFF607D8B,
+ Transparent = 0x00000000,
+ Black = 0xFF000000,
+ White = 0xFFFFFFFF,
+ };
+ }
+
+ static_assert(Color::White == SK_ColorWHITE, "color format has changed");
+ static_assert(Color::Black == SK_ColorBLACK, "color format has changed");
+
+ // Array of bright (500 intensity) colors for synthetic content
+ static const Color::Color BrightColors[] = {
+ Color::Red_500,
+ Color::Pink_500,
+ Color::Purple_500,
+ Color::DeepPurple_500,
+ Color::Indigo_500,
+ Color::Blue_500,
+ Color::LightBlue_500,
+ Color::Cyan_500,
+ Color::Teal_500,
+ Color::Green_500,
+ Color::LightGreen_500,
+ Color::Lime_500,
+ Color::Yellow_500,
+ Color::Amber_500,
+ Color::Orange_500,
+ Color::DeepOrange_500,
+ Color::Brown_500,
+ Color::Grey_500,
+ Color::BlueGrey_500,
+ };
+ static constexpr int BrightColorsCount = sizeof(BrightColors) / sizeof(Color::Color);
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* TEST_UTILS_H */
diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h
new file mode 100644
index 000000000000..93d37c28f8a4
--- /dev/null
+++ b/libs/hwui/utils/FatVector.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2015, The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ANDROID_FAT_VECTOR_H
+#define ANDROID_FAT_VECTOR_H
+
+#include "utils/Macros.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <type_traits>
+#include <utils/Log.h>
+
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+template <typename T, size_t SIZE>
+class InlineStdAllocator {
+public:
+ struct Allocation {
+ PREVENT_COPY_AND_ASSIGN(Allocation);
+ public:
+ Allocation() {};
+ // char array instead of T array, so memory is uninitialized, with no destructors run
+ char array[sizeof(T) * SIZE];
+ bool inUse = false;
+ };
+
+ typedef T value_type; // needed to implement std::allocator
+ typedef T* pointer; // needed to implement std::allocator
+
+ InlineStdAllocator(Allocation& allocation)
+ : mAllocation(allocation) {}
+ InlineStdAllocator(const InlineStdAllocator& other)
+ : mAllocation(other.mAllocation) {}
+ ~InlineStdAllocator() {}
+
+ T* allocate(size_t num, const void* = 0) {
+ if (!mAllocation.inUse && num <= SIZE) {
+ mAllocation.inUse = true;
+ return (T*) mAllocation.array;
+ } else {
+ return (T*) malloc(num * sizeof(T));
+ }
+ }
+
+ void deallocate(pointer p, size_t num) {
+ if (p == (T*)mAllocation.array) {
+ mAllocation.inUse = false;
+ } else {
+ // 'free' instead of delete here - destruction handled separately
+ free(p);
+ }
+ }
+ Allocation& mAllocation;
+};
+
+/**
+ * std::vector with SIZE elements preallocated into an internal buffer.
+ *
+ * Useful for avoiding the cost of malloc in cases where only SIZE or
+ * fewer elements are needed in the common case.
+ */
+template <typename T, size_t SIZE>
+class FatVector : public std::vector<T, InlineStdAllocator<T, SIZE>> {
+public:
+ FatVector() : std::vector<T, InlineStdAllocator<T, SIZE>>(
+ InlineStdAllocator<T, SIZE>(mAllocation)) {
+ this->reserve(SIZE);
+ }
+
+ FatVector(size_t capacity) : FatVector() {
+ this->resize(capacity);
+ }
+
+private:
+ typename InlineStdAllocator<T, SIZE>::Allocation mAllocation;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_FAT_VECTOR_H
diff --git a/libs/hwui/utils/GLUtils.cpp b/libs/hwui/utils/GLUtils.cpp
index 55104de5a9d8..332097593c86 100644
--- a/libs/hwui/utils/GLUtils.cpp
+++ b/libs/hwui/utils/GLUtils.cpp
@@ -21,10 +21,19 @@
#include "GLUtils.h"
+#if DEBUG_OPENGL >= DEBUG_LEVEL_HIGH && !defined(HWUI_GLES_WRAP_ENABLED)
+#error Setting DEBUG_OPENGL to HIGH requires setting HWUI_ENABLE_OPENGL_VALIDATION to true in the Android.mk!
+#endif
+
namespace android {
namespace uirenderer {
bool GLUtils::dumpGLErrors() {
+#if DEBUG_OPENGL >= DEBUG_LEVEL_HIGH
+ // If DEBUG_LEVEL_HIGH is set then every GLES call is already wrapped
+ // and asserts that there was no error. So this can just return success.
+ return false;
+#else
bool errorObserved = false;
GLenum status = GL_NO_ERROR;
while ((status = glGetError()) != GL_NO_ERROR) {
@@ -47,6 +56,7 @@ bool GLUtils::dumpGLErrors() {
}
}
return errorObserved;
+#endif
}
}; // namespace uirenderer
diff --git a/libs/hwui/utils/GLUtils.h b/libs/hwui/utils/GLUtils.h
index 702046148ad8..b49c1eb1dc05 100644
--- a/libs/hwui/utils/GLUtils.h
+++ b/libs/hwui/utils/GLUtils.h
@@ -16,13 +16,29 @@
#ifndef GLUTILS_H
#define GLUTILS_H
+#include "Debug.h"
+
+#include <cutils/log.h>
+
namespace android {
namespace uirenderer {
+
+#if DEBUG_OPENGL
+#define GL_CHECKPOINT(LEVEL) \
+ do { if (DEBUG_OPENGL >= DEBUG_LEVEL_##LEVEL) {\
+ LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(),\
+ "GL errors! %s:%d", __FILE__, __LINE__);\
+ } } while (0)
+#else
+#define GL_CHECKPOINT(LEVEL)
+#endif
+
class GLUtils {
public:
/**
* Print out any GL errors with ALOGE, returns true if any errors were found.
+ * You probably want to use GL_CHECKPOINT(LEVEL) instead of calling this directly
*/
static bool dumpGLErrors();
diff --git a/libs/hwui/utils/LinearAllocator.cpp b/libs/hwui/utils/LinearAllocator.cpp
index 59b12cf66a89..5bba420a258f 100644
--- a/libs/hwui/utils/LinearAllocator.cpp
+++ b/libs/hwui/utils/LinearAllocator.cpp
@@ -32,7 +32,7 @@
// The ideal size of a page allocation (these need to be multiples of 8)
-#define INITIAL_PAGE_SIZE ((size_t)4096) // 4kb
+#define INITIAL_PAGE_SIZE ((size_t)512) // 512b
#define MAX_PAGE_SIZE ((size_t)131072) // 128kb
// The maximum amount of wasted space we can have per page
@@ -40,7 +40,7 @@
// If this is too low, we will malloc too much
// Too high, and we may waste too much space
// Must be smaller than INITIAL_PAGE_SIZE
-#define MAX_WASTE_SIZE ((size_t)1024)
+#define MAX_WASTE_RATIO (0.5f)
#if ALIGN_DOUBLE
#define ALIGN_SZ (sizeof(double))
@@ -52,8 +52,8 @@
#define ALIGN_PTR(p) ((void*)(ALIGN((size_t)p)))
#if LOG_NDEBUG
-#define ADD_ALLOCATION(size)
-#define RM_ALLOCATION(size)
+#define ADD_ALLOCATION()
+#define RM_ALLOCATION()
#else
#include <utils/Thread.h>
#include <utils/Timers.h>
@@ -65,26 +65,22 @@ static void _logUsageLocked() {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
if (now > s_nextLog) {
s_nextLog = now + milliseconds_to_nanoseconds(10);
- ALOGV("Total memory usage: %zu kb", s_totalAllocations / 1024);
+ ALOGV("Total pages allocated: %zu", s_totalAllocations);
}
}
-static void _addAllocation(size_t size) {
+static void _addAllocation(int count) {
android::AutoMutex lock(s_mutex);
- s_totalAllocations += size;
+ s_totalAllocations += count;
_logUsageLocked();
}
-#define ADD_ALLOCATION(size) _addAllocation(size);
-#define RM_ALLOCATION(size) _addAllocation(-size);
+#define ADD_ALLOCATION(size) _addAllocation(1);
+#define RM_ALLOCATION(size) _addAllocation(-1);
#endif
#define min(x,y) (((x) < (y)) ? (x) : (y))
-void* operator new(std::size_t size, android::uirenderer::LinearAllocator& la) {
- return la.alloc(size);
-}
-
namespace android {
namespace uirenderer {
@@ -114,7 +110,7 @@ private:
LinearAllocator::LinearAllocator()
: mPageSize(INITIAL_PAGE_SIZE)
- , mMaxAllocSize(MAX_WASTE_SIZE)
+ , mMaxAllocSize(INITIAL_PAGE_SIZE * MAX_WASTE_RATIO)
, mNext(0)
, mCurrentPage(0)
, mPages(0)
@@ -134,13 +130,13 @@ LinearAllocator::~LinearAllocator(void) {
Page* next = p->next();
p->~Page();
free(p);
- RM_ALLOCATION(mPageSize);
+ RM_ALLOCATION();
p = next;
}
}
void* LinearAllocator::start(Page* p) {
- return ALIGN_PTR(((size_t*)p) + sizeof(Page));
+ return ALIGN_PTR((size_t)p + sizeof(Page));
}
void* LinearAllocator::end(Page* p) {
@@ -156,6 +152,7 @@ void LinearAllocator::ensureNext(size_t size) {
if (mCurrentPage && mPageSize < MAX_PAGE_SIZE) {
mPageSize = min(MAX_PAGE_SIZE, mPageSize * 2);
+ mMaxAllocSize = mPageSize * MAX_WASTE_RATIO;
mPageSize = ALIGN(mPageSize);
}
mWastedSpace += mPageSize;
@@ -170,7 +167,7 @@ void LinearAllocator::ensureNext(size_t size) {
mNext = start(mCurrentPage);
}
-void* LinearAllocator::alloc(size_t size) {
+void* LinearAllocator::allocImpl(size_t size) {
size = ALIGN(size);
if (size > mMaxAllocSize && !fitsInCurrentPage(size)) {
ALOGV("Exceeded max size %zu > %zu", size, mMaxAllocSize);
@@ -195,7 +192,7 @@ void LinearAllocator::addToDestructionList(Destructor dtor, void* addr) {
"DestructorNode must have standard layout");
static_assert(std::is_trivially_destructible<DestructorNode>::value,
"DestructorNode must be trivially destructable");
- auto node = new (*this) DestructorNode();
+ auto node = new (allocImpl(sizeof(DestructorNode))) DestructorNode();
node->dtor = dtor;
node->addr = addr;
node->next = mDtorList;
@@ -237,7 +234,7 @@ void LinearAllocator::rewindIfLastAlloc(void* ptr, size_t allocSize) {
LinearAllocator::Page* LinearAllocator::newPage(size_t pageSize) {
pageSize = ALIGN(pageSize + sizeof(LinearAllocator::Page));
- ADD_ALLOCATION(pageSize);
+ ADD_ALLOCATION();
mTotalAllocated += pageSize;
mPageCount++;
void* buf = malloc(pageSize);
diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h
index d90dd825ea1d..34c8c6beea81 100644
--- a/libs/hwui/utils/LinearAllocator.h
+++ b/libs/hwui/utils/LinearAllocator.h
@@ -29,6 +29,8 @@
#include <stddef.h>
#include <type_traits>
+#include <vector>
+
namespace android {
namespace uirenderer {
@@ -50,30 +52,43 @@ public:
* The lifetime of the returned buffers is tied to that of the LinearAllocator. If calling
* delete() on an object stored in a buffer is needed, it should be overridden to use
* rewindIfLastAlloc()
+ *
+ * Note that unlike create, for alloc the type is purely for compile-time error
+ * checking and does not affect size.
*/
- void* alloc(size_t size);
+ template<class T>
+ void* alloc(size_t size) {
+ static_assert(std::is_trivially_destructible<T>::value,
+ "Error, type is non-trivial! did you mean to use create()?");
+ return allocImpl(size);
+ }
/**
- * Allocates an instance of the template type with the default constructor
+ * Allocates an instance of the template type with the given construction parameters
* and adds it to the automatic destruction list.
*/
- template<class T>
- T* alloc() {
- T* ret = new (*this) T;
- autoDestroy(ret);
+ template<class T, typename... Params>
+ T* create(Params&&... params) {
+ T* ret = new (allocImpl(sizeof(T))) T(std::forward<Params>(params)...);
+ if (!std::is_trivially_destructible<T>::value) {
+ auto dtor = [](void* ret) { ((T*)ret)->~T(); };
+ addToDestructionList(dtor, ret);
+ }
return ret;
}
- /**
- * Adds the pointer to the tracking list to have its destructor called
- * when the LinearAllocator is destroyed.
- */
+ template<class T, typename... Params>
+ T* create_trivial(Params&&... params) {
+ static_assert(std::is_trivially_destructible<T>::value,
+ "Error, called create_trivial on a non-trivial type");
+ return new (allocImpl(sizeof(T))) T(std::forward<Params>(params)...);
+ }
+
template<class T>
- void autoDestroy(T* addr) {
- if (!std::is_trivially_destructible<T>::value) {
- auto dtor = [](void* addr) { ((T*)addr)->~T(); };
- addToDestructionList(dtor, addr);
- }
+ T* create_trivial_array(int count) {
+ static_assert(std::is_trivially_destructible<T>::value,
+ "Error, called create_trivial_array on a non-trivial type");
+ return reinterpret_cast<T*>(allocImpl(sizeof(T) * count));
}
/**
@@ -112,6 +127,8 @@ private:
DestructorNode* next = nullptr;
};
+ void* allocImpl(size_t size);
+
void addToDestructionList(Destructor, void* addr);
void runDestructorFor(void* addr);
Page* newPage(size_t pageSize);
@@ -134,9 +151,55 @@ private:
size_t mDedicatedPageCount;
};
+template <class T>
+class LinearStdAllocator {
+public:
+ typedef T value_type; // needed to implement std::allocator
+ typedef T* pointer; // needed to implement std::allocator
+
+ LinearStdAllocator(LinearAllocator& allocator)
+ : linearAllocator(allocator) {}
+ LinearStdAllocator(const LinearStdAllocator& other)
+ : linearAllocator(other.linearAllocator) {}
+ ~LinearStdAllocator() {}
+
+ // rebind marks that allocators can be rebound to different types
+ template <class U>
+ struct rebind {
+ typedef LinearStdAllocator<U> other;
+ };
+ // enable allocators to be constructed from other templated types
+ template <class U>
+ LinearStdAllocator(const LinearStdAllocator<U>& other)
+ : linearAllocator(other.linearAllocator) {}
+
+ T* allocate(size_t num, const void* = 0) {
+ return (T*)(linearAllocator.alloc<void*>(num * sizeof(T)));
+ }
+
+ void deallocate(pointer p, size_t num) {
+ // attempt to rewind, but no guarantees
+ linearAllocator.rewindIfLastAlloc(p, num * sizeof(T));
+ }
+
+ // public so template copy constructor can access
+ LinearAllocator& linearAllocator;
+};
+
+// return that all specializations of LinearStdAllocator are interchangeable
+template <class T1, class T2>
+bool operator== (const LinearStdAllocator<T1>&, const LinearStdAllocator<T2>&) { return true; }
+template <class T1, class T2>
+bool operator!= (const LinearStdAllocator<T1>&, const LinearStdAllocator<T2>&) { return false; }
+
+template <class T>
+class LsaVector : public std::vector<T, LinearStdAllocator<T>> {
+public:
+ LsaVector(const LinearStdAllocator<T>& allocator)
+ : std::vector<T, LinearStdAllocator<T>>(allocator) {}
+};
+
}; // namespace uirenderer
}; // namespace android
-void* operator new(std::size_t size, android::uirenderer::LinearAllocator& la);
-
#endif // ANDROID_LINEARALLOCATOR_H
diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h
index 5ca9083aab87..7212897bf5d3 100644
--- a/libs/hwui/utils/Macros.h
+++ b/libs/hwui/utils/Macros.h
@@ -23,16 +23,17 @@
Type(const Type&) = delete; \
void operator=(const Type&) = delete
-#define DESCRIPTION_TYPE(Type) \
- int compare(const Type& rhs) const { return memcmp(this, &rhs, sizeof(Type));} \
- bool operator==(const Type& other) const { return compare(other) == 0; } \
- bool operator!=(const Type& other) const { return compare(other) != 0; } \
- friend inline int strictly_order_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs) < 0; } \
- friend inline int compare_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs); } \
+#define HASHABLE_TYPE(Type) \
+ bool operator==(const Type& other) const; \
+ hash_t hash() const; \
+ bool operator!=(const Type& other) const { return !(*this == other); } \
friend inline hash_t hash_type(const Type& entry) { return entry.hash(); }
#define REQUIRE_COMPATIBLE_LAYOUT(Type) \
static_assert(std::is_standard_layout<Type>::value, \
#Type " must have standard layout")
+#define WARN_UNUSED_RESULT \
+ __attribute__((warn_unused_result))
+
#endif /* MACROS_H */
diff --git a/libs/hwui/utils/MathUtils.h b/libs/hwui/utils/MathUtils.h
index 9c3787cd1e7f..8d20f2142e73 100644
--- a/libs/hwui/utils/MathUtils.h
+++ b/libs/hwui/utils/MathUtils.h
@@ -16,6 +16,7 @@
#ifndef MATHUTILS_H
#define MATHUTILS_H
+#include <algorithm>
#include <math.h>
namespace android {
@@ -82,18 +83,8 @@ public:
}
template<typename T>
- static inline T max(T a, T b) {
- return a > b ? a : b;
- }
-
- template<typename T>
- static inline T min(T a, T b) {
- return a < b ? a : b;
- }
-
- template<typename T>
static inline T clamp(T a, T minValue, T maxValue) {
- return min(max(a, minValue), maxValue);
+ return std::min(std::max(a, minValue), maxValue);
}
inline static float lerp(float v1, float v2, float t) {
diff --git a/libs/hwui/utils/NinePatch.h b/libs/hwui/utils/NinePatch.h
new file mode 100644
index 000000000000..323e56312fb7
--- /dev/null
+++ b/libs/hwui/utils/NinePatch.h
@@ -0,0 +1,37 @@
+/*
+**
+** Copyright 2015, 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_GRAPHICS_NINEPATCH_H
+#define ANDROID_GRAPHICS_NINEPATCH_H
+
+#include <androidfw/ResourceTypes.h>
+#include <cutils/compiler.h>
+
+#include "SkCanvas.h"
+#include "SkRegion.h"
+
+namespace android {
+
+class ANDROID_API NinePatch {
+public:
+ static void Draw(SkCanvas* canvas, const SkRect& bounds, const SkBitmap& bitmap,
+ const Res_png_9patch& chunk, const SkPaint* paint, SkRegion** outRegion);
+};
+
+} // namespace android
+
+#endif // ANDROID_GRAPHICS_NINEPATCH_H
diff --git a/libs/hwui/utils/NinePatchImpl.cpp b/libs/hwui/utils/NinePatchImpl.cpp
new file mode 100644
index 000000000000..985f3fb66814
--- /dev/null
+++ b/libs/hwui/utils/NinePatchImpl.cpp
@@ -0,0 +1,326 @@
+/*
+**
+** Copyright 2006, 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/NinePatch.h"
+
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkColorPriv.h"
+#include "SkNinePatch.h"
+#include "SkPaint.h"
+#include "SkUnPreMultiply.h"
+
+#include <utils/Log.h>
+
+namespace android {
+
+static const bool kUseTrace = true;
+static bool gTrace = false;
+
+static bool getColor(const SkBitmap& bitmap, int x, int y, SkColor* c) {
+ switch (bitmap.colorType()) {
+ case kN32_SkColorType:
+ *c = SkUnPreMultiply::PMColorToColor(*bitmap.getAddr32(x, y));
+ break;
+ case kRGB_565_SkColorType:
+ *c = SkPixel16ToPixel32(*bitmap.getAddr16(x, y));
+ break;
+ case kARGB_4444_SkColorType:
+ *c = SkUnPreMultiply::PMColorToColor(
+ SkPixel4444ToPixel32(*bitmap.getAddr16(x, y)));
+ break;
+ case kIndex_8_SkColorType: {
+ SkColorTable* ctable = bitmap.getColorTable();
+ *c = SkUnPreMultiply::PMColorToColor(
+ (*ctable)[*bitmap.getAddr8(x, y)]);
+ break;
+ }
+ default:
+ return false;
+ }
+ return true;
+}
+
+static SkColor modAlpha(SkColor c, int alpha) {
+ int scale = alpha + (alpha >> 7);
+ int a = SkColorGetA(c) * scale >> 8;
+ return SkColorSetA(c, a);
+}
+
+static void drawStretchyPatch(SkCanvas* canvas, SkIRect& src, const SkRect& dst,
+ const SkBitmap& bitmap, const SkPaint& paint,
+ SkColor initColor, uint32_t colorHint,
+ bool hasXfer) {
+ if (colorHint != android::Res_png_9patch::NO_COLOR) {
+ ((SkPaint*)&paint)->setColor(modAlpha(colorHint, paint.getAlpha()));
+ canvas->drawRect(dst, paint);
+ ((SkPaint*)&paint)->setColor(initColor);
+ } else if (src.width() == 1 && src.height() == 1) {
+ SkColor c;
+ if (!getColor(bitmap, src.fLeft, src.fTop, &c)) {
+ goto SLOW_CASE;
+ }
+ if (0 != c || hasXfer) {
+ SkColor prev = paint.getColor();
+ ((SkPaint*)&paint)->setColor(c);
+ canvas->drawRect(dst, paint);
+ ((SkPaint*)&paint)->setColor(prev);
+ }
+ } else {
+ SLOW_CASE:
+ canvas->drawBitmapRect(bitmap, SkRect::Make(src), dst, &paint);
+ }
+}
+
+SkScalar calculateStretch(SkScalar boundsLimit, SkScalar startingPoint,
+ int srcSpace, int numStrechyPixelsRemaining,
+ int numFixedPixelsRemaining) {
+ SkScalar spaceRemaining = boundsLimit - startingPoint;
+ SkScalar stretchySpaceRemaining =
+ spaceRemaining - SkIntToScalar(numFixedPixelsRemaining);
+ return srcSpace * stretchySpaceRemaining / numStrechyPixelsRemaining;
+}
+
+void NinePatch::Draw(SkCanvas* canvas, const SkRect& bounds,
+ const SkBitmap& bitmap, const Res_png_9patch& chunk,
+ const SkPaint* paint, SkRegion** outRegion) {
+ if (canvas && canvas->quickReject(bounds)) {
+ return;
+ }
+
+ SkPaint defaultPaint;
+ if (NULL == paint) {
+ // matches default dither in NinePatchDrawable.java.
+ defaultPaint.setDither(true);
+ paint = &defaultPaint;
+ }
+
+ const int32_t* xDivs = chunk.getXDivs();
+ const int32_t* yDivs = chunk.getYDivs();
+ // if our SkCanvas were back by GL we should enable this and draw this as
+ // a mesh, which will be faster in most cases.
+ if ((false)) {
+ SkNinePatch::DrawMesh(canvas, bounds, bitmap,
+ xDivs, chunk.numXDivs,
+ yDivs, chunk.numYDivs,
+ paint);
+ return;
+ }
+
+ if (kUseTrace) {
+ gTrace = true;
+ }
+
+ SkASSERT(canvas || outRegion);
+
+ if (kUseTrace) {
+ if (canvas) {
+ const SkMatrix& m = canvas->getTotalMatrix();
+ ALOGV("ninepatch [%g %g %g] [%g %g %g]\n",
+ SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]),
+ SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5]));
+ }
+
+ ALOGV("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()),
+ SkScalarToFloat(bounds.height()));
+ ALOGV("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height());
+ ALOGV("======== ninepatch xDivs [%d,%d]\n", xDivs[0], xDivs[1]);
+ ALOGV("======== ninepatch yDivs [%d,%d]\n", yDivs[0], yDivs[1]);
+ }
+
+ if (bounds.isEmpty() ||
+ bitmap.width() == 0 || bitmap.height() == 0 ||
+ (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0))
+ {
+ if (kUseTrace) {
+ ALOGV("======== abort ninepatch draw\n");
+ }
+ return;
+ }
+
+ // should try a quick-reject test before calling lockPixels
+
+ SkAutoLockPixels alp(bitmap);
+ // after the lock, it is valid to check getPixels()
+ if (bitmap.getPixels() == NULL)
+ return;
+
+ const bool hasXfer = paint->getXfermode() != NULL;
+ SkRect dst;
+ SkIRect src;
+
+ const int32_t x0 = xDivs[0];
+ const int32_t y0 = yDivs[0];
+ const SkColor initColor = ((SkPaint*)paint)->getColor();
+ const uint8_t numXDivs = chunk.numXDivs;
+ const uint8_t numYDivs = chunk.numYDivs;
+ int i;
+ int j;
+ int colorIndex = 0;
+ uint32_t color;
+ bool xIsStretchable;
+ const bool initialXIsStretchable = (x0 == 0);
+ bool yIsStretchable = (y0 == 0);
+ const int bitmapWidth = bitmap.width();
+ const int bitmapHeight = bitmap.height();
+
+ // Number of bytes needed for dstRights array.
+ // Need to cast numXDivs to a larger type to avoid overflow.
+ const size_t dstBytes = ((size_t) numXDivs + 1) * sizeof(SkScalar);
+ SkScalar* dstRights = (SkScalar*) alloca(dstBytes);
+ bool dstRightsHaveBeenCached = false;
+
+ int numStretchyXPixelsRemaining = 0;
+ for (i = 0; i < numXDivs; i += 2) {
+ numStretchyXPixelsRemaining += xDivs[i + 1] - xDivs[i];
+ }
+ int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining;
+ int numStretchyYPixelsRemaining = 0;
+ for (i = 0; i < numYDivs; i += 2) {
+ numStretchyYPixelsRemaining += yDivs[i + 1] - yDivs[i];
+ }
+ int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining;
+
+ if (kUseTrace) {
+ ALOGV("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n",
+ bitmap.width(), bitmap.height(),
+ SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
+ SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
+ numXDivs, numYDivs);
+ }
+
+ src.fTop = 0;
+ dst.fTop = bounds.fTop;
+ // The first row always starts with the top being at y=0 and the bottom
+ // being either yDivs[1] (if yDivs[0]=0) or yDivs[0]. In the former case
+ // the first row is stretchable along the Y axis, otherwise it is fixed.
+ // The last row always ends with the bottom being bitmap.height and the top
+ // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+ // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+ // the Y axis, otherwise it is fixed.
+ //
+ // The first and last columns are similarly treated with respect to the X
+ // axis.
+ //
+ // The above is to help explain some of the special casing that goes on the
+ // code below.
+
+ // The initial yDiv and whether the first row is considered stretchable or
+ // not depends on whether yDiv[0] was zero or not.
+ for (j = yIsStretchable ? 1 : 0;
+ j <= numYDivs && src.fTop < bitmapHeight;
+ j++, yIsStretchable = !yIsStretchable) {
+ src.fLeft = 0;
+ dst.fLeft = bounds.fLeft;
+ if (j == numYDivs) {
+ src.fBottom = bitmapHeight;
+ dst.fBottom = bounds.fBottom;
+ } else {
+ src.fBottom = yDivs[j];
+ const int srcYSize = src.fBottom - src.fTop;
+ if (yIsStretchable) {
+ dst.fBottom = dst.fTop + calculateStretch(bounds.fBottom, dst.fTop,
+ srcYSize,
+ numStretchyYPixelsRemaining,
+ numFixedYPixelsRemaining);
+ numStretchyYPixelsRemaining -= srcYSize;
+ } else {
+ dst.fBottom = dst.fTop + SkIntToScalar(srcYSize);
+ numFixedYPixelsRemaining -= srcYSize;
+ }
+ }
+
+ xIsStretchable = initialXIsStretchable;
+ // The initial xDiv and whether the first column is considered
+ // stretchable or not depends on whether xDiv[0] was zero or not.
+ const uint32_t* colors = chunk.getColors();
+ for (i = xIsStretchable ? 1 : 0;
+ i <= numXDivs && src.fLeft < bitmapWidth;
+ i++, xIsStretchable = !xIsStretchable) {
+ color = colors[colorIndex++];
+ if (i == numXDivs) {
+ src.fRight = bitmapWidth;
+ dst.fRight = bounds.fRight;
+ } else {
+ src.fRight = xDivs[i];
+ if (dstRightsHaveBeenCached) {
+ dst.fRight = dstRights[i];
+ } else {
+ const int srcXSize = src.fRight - src.fLeft;
+ if (xIsStretchable) {
+ dst.fRight = dst.fLeft + calculateStretch(bounds.fRight, dst.fLeft,
+ srcXSize,
+ numStretchyXPixelsRemaining,
+ numFixedXPixelsRemaining);
+ numStretchyXPixelsRemaining -= srcXSize;
+ } else {
+ dst.fRight = dst.fLeft + SkIntToScalar(srcXSize);
+ numFixedXPixelsRemaining -= srcXSize;
+ }
+ dstRights[i] = dst.fRight;
+ }
+ }
+ // If this horizontal patch is too small to be displayed, leave
+ // the destination left edge where it is and go on to the next patch
+ // in the source.
+ if (src.fLeft >= src.fRight) {
+ src.fLeft = src.fRight;
+ continue;
+ }
+ // Make sure that we actually have room to draw any bits
+ if (dst.fRight <= dst.fLeft || dst.fBottom <= dst.fTop) {
+ goto nextDiv;
+ }
+ // If this patch is transparent, skip and don't draw.
+ if (color == android::Res_png_9patch::TRANSPARENT_COLOR && !hasXfer) {
+ if (outRegion) {
+ if (*outRegion == NULL) {
+ *outRegion = new SkRegion();
+ }
+ SkIRect idst;
+ dst.round(&idst);
+ //ALOGI("Adding trans rect: (%d,%d)-(%d,%d)\n",
+ // idst.fLeft, idst.fTop, idst.fRight, idst.fBottom);
+ (*outRegion)->op(idst, SkRegion::kUnion_Op);
+ }
+ goto nextDiv;
+ }
+ if (canvas) {
+ if (kUseTrace) {
+ ALOGV("-- src [%d %d %d %d] dst [%g %g %g %g]\n",
+ src.fLeft, src.fTop, src.width(), src.height(),
+ SkScalarToFloat(dst.fLeft), SkScalarToFloat(dst.fTop),
+ SkScalarToFloat(dst.width()), SkScalarToFloat(dst.height()));
+ if (2 == src.width() && SkIntToScalar(5) == dst.width()) {
+ ALOGV("--- skip patch\n");
+ }
+ }
+ drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor,
+ color, hasXfer);
+ }
+
+nextDiv:
+ src.fLeft = src.fRight;
+ dst.fLeft = dst.fRight;
+ }
+ src.fTop = src.fBottom;
+ dst.fTop = dst.fBottom;
+ dstRightsHaveBeenCached = true;
+ }
+}
+
+} // namespace android
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index ba02f5f1a77d..4faab9a5f648 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -16,12 +16,20 @@
#ifndef PAINT_UTILS_H
#define PAINT_UTILS_H
+#include <utils/Blur.h>
+
#include <SkColorFilter.h>
+#include <SkDrawLooper.h>
+#include <SkShader.h>
#include <SkXfermode.h>
namespace android {
namespace uirenderer {
+/**
+ * Utility methods for accessing data within SkPaint, and providing defaults
+ * with optional SkPaint pointers.
+ */
class PaintUtils {
public:
@@ -59,6 +67,21 @@ public:
&& getXfermode(paint.getXfermode()) == SkXfermode::kSrcOver_Mode;
}
+ static bool isOpaquePaint(const SkPaint* paint) {
+ if (!paint) return true; // default (paintless) behavior is SrcOver, black
+
+ if (paint->getAlpha() != 0xFF
+ || PaintUtils::isBlendedShader(paint->getShader())
+ || PaintUtils::isBlendedColorFilter(paint->getColorFilter())) {
+ return false;
+ }
+
+ // Only let simple srcOver / src blending modes declare opaque, since behavior is clear.
+ SkXfermode::Mode mode = getXfermode(paint->getXfermode());
+ return mode == SkXfermode::Mode::kSrcOver_Mode
+ || mode == SkXfermode::Mode::kSrc_Mode;
+ }
+
static bool isBlendedShader(const SkShader* shader) {
if (shader == nullptr) {
return false;
@@ -73,6 +96,39 @@ public:
return (filter->getFlags() & SkColorFilter::kAlphaUnchanged_Flag) == 0;
}
+ struct TextShadow {
+ SkScalar radius;
+ float dx;
+ float dy;
+ SkColor color;
+ };
+
+ static inline bool getTextShadow(const SkPaint* paint, TextShadow* textShadow) {
+ SkDrawLooper::BlurShadowRec blur;
+ if (paint && paint->getLooper() && paint->getLooper()->asABlurShadow(&blur)) {
+ if (textShadow) {
+ textShadow->radius = Blur::convertSigmaToRadius(blur.fSigma);
+ textShadow->dx = blur.fOffset.fX;
+ textShadow->dy = blur.fOffset.fY;
+ textShadow->color = blur.fColor;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ static inline bool hasTextShadow(const SkPaint* paint) {
+ return getTextShadow(paint, nullptr);
+ }
+
+ static inline SkXfermode::Mode getXfermodeDirect(const SkPaint* paint) {
+ return paint ? getXfermode(paint->getXfermode()) : SkXfermode::kSrcOver_Mode;
+ }
+
+ static inline int getAlphaDirect(const SkPaint* paint) {
+ return paint ? paint->getAlpha() : 255;
+ }
+
}; // class PaintUtils
} /* namespace uirenderer */
diff --git a/libs/hwui/utils/RingBuffer.h b/libs/hwui/utils/RingBuffer.h
index 6895f07fd60a..06bcdcd7d84b 100644
--- a/libs/hwui/utils/RingBuffer.h
+++ b/libs/hwui/utils/RingBuffer.h
@@ -32,7 +32,7 @@ public:
~RingBuffer() {}
constexpr size_t capacity() const { return SIZE; }
- size_t size() { return mCount; }
+ size_t size() const { return mCount; }
T& next() {
mHead = (mHead + 1) % SIZE;
@@ -54,6 +54,10 @@ public:
return mBuffer[(mHead + index + 1) % mCount];
}
+ const T& operator[](size_t index) const {
+ return mBuffer[(mHead + index + 1) % mCount];
+ }
+
void clear() {
mCount = 0;
mHead = -1;
diff --git a/libs/hwui/utils/SortedList.h b/libs/hwui/utils/SortedList.h
deleted file mode 100644
index a2c8c52fcbc7..000000000000
--- a/libs/hwui/utils/SortedList.h
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2010 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_SORTED_LIST_H
-#define ANDROID_HWUI_SORTED_LIST_H
-
-#include <stdint.h>
-#include <sys/types.h>
-
-#include <utils/Vector.h>
-#include <utils/TypeHelpers.h>
-
-#include "SortedListImpl.h"
-
-namespace android {
-namespace uirenderer {
-
-///////////////////////////////////////////////////////////////////////////////
-// Sorted list
-///////////////////////////////////////////////////////////////////////////////
-
-template<class TYPE>
-class SortedList: private SortedListImpl {
-public:
- typedef TYPE value_type;
-
- SortedList();
- SortedList(const SortedList<TYPE>& rhs);
- virtual ~SortedList();
-
- const SortedList<TYPE>& operator =(const SortedList<TYPE>& rhs) const;
- SortedList<TYPE>& operator =(const SortedList<TYPE>& rhs);
-
- inline void clear() {
- VectorImpl::clear();
- }
-
- inline size_t size() const {
- return VectorImpl::size();
- }
-
- inline bool isEmpty() const {
- return VectorImpl::isEmpty();
- }
-
- inline size_t capacity() const {
- return VectorImpl::capacity();
- }
-
- inline ssize_t setCapacity(size_t size) {
- return VectorImpl::setCapacity(size);
- }
-
- inline const TYPE* array() const;
-
- TYPE* editArray();
-
- ssize_t indexOf(const TYPE& item) const;
- size_t orderOf(const TYPE& item) const;
-
- inline const TYPE& operator [](size_t index) const;
- inline const TYPE& itemAt(size_t index) const;
- const TYPE& top() const;
- const TYPE& mirrorItemAt(ssize_t index) const;
-
- ssize_t add(const TYPE& item);
-
- TYPE& editItemAt(size_t index) {
- return *(static_cast<TYPE *> (VectorImpl::editItemLocation(index)));
- }
-
- ssize_t merge(const Vector<TYPE>& vector);
- ssize_t merge(const SortedList<TYPE>& vector);
-
- ssize_t remove(const TYPE&);
-
- inline ssize_t removeItemsAt(size_t index, size_t count = 1);
- inline ssize_t removeAt(size_t index) {
- return removeItemsAt(index);
- }
-
-protected:
- virtual void do_construct(void* storage, size_t num) const override;
- virtual void do_destroy(void* storage, size_t num) const override;
- virtual void do_copy(void* dest, const void* from, size_t num) const override;
- virtual void do_splat(void* dest, const void* item, size_t num) const override;
- virtual void do_move_forward(void* dest, const void* from, size_t num) const override;
- virtual void do_move_backward(void* dest, const void* from, size_t num) const override;
- virtual int do_compare(const void* lhs, const void* rhs) const override;
-}; // class SortedList
-
-///////////////////////////////////////////////////////////////////////////////
-// Implementation
-///////////////////////////////////////////////////////////////////////////////
-
-template<class TYPE>
-inline SortedList<TYPE>::SortedList():
- SortedListImpl(sizeof(TYPE), ((traits<TYPE>::has_trivial_ctor ? HAS_TRIVIAL_CTOR : 0)
- | (traits<TYPE>::has_trivial_dtor ? HAS_TRIVIAL_DTOR : 0)
- | (traits<TYPE>::has_trivial_copy ? HAS_TRIVIAL_COPY : 0))) {
-}
-
-template<class TYPE>
-inline SortedList<TYPE>::SortedList(const SortedList<TYPE>& rhs): SortedListImpl(rhs) {
-}
-
-template<class TYPE> inline SortedList<TYPE>::~SortedList() {
- finish_vector();
-}
-
-template<class TYPE>
-inline SortedList<TYPE>& SortedList<TYPE>::operator =(const SortedList<TYPE>& rhs) {
- SortedListImpl::operator =(rhs);
- return *this;
-}
-
-template<class TYPE>
-inline const SortedList<TYPE>& SortedList<TYPE>::operator =(
- const SortedList<TYPE>& rhs) const {
- SortedListImpl::operator =(rhs);
- return *this;
-}
-
-template<class TYPE>
-inline const TYPE* SortedList<TYPE>::array() const {
- return static_cast<const TYPE *> (arrayImpl());
-}
-
-template<class TYPE>
-inline TYPE* SortedList<TYPE>::editArray() {
- return static_cast<TYPE *> (editArrayImpl());
-}
-
-template<class TYPE>
-inline const TYPE& SortedList<TYPE>::operator[](size_t index) const {
- assert( index<size() );
- return *(array() + index);
-}
-
-template<class TYPE>
-inline const TYPE& SortedList<TYPE>::itemAt(size_t index) const {
- return operator[](index);
-}
-
-template<class TYPE>
-inline const TYPE& SortedList<TYPE>::mirrorItemAt(ssize_t index) const {
- assert( (index>0 ? index : -index)<size() );
- return *(array() + ((index < 0) ? (size() - index) : index));
-}
-
-template<class TYPE>
-inline const TYPE& SortedList<TYPE>::top() const {
- return *(array() + size() - 1);
-}
-
-template<class TYPE>
-inline ssize_t SortedList<TYPE>::add(const TYPE& item) {
- return SortedListImpl::add(&item);
-}
-
-template<class TYPE>
-inline ssize_t SortedList<TYPE>::indexOf(const TYPE& item) const {
- return SortedListImpl::indexOf(&item);
-}
-
-template<class TYPE>
-inline size_t SortedList<TYPE>::orderOf(const TYPE& item) const {
- return SortedListImpl::orderOf(&item);
-}
-
-template<class TYPE>
-inline ssize_t SortedList<TYPE>::merge(const Vector<TYPE>& vector) {
- return SortedListImpl::merge(reinterpret_cast<const VectorImpl&> (vector));
-}
-
-template<class TYPE>
-inline ssize_t SortedList<TYPE>::merge(const SortedList<TYPE>& vector) {
- return SortedListImpl::merge(reinterpret_cast<const SortedListImpl&> (vector));
-}
-
-template<class TYPE>
-inline ssize_t SortedList<TYPE>::remove(const TYPE& item) {
- return SortedListImpl::remove(&item);
-}
-
-template<class TYPE>
-inline ssize_t SortedList<TYPE>::removeItemsAt(size_t index, size_t count) {
- return VectorImpl::removeItemsAt(index, count);
-}
-
-template<class TYPE>
-void SortedList<TYPE>::do_construct(void* storage, size_t num) const {
- construct_type(reinterpret_cast<TYPE*> (storage), num);
-}
-
-template<class TYPE>
-void SortedList<TYPE>::do_destroy(void* storage, size_t num) const {
- destroy_type(reinterpret_cast<TYPE*> (storage), num);
-}
-
-template<class TYPE>
-void SortedList<TYPE>::do_copy(void* dest, const void* from, size_t num) const {
- copy_type(reinterpret_cast<TYPE*> (dest), reinterpret_cast<const TYPE*> (from), num);
-}
-
-template<class TYPE>
-void SortedList<TYPE>::do_splat(void* dest, const void* item, size_t num) const {
- splat_type(reinterpret_cast<TYPE*> (dest), reinterpret_cast<const TYPE*> (item), num);
-}
-
-template<class TYPE>
-void SortedList<TYPE>::do_move_forward(void* dest, const void* from, size_t num) const {
- move_forward_type(reinterpret_cast<TYPE*> (dest), reinterpret_cast<const TYPE*> (from), num);
-}
-
-template<class TYPE>
-void SortedList<TYPE>::do_move_backward(void* dest, const void* from, size_t num) const {
- move_backward_type(reinterpret_cast<TYPE*> (dest), reinterpret_cast<const TYPE*> (from), num);
-}
-
-template<class TYPE>
-int SortedList<TYPE>::do_compare(const void* lhs, const void* rhs) const {
- return compare_type(*reinterpret_cast<const TYPE*> (lhs), *reinterpret_cast<const TYPE*> (rhs));
-}
-
-}; // namespace uirenderer
-}; // namespace android
-
-#endif // ANDROID_HWUI_SORTED_LIST_H
diff --git a/libs/hwui/utils/SortedListImpl.cpp b/libs/hwui/utils/SortedListImpl.cpp
deleted file mode 100644
index 35171d5b1a5b..000000000000
--- a/libs/hwui/utils/SortedListImpl.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2010 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 "SortedListImpl.h"
-
-namespace android {
-namespace uirenderer {
-
-///////////////////////////////////////////////////////////////////////////////
-// Sorted list implementation, not for direct use
-///////////////////////////////////////////////////////////////////////////////
-
-SortedListImpl::SortedListImpl(size_t itemSize, uint32_t flags): VectorImpl(itemSize, flags) {
-}
-
-SortedListImpl::SortedListImpl(const VectorImpl& rhs): VectorImpl(rhs) {
-}
-
-SortedListImpl::~SortedListImpl() {
-}
-
-SortedListImpl& SortedListImpl::operator =(const SortedListImpl& rhs) {
- return static_cast<SortedListImpl&>
- (VectorImpl::operator =(static_cast<const VectorImpl&> (rhs)));
-}
-
-ssize_t SortedListImpl::indexOf(const void* item) const {
- return _indexOrderOf(item);
-}
-
-size_t SortedListImpl::orderOf(const void* item) const {
- size_t o;
- _indexOrderOf(item, &o);
- return o;
-}
-
-ssize_t SortedListImpl::_indexOrderOf(const void* item, size_t* order) const {
- // binary search
- ssize_t err = NAME_NOT_FOUND;
- ssize_t l = 0;
- ssize_t h = size() - 1;
- ssize_t mid;
- const void* a = arrayImpl();
- const size_t s = itemSize();
- while (l <= h) {
- mid = l + (h - l) / 2;
- const void* const curr = reinterpret_cast<const char *> (a) + (mid * s);
- const int c = do_compare(curr, item);
- if (c == 0) {
- err = l = mid;
- break;
- } else if (c < 0) {
- l = mid + 1;
- } else {
- h = mid - 1;
- }
- }
- if (order) {
- *order = l;
- }
- return err;
-}
-
-ssize_t SortedListImpl::add(const void* item) {
- size_t order;
- ssize_t index = _indexOrderOf(item, &order);
- index = VectorImpl::insertAt(item, order, 1);
- return index;
-}
-
-ssize_t SortedListImpl::merge(const VectorImpl& vector) {
- // naive merge...
- if (!vector.isEmpty()) {
- const void* buffer = vector.arrayImpl();
- const size_t is = itemSize();
- size_t s = vector.size();
- for (size_t i = 0; i < s; i++) {
- ssize_t err = add(reinterpret_cast<const char*> (buffer) + i * is);
- if (err < 0) {
- return err;
- }
- }
- }
- return NO_ERROR;
-}
-
-ssize_t SortedListImpl::merge(const SortedListImpl& vector) {
- // we've merging a sorted vector... nice!
- ssize_t err = NO_ERROR;
- if (!vector.isEmpty()) {
- // first take care of the case where the vectors are sorted together
- if (do_compare(vector.itemLocation(vector.size() - 1), arrayImpl()) <= 0) {
- err = VectorImpl::insertVectorAt(static_cast<const VectorImpl&> (vector), 0);
- } else if (do_compare(vector.arrayImpl(), itemLocation(size() - 1)) >= 0) {
- err = VectorImpl::appendVector(static_cast<const VectorImpl&> (vector));
- } else {
- // this could be made a little better
- err = merge(static_cast<const VectorImpl&> (vector));
- }
- }
- return err;
-}
-
-ssize_t SortedListImpl::remove(const void* item) {
- ssize_t i = indexOf(item);
- if (i >= 0) {
- VectorImpl::removeItemsAt(i, 1);
- }
- return i;
-}
-
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/utils/SortedListImpl.h b/libs/hwui/utils/SortedListImpl.h
deleted file mode 100644
index b1018265566a..000000000000
--- a/libs/hwui/utils/SortedListImpl.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2010 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_SORTED_LIST_IMPL_H
-#define ANDROID_HWUI_SORTED_LIST_IMPL_H
-
-#include <utils/VectorImpl.h>
-
-namespace android {
-namespace uirenderer {
-
-class SortedListImpl: public VectorImpl {
-public:
- SortedListImpl(size_t itemSize, uint32_t flags);
- SortedListImpl(const VectorImpl& rhs);
- virtual ~SortedListImpl();
-
- SortedListImpl& operator =(const SortedListImpl& rhs);
-
- ssize_t indexOf(const void* item) const;
- size_t orderOf(const void* item) const;
- ssize_t add(const void* item);
- ssize_t merge(const VectorImpl& vector);
- ssize_t merge(const SortedListImpl& vector);
- ssize_t remove(const void* item);
-
-protected:
- virtual int do_compare(const void* lhs, const void* rhs) const = 0;
-
-private:
- ssize_t _indexOrderOf(const void* item, size_t* order = nullptr) const;
-
- // these are made private, because they can't be used on a SortedVector
- // (they don't have an implementation either)
- ssize_t add();
- void pop();
- void push();
- void push(const void* item);
- ssize_t insertVectorAt(const VectorImpl& vector, size_t index);
- ssize_t appendVector(const VectorImpl& vector);
- ssize_t insertArrayAt(const void* array, size_t index, size_t length);
- ssize_t appendArray(const void* array, size_t length);
- ssize_t insertAt(size_t where, size_t numItems = 1);
- ssize_t insertAt(const void* item, size_t where, size_t numItems = 1);
- ssize_t replaceAt(size_t index);
- ssize_t replaceAt(const void* item, size_t index);
-};
-
-}; // namespace uirenderer
-}; // namespace android
-
-#endif // ANDROID_HWUI_SORTED_LIST_IMPL_H
diff --git a/libs/hwui/utils/StringUtils.cpp b/libs/hwui/utils/StringUtils.cpp
new file mode 100644
index 000000000000..64a59705028a
--- /dev/null
+++ b/libs/hwui/utils/StringUtils.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 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 "StringUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+unordered_string_set StringUtils::split(const char* spacedList) {
+ unordered_string_set set;
+ const char* current = spacedList;
+ const char* head = current;
+ do {
+ head = strchr(current, ' ');
+ std::string s(current, head ? head - current : strlen(current));
+ if (s.length()) {
+ set.insert(std::move(s));
+ }
+ current = head + 1;
+ } while (head);
+ return set;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/utils/StringUtils.h b/libs/hwui/utils/StringUtils.h
new file mode 100644
index 000000000000..5add95711f2d
--- /dev/null
+++ b/libs/hwui/utils/StringUtils.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 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 STRING_UTILS_H
+#define STRING_UTILS_H
+
+#include <string>
+#include <unordered_set>
+#include <ostream>
+#include <iomanip>
+
+namespace android {
+namespace uirenderer {
+
+class unordered_string_set : public std::unordered_set<std::string> {
+public:
+ bool has(const char* str) {
+ return find(std::string(str)) != end();
+ }
+};
+
+class StringUtils {
+public:
+ static unordered_string_set split(const char* spacedList);
+};
+
+struct SizePrinter {
+ int bytes;
+ friend std::ostream& operator<<(std::ostream& stream, const SizePrinter& d) {
+ static const char* SUFFIXES[] = {"B", "KiB", "MiB"};
+ size_t suffix = 0;
+ double temp = d.bytes;
+ while (temp > 1024 && suffix < 2) {
+ temp /= 1024.0;
+ suffix++;
+ }
+ stream << std::fixed << std::setprecision(2) << temp << SUFFIXES[suffix];
+ return stream;
+ }
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* GLUTILS_H */
diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp
new file mode 100644
index 000000000000..b3195c4bbbfb
--- /dev/null
+++ b/libs/hwui/utils/TestWindowContext.cpp
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2015 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 "TestWindowContext.h"
+
+#include "AnimationContext.h"
+#include "DisplayListCanvas.h"
+#include "IContextFactory.h"
+#include "RecordingCanvas.h"
+#include "RenderNode.h"
+#include "SkTypes.h"
+#include "gui/BufferQueue.h"
+#include "gui/CpuConsumer.h"
+#include "gui/IGraphicBufferConsumer.h"
+#include "gui/IGraphicBufferProducer.h"
+#include "gui/Surface.h"
+#include "renderthread/RenderProxy.h"
+
+
+namespace {
+
+/**
+ * Helper class for setting up android::uirenderer::renderthread::RenderProxy.
+ */
+class ContextFactory : public android::uirenderer::IContextFactory {
+public:
+ android::uirenderer::AnimationContext* createAnimationContext
+ (android::uirenderer::renderthread::TimeLord& clock) override {
+ return new android::uirenderer::AnimationContext(clock);
+ }
+};
+
+} // anonymous namespace
+
+namespace android {
+namespace uirenderer {
+
+/**
+ Android strong pointers (android::sp) can't hold forward-declared classes,
+ so we have to use pointer-to-implementation here if we want to hide the
+ details from our non-framework users.
+*/
+
+class TestWindowContext::TestWindowData {
+
+public:
+
+ TestWindowData(SkISize size) : mSize(size) {
+ android::BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+ mCpuConsumer = new android::CpuConsumer(mConsumer, 1);
+ mCpuConsumer->setName(android::String8("TestWindowContext"));
+ mCpuConsumer->setDefaultBufferSize(mSize.width(), mSize.height());
+ mAndroidSurface = new android::Surface(mProducer);
+ native_window_set_buffers_dimensions(mAndroidSurface.get(),
+ mSize.width(), mSize.height());
+ native_window_set_buffers_format(mAndroidSurface.get(),
+ android::PIXEL_FORMAT_RGBA_8888);
+ native_window_set_usage(mAndroidSurface.get(),
+ GRALLOC_USAGE_SW_READ_OFTEN |
+ GRALLOC_USAGE_SW_WRITE_NEVER |
+ GRALLOC_USAGE_HW_RENDER);
+ mRootNode.reset(new android::uirenderer::RenderNode());
+ mRootNode->incStrong(nullptr);
+ mRootNode->mutateStagingProperties().setLeftTopRightBottom
+ (0, 0, mSize.width(), mSize.height());
+ mRootNode->mutateStagingProperties().setClipToBounds(false);
+ mRootNode->setPropertyFieldsDirty(android::uirenderer::RenderNode::GENERIC);
+ ContextFactory factory;
+ mProxy.reset
+ (new android::uirenderer::renderthread::RenderProxy(false,
+ mRootNode.get(),
+ &factory));
+ mProxy->loadSystemProperties();
+ mProxy->initialize(mAndroidSurface.get());
+ float lightX = mSize.width() / 2.0f;
+ android::uirenderer::Vector3 lightVector { lightX, -200.0f, 800.0f };
+ mProxy->setup(mSize.width(), mSize.height(), 800.0f,
+ 255 * 0.075f, 255 * 0.15f);
+ mProxy->setLightCenter(lightVector);
+#if HWUI_NEW_OPS
+ mCanvas.reset(new android::uirenderer::RecordingCanvas(mSize.width(), mSize.height()));
+#else
+ mCanvas.reset(new android::uirenderer::DisplayListCanvas(mSize.width(), mSize.height()));
+#endif
+ }
+
+ SkCanvas* prepareToDraw() {
+ //mCanvas->reset(mSize.width(), mSize.height());
+ mCanvas->clipRect(0, 0, mSize.width(), mSize.height(),
+ SkRegion::Op::kReplace_Op);
+ return mCanvas->asSkCanvas();
+ }
+
+ void finishDrawing() {
+ mRootNode->setStagingDisplayList(mCanvas->finishRecording(), nullptr);
+ mProxy->syncAndDrawFrame(nullptr);
+ // Surprisingly, calling mProxy->fence() here appears to make no difference to
+ // the timings we record.
+ }
+
+ void fence() {
+ mProxy->fence();
+ }
+
+ bool capturePixels(SkBitmap* bmp) {
+ SkImageInfo destinationConfig =
+ SkImageInfo::Make(mSize.width(), mSize.height(),
+ kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+ bmp->allocPixels(destinationConfig);
+ sk_memset32((uint32_t*) bmp->getPixels(), SK_ColorRED,
+ mSize.width() * mSize.height());
+
+ android::CpuConsumer::LockedBuffer nativeBuffer;
+ android::status_t retval = mCpuConsumer->lockNextBuffer(&nativeBuffer);
+ if (retval == android::BAD_VALUE) {
+ SkDebugf("write_canvas_png() got no buffer; returning transparent");
+ // No buffer ready to read - commonly triggered by dm sending us
+ // a no-op source, or calling code that doesn't do anything on this
+ // backend.
+ bmp->eraseColor(SK_ColorTRANSPARENT);
+ return false;
+ } else if (retval) {
+ SkDebugf("Failed to lock buffer to read pixels: %d.", retval);
+ return false;
+ }
+
+ // Move the pixels into the destination SkBitmap
+
+ LOG_ALWAYS_FATAL_IF(nativeBuffer.format != android::PIXEL_FORMAT_RGBA_8888,
+ "Native buffer not RGBA!");
+ SkImageInfo nativeConfig =
+ SkImageInfo::Make(nativeBuffer.width, nativeBuffer.height,
+ kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+
+ // Android stride is in pixels, Skia stride is in bytes
+ SkBitmap nativeWrapper;
+ bool success =
+ nativeWrapper.installPixels(nativeConfig, nativeBuffer.data, nativeBuffer.stride * 4);
+ if (!success) {
+ SkDebugf("Failed to wrap HWUI buffer in a SkBitmap");
+ return false;
+ }
+
+ LOG_ALWAYS_FATAL_IF(bmp->colorType() != kRGBA_8888_SkColorType,
+ "Destination buffer not RGBA!");
+ success =
+ nativeWrapper.readPixels(destinationConfig, bmp->getPixels(), bmp->rowBytes(), 0, 0);
+ if (!success) {
+ SkDebugf("Failed to extract pixels from HWUI buffer");
+ return false;
+ }
+
+ mCpuConsumer->unlockBuffer(nativeBuffer);
+
+ return true;
+ }
+
+private:
+
+ std::unique_ptr<android::uirenderer::RenderNode> mRootNode;
+ std::unique_ptr<android::uirenderer::renderthread::RenderProxy> mProxy;
+#if HWUI_NEW_OPS
+ std::unique_ptr<android::uirenderer::RecordingCanvas> mCanvas;
+#else
+ std::unique_ptr<android::uirenderer::DisplayListCanvas> mCanvas;
+#endif
+ android::sp<android::IGraphicBufferProducer> mProducer;
+ android::sp<android::IGraphicBufferConsumer> mConsumer;
+ android::sp<android::CpuConsumer> mCpuConsumer;
+ android::sp<android::Surface> mAndroidSurface;
+ SkISize mSize;
+};
+
+
+TestWindowContext::TestWindowContext() :
+ mData (nullptr) { }
+
+TestWindowContext::~TestWindowContext() {
+ delete mData;
+}
+
+void TestWindowContext::initialize(int width, int height) {
+ mData = new TestWindowData(SkISize::Make(width, height));
+}
+
+SkCanvas* TestWindowContext::prepareToDraw() {
+ return mData ? mData->prepareToDraw() : nullptr;
+}
+
+void TestWindowContext::finishDrawing() {
+ if (mData) {
+ mData->finishDrawing();
+ }
+}
+
+void TestWindowContext::fence() {
+ if (mData) {
+ mData->fence();
+ }
+}
+
+bool TestWindowContext::capturePixels(SkBitmap* bmp) {
+ return mData ? mData->capturePixels(bmp) : false;
+}
+
+} // namespace uirenderer
+} // namespace android
+
diff --git a/libs/hwui/utils/TestWindowContext.h b/libs/hwui/utils/TestWindowContext.h
new file mode 100644
index 000000000000..48ec95216b14
--- /dev/null
+++ b/libs/hwui/utils/TestWindowContext.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 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 TESTWINDOWCONTEXT_H_
+#define TESTWINDOWCONTEXT_H_
+
+#include <cutils/compiler.h>
+
+class SkBitmap;
+class SkCanvas;
+
+namespace android {
+
+namespace uirenderer {
+
+/**
+ Wraps all libui/libgui classes and types that external tests depend on,
+ exposing only primitive Skia types.
+*/
+
+class ANDROID_API TestWindowContext {
+
+public:
+
+ TestWindowContext();
+ ~TestWindowContext();
+
+ /// We need to know the size of the window.
+ void initialize(int width, int height);
+
+ /// Returns a canvas to draw into; NULL if not yet initialize()d.
+ SkCanvas* prepareToDraw();
+
+ /// Flushes all drawing commands to HWUI; no-op if not yet initialize()d.
+ void finishDrawing();
+
+ /// Blocks until HWUI has processed all pending drawing commands;
+ /// no-op if not yet initialize()d.
+ void fence();
+
+ /// Returns false if not yet initialize()d.
+ bool capturePixels(SkBitmap* bmp);
+
+private:
+ /// Hidden implementation.
+ class TestWindowData;
+
+ TestWindowData* mData;
+
+};
+
+} // namespace uirenderer
+} // namespace android
+
+#endif // TESTWINDOWCONTEXT_H_
+
diff --git a/libs/hwui/utils/TimeUtils.h b/libs/hwui/utils/TimeUtils.h
new file mode 100644
index 000000000000..8d42d7e55521
--- /dev/null
+++ b/libs/hwui/utils/TimeUtils.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 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 UTILS_TIMEUTILS_H
+#define UTILS_TIMEUTILS_H
+
+#include <utils/Timers.h>
+
+namespace android {
+namespace uirenderer {
+
+constexpr nsecs_t operator"" _ms (unsigned long long ms) {
+ return milliseconds_to_nanoseconds(ms);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* UTILS_TIMEUTILS_H */
diff --git a/libs/hwui/utils/TinyHashMap.h b/libs/hwui/utils/TinyHashMap.h
deleted file mode 100644
index 4ff9a42f6d5d..000000000000
--- a/libs/hwui/utils/TinyHashMap.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2013 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_TINYHASHMAP_H
-#define ANDROID_HWUI_TINYHASHMAP_H
-
-#include <utils/BasicHashtable.h>
-
-namespace android {
-namespace uirenderer {
-
-/**
- * A very simple hash map that doesn't allow duplicate keys, overwriting the older entry.
- */
-template <typename TKey, typename TValue>
-class TinyHashMap {
-public:
- typedef key_value_pair_t<TKey, TValue> TEntry;
-
- /**
- * Puts an entry in the hash, removing any existing entry with the same key
- */
- void put(TKey key, TValue value) {
- hash_t hash = android::hash_type(key);
-
- ssize_t index = mTable.find(-1, hash, key);
- if (index != -1) {
- mTable.removeAt(index);
- }
-
- TEntry initEntry(key, value);
- mTable.add(hash, initEntry);
- }
-
- /**
- * Return true if key is in the map, in which case stores the value in the output ref
- */
- bool get(TKey key, TValue& outValue) {
- hash_t hash = android::hash_type(key);
- ssize_t index = mTable.find(-1, hash, key);
- if (index == -1) {
- return false;
- }
- outValue = mTable.entryAt(index).value;
- return true;
- }
-
- void clear() { mTable.clear(); }
-
-private:
- BasicHashtable<TKey, TEntry> mTable;
-};
-
-}; // namespace uirenderer
-}; // namespace android
-
-#endif // ANDROID_HWUI_TINYHASHMAP_H
diff --git a/libs/hwui/utils/VectorDrawableUtils.cpp b/libs/hwui/utils/VectorDrawableUtils.cpp
new file mode 100644
index 000000000000..ca75c5945b7f
--- /dev/null
+++ b/libs/hwui/utils/VectorDrawableUtils.cpp
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2015 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 "VectorDrawableUtils.h"
+
+#include "PathParser.h"
+
+#include <math.h>
+#include <utils/Log.h>
+
+namespace android {
+namespace uirenderer {
+
+class PathResolver {
+public:
+ float currentX = 0;
+ float currentY = 0;
+ float ctrlPointX = 0;
+ float ctrlPointY = 0;
+ float currentSegmentStartX = 0;
+ float currentSegmentStartY = 0;
+ void addCommand(SkPath* outPath, char previousCmd,
+ char cmd, const std::vector<float>* points, size_t start, size_t end);
+};
+
+bool VectorDrawableUtils::canMorph(const PathData& morphFrom, const PathData& morphTo) {
+ if (morphFrom.verbs.size() != morphTo.verbs.size()) {
+ return false;
+ }
+
+ for (unsigned int i = 0; i < morphFrom.verbs.size(); i++) {
+ if (morphFrom.verbs[i] != morphTo.verbs[i]
+ || morphFrom.verbSizes[i] != morphTo.verbSizes[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool VectorDrawableUtils::interpolatePathData(PathData* outData, const PathData& morphFrom,
+ const PathData& morphTo, float fraction) {
+ if (!canMorph(morphFrom, morphTo)) {
+ return false;
+ }
+ interpolatePaths(outData, morphFrom, morphTo, fraction);
+ return true;
+}
+
+ /**
+ * Convert an array of PathVerb to Path.
+ */
+void VectorDrawableUtils::verbsToPath(SkPath* outPath, const PathData& data) {
+ PathResolver resolver;
+ char previousCommand = 'm';
+ size_t start = 0;
+ outPath->reset();
+ for (unsigned int i = 0; i < data.verbs.size(); i++) {
+ size_t verbSize = data.verbSizes[i];
+ resolver.addCommand(outPath, previousCommand, data.verbs[i], &data.points, start,
+ start + verbSize);
+ previousCommand = data.verbs[i];
+ start += verbSize;
+ }
+}
+
+/**
+ * The current PathVerb will be interpolated between the
+ * <code>nodeFrom</code> and <code>nodeTo</code> according to the
+ * <code>fraction</code>.
+ *
+ * @param nodeFrom The start value as a PathVerb.
+ * @param nodeTo The end value as a PathVerb
+ * @param fraction The fraction to interpolate.
+ */
+void VectorDrawableUtils::interpolatePaths(PathData* outData,
+ const PathData& from, const PathData& to, float fraction) {
+ outData->points.resize(from.points.size());
+ outData->verbSizes = from.verbSizes;
+ outData->verbs = from.verbs;
+
+ for (size_t i = 0; i < from.points.size(); i++) {
+ outData->points[i] = from.points[i] * (1 - fraction) + to.points[i] * fraction;
+ }
+}
+
+/**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+static void arcToBezier(SkPath* p,
+ double cx,
+ double cy,
+ double a,
+ double b,
+ double e1x,
+ double e1y,
+ double theta,
+ double start,
+ double sweep) {
+ // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+ // and http://www.spaceroots.org/documents/ellipse/node22.html
+
+ // Maximum of 45 degrees per cubic Bezier segment
+ int numSegments = ceil(fabs(sweep * 4 / M_PI));
+
+ double eta1 = start;
+ double cosTheta = cos(theta);
+ double sinTheta = sin(theta);
+ double cosEta1 = cos(eta1);
+ double sinEta1 = sin(eta1);
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+ double anglePerSegment = sweep / numSegments;
+ for (int i = 0; i < numSegments; i++) {
+ double eta2 = eta1 + anglePerSegment;
+ double sinEta2 = sin(eta2);
+ double cosEta2 = cos(eta2);
+ double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+ double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+ double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+ double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+ double tanDiff2 = tan((eta2 - eta1) / 2);
+ double alpha =
+ sin(eta2 - eta1) * (sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+ double q1x = e1x + alpha * ep1x;
+ double q1y = e1y + alpha * ep1y;
+ double q2x = e2x - alpha * ep2x;
+ double q2y = e2y - alpha * ep2y;
+
+ p->cubicTo((float) q1x,
+ (float) q1y,
+ (float) q2x,
+ (float) q2y,
+ (float) e2x,
+ (float) e2y);
+ eta1 = eta2;
+ e1x = e2x;
+ e1y = e2y;
+ ep1x = ep2x;
+ ep1y = ep2y;
+ }
+}
+
+inline double toRadians(float theta) { return theta * M_PI / 180;}
+
+static void drawArc(SkPath* p,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ float a,
+ float b,
+ float theta,
+ bool isMoreThanHalf,
+ bool isPositiveArc) {
+
+ /* Convert rotation angle from degrees to radians */
+ double thetaD = toRadians(theta);
+ /* Pre-compute rotation matrix entries */
+ double cosTheta = cos(thetaD);
+ double sinTheta = sin(thetaD);
+ /* Transform (x0, y0) and (x1, y1) into unit space */
+ /* using (inverse) rotation, followed by (inverse) scale */
+ double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+ double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+ double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+ double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+
+ /* Compute differences and averages */
+ double dx = x0p - x1p;
+ double dy = y0p - y1p;
+ double xm = (x0p + x1p) / 2;
+ double ym = (y0p + y1p) / 2;
+ /* Solve for intersecting unit circles */
+ double dsq = dx * dx + dy * dy;
+ if (dsq == 0.0) {
+ ALOGW("Points are coincident");
+ return; /* Points are coincident */
+ }
+ double disc = 1.0 / dsq - 1.0 / 4.0;
+ if (disc < 0.0) {
+ ALOGW("Points are too far apart %f", dsq);
+ float adjust = (float) (sqrt(dsq) / 1.99999);
+ drawArc(p, x0, y0, x1, y1, a * adjust,
+ b * adjust, theta, isMoreThanHalf, isPositiveArc);
+ return; /* Points are too far apart */
+ }
+ double s = sqrt(disc);
+ double sdx = s * dx;
+ double sdy = s * dy;
+ double cx;
+ double cy;
+ if (isMoreThanHalf == isPositiveArc) {
+ cx = xm - sdy;
+ cy = ym + sdx;
+ } else {
+ cx = xm + sdy;
+ cy = ym - sdx;
+ }
+
+ double eta0 = atan2((y0p - cy), (x0p - cx));
+
+ double eta1 = atan2((y1p - cy), (x1p - cx));
+
+ double sweep = (eta1 - eta0);
+ if (isPositiveArc != (sweep >= 0)) {
+ if (sweep > 0) {
+ sweep -= 2 * M_PI;
+ } else {
+ sweep += 2 * M_PI;
+ }
+ }
+
+ cx *= a;
+ cy *= b;
+ double tcx = cx;
+ cx = cx * cosTheta - cy * sinTheta;
+ cy = tcx * sinTheta + cy * cosTheta;
+
+ arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+}
+
+
+
+// Use the given verb, and points in the range [start, end) to insert a command into the SkPath.
+void PathResolver::addCommand(SkPath* outPath, char previousCmd,
+ char cmd, const std::vector<float>* points, size_t start, size_t end) {
+
+ int incr = 2;
+ float reflectiveCtrlPointX;
+ float reflectiveCtrlPointY;
+
+ switch (cmd) {
+ case 'z':
+ case 'Z':
+ outPath->close();
+ // Path is closed here, but we need to move the pen to the
+ // closed position. So we cache the segment's starting position,
+ // and restore it here.
+ currentX = currentSegmentStartX;
+ currentY = currentSegmentStartY;
+ ctrlPointX = currentSegmentStartX;
+ ctrlPointY = currentSegmentStartY;
+ outPath->moveTo(currentX, currentY);
+ break;
+ case 'm':
+ case 'M':
+ case 'l':
+ case 'L':
+ case 't':
+ case 'T':
+ incr = 2;
+ break;
+ case 'h':
+ case 'H':
+ case 'v':
+ case 'V':
+ incr = 1;
+ break;
+ case 'c':
+ case 'C':
+ incr = 6;
+ break;
+ case 's':
+ case 'S':
+ case 'q':
+ case 'Q':
+ incr = 4;
+ break;
+ case 'a':
+ case 'A':
+ incr = 7;
+ break;
+ }
+
+ for (unsigned int k = start; k < end; k += incr) {
+ switch (cmd) {
+ case 'm': // moveto - Start a new sub-path (relative)
+ currentX += points->at(k + 0);
+ currentY += points->at(k + 1);
+ if (k > start) {
+ // According to the spec, if a moveto is followed by multiple
+ // pairs of coordinates, the subsequent pairs are treated as
+ // implicit lineto commands.
+ outPath->rLineTo(points->at(k + 0), points->at(k + 1));
+ } else {
+ outPath->rMoveTo(points->at(k + 0), points->at(k + 1));
+ currentSegmentStartX = currentX;
+ currentSegmentStartY = currentY;
+ }
+ break;
+ case 'M': // moveto - Start a new sub-path
+ currentX = points->at(k + 0);
+ currentY = points->at(k + 1);
+ if (k > start) {
+ // According to the spec, if a moveto is followed by multiple
+ // pairs of coordinates, the subsequent pairs are treated as
+ // implicit lineto commands.
+ outPath->lineTo(points->at(k + 0), points->at(k + 1));
+ } else {
+ outPath->moveTo(points->at(k + 0), points->at(k + 1));
+ currentSegmentStartX = currentX;
+ currentSegmentStartY = currentY;
+ }
+ break;
+ case 'l': // lineto - Draw a line from the current point (relative)
+ outPath->rLineTo(points->at(k + 0), points->at(k + 1));
+ currentX += points->at(k + 0);
+ currentY += points->at(k + 1);
+ break;
+ case 'L': // lineto - Draw a line from the current point
+ outPath->lineTo(points->at(k + 0), points->at(k + 1));
+ currentX = points->at(k + 0);
+ currentY = points->at(k + 1);
+ break;
+ case 'h': // horizontal lineto - Draws a horizontal line (relative)
+ outPath->rLineTo(points->at(k + 0), 0);
+ currentX += points->at(k + 0);
+ break;
+ case 'H': // horizontal lineto - Draws a horizontal line
+ outPath->lineTo(points->at(k + 0), currentY);
+ currentX = points->at(k + 0);
+ break;
+ case 'v': // vertical lineto - Draws a vertical line from the current point (r)
+ outPath->rLineTo(0, points->at(k + 0));
+ currentY += points->at(k + 0);
+ break;
+ case 'V': // vertical lineto - Draws a vertical line from the current point
+ outPath->lineTo(currentX, points->at(k + 0));
+ currentY = points->at(k + 0);
+ break;
+ case 'c': // curveto - Draws a cubic Bézier curve (relative)
+ outPath->rCubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
+ points->at(k + 4), points->at(k + 5));
+
+ ctrlPointX = currentX + points->at(k + 2);
+ ctrlPointY = currentY + points->at(k + 3);
+ currentX += points->at(k + 4);
+ currentY += points->at(k + 5);
+
+ break;
+ case 'C': // curveto - Draws a cubic Bézier curve
+ outPath->cubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
+ points->at(k + 4), points->at(k + 5));
+ currentX = points->at(k + 4);
+ currentY = points->at(k + 5);
+ ctrlPointX = points->at(k + 2);
+ ctrlPointY = points->at(k + 3);
+ break;
+ case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
+ reflectiveCtrlPointX = 0;
+ reflectiveCtrlPointY = 0;
+ if (previousCmd == 'c' || previousCmd == 's'
+ || previousCmd == 'C' || previousCmd == 'S') {
+ reflectiveCtrlPointX = currentX - ctrlPointX;
+ reflectiveCtrlPointY = currentY - ctrlPointY;
+ }
+ outPath->rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ points->at(k + 0), points->at(k + 1),
+ points->at(k + 2), points->at(k + 3));
+ ctrlPointX = currentX + points->at(k + 0);
+ ctrlPointY = currentY + points->at(k + 1);
+ currentX += points->at(k + 2);
+ currentY += points->at(k + 3);
+ break;
+ case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
+ reflectiveCtrlPointX = currentX;
+ reflectiveCtrlPointY = currentY;
+ if (previousCmd == 'c' || previousCmd == 's'
+ || previousCmd == 'C' || previousCmd == 'S') {
+ reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+ reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+ }
+ outPath->cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
+ ctrlPointX = points->at(k + 0);
+ ctrlPointY = points->at(k + 1);
+ currentX = points->at(k + 2);
+ currentY = points->at(k + 3);
+ break;
+ case 'q': // Draws a quadratic Bézier (relative)
+ outPath->rQuadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
+ ctrlPointX = currentX + points->at(k + 0);
+ ctrlPointY = currentY + points->at(k + 1);
+ currentX += points->at(k + 2);
+ currentY += points->at(k + 3);
+ break;
+ case 'Q': // Draws a quadratic Bézier
+ outPath->quadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
+ ctrlPointX = points->at(k + 0);
+ ctrlPointY = points->at(k + 1);
+ currentX = points->at(k + 2);
+ currentY = points->at(k + 3);
+ break;
+ case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
+ reflectiveCtrlPointX = 0;
+ reflectiveCtrlPointY = 0;
+ if (previousCmd == 'q' || previousCmd == 't'
+ || previousCmd == 'Q' || previousCmd == 'T') {
+ reflectiveCtrlPointX = currentX - ctrlPointX;
+ reflectiveCtrlPointY = currentY - ctrlPointY;
+ }
+ outPath->rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ points->at(k + 0), points->at(k + 1));
+ ctrlPointX = currentX + reflectiveCtrlPointX;
+ ctrlPointY = currentY + reflectiveCtrlPointY;
+ currentX += points->at(k + 0);
+ currentY += points->at(k + 1);
+ break;
+ case 'T': // Draws a quadratic Bézier curve (reflective control point)
+ reflectiveCtrlPointX = currentX;
+ reflectiveCtrlPointY = currentY;
+ if (previousCmd == 'q' || previousCmd == 't'
+ || previousCmd == 'Q' || previousCmd == 'T') {
+ reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+ reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+ }
+ outPath->quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ points->at(k + 0), points->at(k + 1));
+ ctrlPointX = reflectiveCtrlPointX;
+ ctrlPointY = reflectiveCtrlPointY;
+ currentX = points->at(k + 0);
+ currentY = points->at(k + 1);
+ break;
+ case 'a': // Draws an elliptical arc
+ // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+ drawArc(outPath,
+ currentX,
+ currentY,
+ points->at(k + 5) + currentX,
+ points->at(k + 6) + currentY,
+ points->at(k + 0),
+ points->at(k + 1),
+ points->at(k + 2),
+ points->at(k + 3) != 0,
+ points->at(k + 4) != 0);
+ currentX += points->at(k + 5);
+ currentY += points->at(k + 6);
+ ctrlPointX = currentX;
+ ctrlPointY = currentY;
+ break;
+ case 'A': // Draws an elliptical arc
+ drawArc(outPath,
+ currentX,
+ currentY,
+ points->at(k + 5),
+ points->at(k + 6),
+ points->at(k + 0),
+ points->at(k + 1),
+ points->at(k + 2),
+ points->at(k + 3) != 0,
+ points->at(k + 4) != 0);
+ currentX = points->at(k + 5);
+ currentY = points->at(k + 6);
+ ctrlPointX = currentX;
+ ctrlPointY = currentY;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Unsupported command: %c", cmd);
+ break;
+ }
+ previousCmd = cmd;
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/utils/VectorDrawableUtils.h b/libs/hwui/utils/VectorDrawableUtils.h
new file mode 100644
index 000000000000..b5ef5102d219
--- /dev/null
+++ b/libs/hwui/utils/VectorDrawableUtils.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 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_VECTORDRAWABLE_UTILS_H
+#define ANDROID_HWUI_VECTORDRAWABLE_UTILS_H
+
+#include "VectorDrawable.h"
+
+#include <cutils/compiler.h>
+#include "SkPath.h"
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+class VectorDrawableUtils {
+public:
+ ANDROID_API static bool canMorph(const PathData& morphFrom, const PathData& morphTo);
+ ANDROID_API static bool interpolatePathData(PathData* outData, const PathData& morphFrom,
+ const PathData& morphTo, float fraction);
+ ANDROID_API static void verbsToPath(SkPath* outPath, const PathData& data);
+ static void interpolatePaths(PathData* outPathData, const PathData& from, const PathData& to,
+ float fraction);
+};
+} // namespace uirenderer
+} // namespace android
+#endif /* ANDROID_HWUI_VECTORDRAWABLE_UTILS_H*/
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 11527378f586..212c6a0d2c3c 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -42,15 +42,14 @@ namespace android {
static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
-// Time to wait between animation frames.
-static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60;
-
// Time to spend fading out the spot completely.
static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
// Time to spend fading out the pointer completely.
static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
+// The number of events to be read at once for DisplayEventReceiver.
+static const int EVENT_BUFFER_SIZE = 100;
// --- PointerController ---
@@ -59,6 +58,13 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>&
mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
mHandler = new WeakMessageHandler(this);
+ if (mDisplayEventReceiver.initCheck() == NO_ERROR) {
+ mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK,
+ Looper::EVENT_INPUT, this, nullptr);
+ } else {
+ ALOGE("Failed to initialize DisplayEventReceiver.");
+ }
+
AutoMutex _l(mLock);
mLocked.animationPending = false;
@@ -78,10 +84,22 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>&
mLocked.pointerAlpha = 0.0f; // pointer is initially faded
mLocked.pointerSprite = mSpriteController->createSprite();
mLocked.pointerIconChanged = false;
+ mLocked.requestedPointerShape = mPolicy->getDefaultPointerIconId();
+
+ mLocked.animationFrameIndex = 0;
+ mLocked.lastFrameUpdatedTime = 0;
mLocked.buttonState = 0;
+ mLocked.iconDetached = false;
+
+ mPolicy->loadPointerIcon(&mLocked.pointerIcon);
loadResources();
+
+ if (mLocked.pointerIcon.isValid()) {
+ mLocked.pointerIconChanged = true;
+ updatePointerLocked();
+ }
}
PointerController::~PointerController() {
@@ -167,6 +185,10 @@ void PointerController::setPosition(float x, float y) {
}
void PointerController::setPositionLocked(float x, float y) {
+ if (mLocked.iconDetached) {
+ return;
+ }
+
float minX, minY, maxX, maxY;
if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
if (x <= minX) {
@@ -200,6 +222,10 @@ void PointerController::fade(Transition transition) {
// Remove the inactivity timeout, since we are fading now.
removeInactivityTimeoutLocked();
+ if (mLocked.iconDetached) {
+ return;
+ }
+
// Start fading.
if (transition == TRANSITION_IMMEDIATE) {
mLocked.pointerFadeDirection = 0;
@@ -217,6 +243,10 @@ void PointerController::unfade(Transition transition) {
// Always reset the inactivity timer.
resetInactivityTimeoutLocked();
+ if (mLocked.iconDetached) {
+ return;
+ }
+
// Start unfading.
if (transition == TRANSITION_IMMEDIATE) {
mLocked.pointerFadeDirection = 0;
@@ -231,6 +261,11 @@ void PointerController::unfade(Transition transition) {
void PointerController::setPresentation(Presentation presentation) {
AutoMutex _l(mLock);
+ if (presentation == PRESENTATION_POINTER && mLocked.additionalMouseResources.empty()) {
+ mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
+ &mLocked.animationResources);
+ }
+
if (mLocked.presentation != presentation) {
mLocked.presentation = presentation;
mLocked.presentationChanged = true;
@@ -310,6 +345,39 @@ void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout
}
}
+void PointerController::reloadPointerResources() {
+ AutoMutex _l(mLock);
+
+ loadResources();
+
+ if (mLocked.presentation == PRESENTATION_POINTER) {
+ mLocked.additionalMouseResources.clear();
+ mLocked.animationResources.clear();
+ mPolicy->loadPointerIcon(&mLocked.pointerIcon);
+ mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
+ &mLocked.animationResources);
+ }
+
+ mLocked.presentationChanged = true;
+ updatePointerLocked();
+}
+
+void PointerController::detachPointerIcon(bool detached) {
+ AutoMutex _l(mLock);
+
+ if (mLocked.iconDetached == detached) {
+ return;
+ }
+
+ mLocked.iconDetached = detached;
+ if (detached) {
+ mLocked.pointerFadeDirection = -1;
+ } else {
+ mLocked.pointerFadeDirection = 1;
+ }
+ startAnimationLocked();
+}
+
void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) {
AutoMutex _l(mLock);
@@ -391,32 +459,80 @@ void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_
updatePointerLocked();
}
-void PointerController::setPointerIcon(const SpriteIcon& icon) {
+void PointerController::updatePointerShape(int32_t iconId) {
+ AutoMutex _l(mLock);
+ if (mLocked.requestedPointerShape != iconId) {
+ mLocked.requestedPointerShape = iconId;
+ mLocked.presentationChanged = true;
+ updatePointerLocked();
+ }
+}
+
+void PointerController::setCustomPointerIcon(const SpriteIcon& icon) {
AutoMutex _l(mLock);
- mLocked.pointerIcon = icon.copy();
- mLocked.pointerIconChanged = true;
+ const int32_t iconId = mPolicy->getCustomPointerIconId();
+ mLocked.additionalMouseResources[iconId] = icon;
+ mLocked.requestedPointerShape = iconId;
+ mLocked.presentationChanged = true;
updatePointerLocked();
}
void PointerController::handleMessage(const Message& message) {
switch (message.what) {
- case MSG_ANIMATE:
- doAnimate();
- break;
case MSG_INACTIVITY_TIMEOUT:
doInactivityTimeout();
break;
}
}
-void PointerController::doAnimate() {
+int PointerController::handleEvent(int /* fd */, int events, void* /* data */) {
+ if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+ ALOGE("Display event receiver pipe was closed or an error occurred. "
+ "events=0x%x", events);
+ return 0; // remove the callback
+ }
+
+ if (!(events & Looper::EVENT_INPUT)) {
+ ALOGW("Received spurious callback for unhandled poll event. "
+ "events=0x%x", events);
+ return 1; // keep the callback
+ }
+
+ bool gotVsync = false;
+ ssize_t n;
+ nsecs_t timestamp;
+ DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+ while ((n = mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+ for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
+ if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
+ timestamp = buf[i].header.timestamp;
+ gotVsync = true;
+ }
+ }
+ }
+ if (gotVsync) {
+ doAnimate(timestamp);
+ }
+ return 1; // keep the callback
+}
+
+void PointerController::doAnimate(nsecs_t timestamp) {
AutoMutex _l(mLock);
- bool keepAnimating = false;
mLocked.animationPending = false;
- nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime;
+
+ bool keepFading = doFadingAnimationLocked(timestamp);
+ bool keepBitmapFlipping = doBitmapAnimationLocked(timestamp);
+ if (keepFading || keepBitmapFlipping) {
+ startAnimationLocked();
+ }
+}
+
+bool PointerController::doFadingAnimationLocked(nsecs_t timestamp) {
+ bool keepAnimating = false;
+ nsecs_t frameDelay = timestamp - mLocked.animationTime;
// Animate pointer fade.
if (mLocked.pointerFadeDirection < 0) {
@@ -453,10 +569,32 @@ void PointerController::doAnimate() {
}
}
}
+ return keepAnimating;
+}
- if (keepAnimating) {
- startAnimationLocked();
+bool PointerController::doBitmapAnimationLocked(nsecs_t timestamp) {
+ std::map<int32_t, PointerAnimation>::const_iterator iter = mLocked.animationResources.find(
+ mLocked.requestedPointerShape);
+ if (iter == mLocked.animationResources.end()) {
+ return false;
}
+
+ if (timestamp - mLocked.lastFrameUpdatedTime > iter->second.durationPerFrame) {
+ mSpriteController->openTransaction();
+
+ int incr = (timestamp - mLocked.lastFrameUpdatedTime) / iter->second.durationPerFrame;
+ mLocked.animationFrameIndex += incr;
+ mLocked.lastFrameUpdatedTime += iter->second.durationPerFrame * incr;
+ while (mLocked.animationFrameIndex >= iter->second.animationFrames.size()) {
+ mLocked.animationFrameIndex -= iter->second.animationFrames.size();
+ }
+ mLocked.pointerSprite->setIcon(iter->second.animationFrames[mLocked.animationFrameIndex]);
+
+ mSpriteController->closeTransaction();
+ }
+
+ // Keep animating.
+ return true;
}
void PointerController::doInactivityTimeout() {
@@ -467,7 +605,7 @@ void PointerController::startAnimationLocked() {
if (!mLocked.animationPending) {
mLocked.animationPending = true;
mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
- mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE));
+ mDisplayEventReceiver.requestNextVsync();
}
}
@@ -497,8 +635,29 @@ void PointerController::updatePointerLocked() {
}
if (mLocked.pointerIconChanged || mLocked.presentationChanged) {
- mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER
- ? mLocked.pointerIcon : mResources.spotAnchor);
+ if (mLocked.presentation == PRESENTATION_POINTER) {
+ if (mLocked.requestedPointerShape == mPolicy->getDefaultPointerIconId()) {
+ mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
+ } else {
+ std::map<int32_t, SpriteIcon>::const_iterator iter =
+ mLocked.additionalMouseResources.find(mLocked.requestedPointerShape);
+ if (iter != mLocked.additionalMouseResources.end()) {
+ std::map<int32_t, PointerAnimation>::const_iterator anim_iter =
+ mLocked.animationResources.find(mLocked.requestedPointerShape);
+ if (anim_iter != mLocked.animationResources.end()) {
+ mLocked.animationFrameIndex = 0;
+ mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ startAnimationLocked();
+ }
+ mLocked.pointerSprite->setIcon(iter->second);
+ } else {
+ ALOGW("Can't find the resource for icon id %d", mLocked.requestedPointerShape);
+ mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
+ }
+ }
+ } else {
+ mLocked.pointerSprite->setIcon(mResources.spotAnchor);
+ }
mLocked.pointerIconChanged = false;
mLocked.presentationChanged = false;
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index b9e4ce7e7ed0..c1381f32a37f 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -19,6 +19,9 @@
#include "SpriteController.h"
+#include <map>
+#include <vector>
+
#include <ui/DisplayInfo.h>
#include <input/Input.h>
#include <inputflinger/PointerControllerInterface.h>
@@ -26,8 +29,7 @@
#include <utils/RefBase.h>
#include <utils/Looper.h>
#include <utils/String8.h>
-
-#include <SkBitmap.h>
+#include <gui/DisplayEventReceiver.h>
namespace android {
@@ -40,6 +42,10 @@ struct PointerResources {
SpriteIcon spotAnchor;
};
+struct PointerAnimation {
+ std::vector<SpriteIcon> animationFrames;
+ nsecs_t durationPerFrame;
+};
/*
* Pointer controller policy interface.
@@ -56,7 +62,12 @@ protected:
virtual ~PointerControllerPolicyInterface() { }
public:
+ virtual void loadPointerIcon(SpriteIcon* icon) = 0;
virtual void loadPointerResources(PointerResources* outResources) = 0;
+ virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources,
+ std::map<int32_t, PointerAnimation>* outAnimationResources) = 0;
+ virtual int32_t getDefaultPointerIconId() = 0;
+ virtual int32_t getCustomPointerIconId() = 0;
};
@@ -65,7 +76,8 @@ public:
*
* Handles pointer acceleration and animation.
*/
-class PointerController : public PointerControllerInterface, public MessageHandler {
+class PointerController : public PointerControllerInterface, public MessageHandler,
+ public LooperCallback {
protected:
virtual ~PointerController();
@@ -93,16 +105,21 @@ public:
const uint32_t* spotIdToIndex, BitSet32 spotIdBits);
virtual void clearSpots();
+ void updatePointerShape(int32_t iconId);
+ void setCustomPointerIcon(const SpriteIcon& icon);
void setDisplayViewport(int32_t width, int32_t height, int32_t orientation);
- void setPointerIcon(const SpriteIcon& icon);
void setInactivityTimeout(InactivityTimeout inactivityTimeout);
+ void reloadPointerResources();
+
+ /* Detach or attach the pointer icon status. When detached, the pointer icon disappears
+ * and the icon location does not change at all. */
+ void detachPointerIcon(bool detached);
private:
static const size_t MAX_RECYCLED_SPRITES = 12;
static const size_t MAX_SPOTS = 12;
enum {
- MSG_ANIMATE,
MSG_INACTIVITY_TIMEOUT,
};
@@ -132,12 +149,17 @@ private:
sp<SpriteController> mSpriteController;
sp<WeakMessageHandler> mHandler;
+ DisplayEventReceiver mDisplayEventReceiver;
+
PointerResources mResources;
struct Locked {
bool animationPending;
nsecs_t animationTime;
+ size_t animationFrameIndex;
+ nsecs_t lastFrameUpdatedTime;
+
int32_t displayWidth;
int32_t displayHeight;
int32_t displayOrientation;
@@ -155,8 +177,15 @@ private:
SpriteIcon pointerIcon;
bool pointerIconChanged;
+ std::map<int32_t, SpriteIcon> additionalMouseResources;
+ std::map<int32_t, PointerAnimation> animationResources;
+
+ int32_t requestedPointerShape;
+
int32_t buttonState;
+ bool iconDetached;
+
Vector<Spot*> spots;
Vector<sp<Sprite> > recycledSprites;
} mLocked;
@@ -165,7 +194,10 @@ private:
void setPositionLocked(float x, float y);
void handleMessage(const Message& message);
- void doAnimate();
+ int handleEvent(int fd, int events, void* data);
+ void doAnimate(nsecs_t timestamp);
+ bool doFadingAnimationLocked(nsecs_t timestamp);
+ bool doBitmapAnimationLocked(nsecs_t timestamp);
void doInactivityTimeout();
void startAnimationLocked();