summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/androidfw/BackupHelpers.cpp2
-rw-r--r--libs/androidfw/Input.cpp4
-rw-r--r--libs/androidfw/InputDevice.cpp4
-rw-r--r--libs/androidfw/InputTransport.cpp5
-rw-r--r--libs/diskusage/Android.mk24
-rw-r--r--libs/diskusage/MODULE_LICENSE_APACHE20
-rw-r--r--libs/diskusage/dirsize.c74
-rw-r--r--libs/hwui/Android.mk21
-rw-r--r--libs/hwui/Caches.cpp93
-rw-r--r--libs/hwui/Caches.h47
-rw-r--r--libs/hwui/Debug.h15
-rw-r--r--libs/hwui/DeferredDisplayList.cpp406
-rw-r--r--libs/hwui/DeferredDisplayList.h106
-rw-r--r--libs/hwui/DisplayList.cpp524
-rw-r--r--libs/hwui/DisplayList.h543
-rw-r--r--libs/hwui/DisplayListLogBuffer.cpp39
-rw-r--r--libs/hwui/DisplayListLogBuffer.h18
-rw-r--r--libs/hwui/DisplayListOp.h1321
-rw-r--r--libs/hwui/DisplayListRenderer.cpp1701
-rw-r--r--libs/hwui/DisplayListRenderer.h685
-rw-r--r--libs/hwui/Dither.cpp49
-rw-r--r--libs/hwui/Dither.h12
-rw-r--r--libs/hwui/Extensions.cpp110
-rw-r--r--libs/hwui/Extensions.h68
-rw-r--r--libs/hwui/FontRenderer.cpp522
-rw-r--r--libs/hwui/FontRenderer.h51
-rw-r--r--libs/hwui/GradientCache.cpp176
-rw-r--r--libs/hwui/GradientCache.h73
-rw-r--r--libs/hwui/Layer.cpp133
-rw-r--r--libs/hwui/Layer.h105
-rw-r--r--libs/hwui/LayerCache.cpp34
-rw-r--r--libs/hwui/LayerCache.h43
-rw-r--r--libs/hwui/LayerRenderer.cpp76
-rw-r--r--libs/hwui/LayerRenderer.h9
-rw-r--r--libs/hwui/Matrix.cpp157
-rw-r--r--libs/hwui/Matrix.h60
-rw-r--r--libs/hwui/OpenGLRenderer.cpp1836
-rw-r--r--libs/hwui/OpenGLRenderer.h303
-rw-r--r--libs/hwui/Patch.cpp47
-rw-r--r--libs/hwui/Patch.h10
-rw-r--r--libs/hwui/PatchCache.cpp33
-rw-r--r--libs/hwui/PatchCache.h44
-rw-r--r--libs/hwui/PathCache.cpp559
-rw-r--r--libs/hwui/PathCache.h291
-rw-r--r--libs/hwui/PathRenderer.cpp721
-rw-r--r--libs/hwui/PathTessellator.cpp971
-rw-r--r--libs/hwui/PathTessellator.h (renamed from libs/hwui/PathRenderer.h)60
-rw-r--r--libs/hwui/Program.cpp2
-rw-r--r--libs/hwui/Program.h21
-rw-r--r--libs/hwui/ProgramCache.cpp174
-rw-r--r--libs/hwui/ProgramCache.h13
-rw-r--r--libs/hwui/Properties.h39
-rw-r--r--libs/hwui/Rect.h26
-rw-r--r--libs/hwui/RenderBuffer.h194
-rw-r--r--libs/hwui/RenderBufferCache.cpp166
-rw-r--r--libs/hwui/RenderBufferCache.h130
-rw-r--r--libs/hwui/ShapeCache.cpp168
-rw-r--r--libs/hwui/ShapeCache.h639
-rw-r--r--libs/hwui/SkiaShader.cpp10
-rw-r--r--libs/hwui/SkiaShader.h2
-rw-r--r--libs/hwui/Snapshot.cpp66
-rw-r--r--libs/hwui/Snapshot.h14
-rw-r--r--libs/hwui/Stencil.cpp29
-rw-r--r--libs/hwui/Stencil.h14
-rw-r--r--libs/hwui/TextDropShadowCache.cpp74
-rw-r--r--libs/hwui/TextDropShadowCache.h100
-rw-r--r--libs/hwui/TextureCache.cpp8
-rw-r--r--libs/hwui/TextureCache.h4
-rw-r--r--libs/hwui/Vertex.h39
-rw-r--r--libs/hwui/font/CacheTexture.cpp82
-rw-r--r--libs/hwui/font/CacheTexture.h119
-rw-r--r--libs/hwui/font/Font.cpp316
-rw-r--r--libs/hwui/font/Font.h86
-rw-r--r--libs/hwui/font/FontUtil.h6
-rw-r--r--libs/hwui/thread/Barrier.h58
-rw-r--r--libs/hwui/thread/Future.h58
-rw-r--r--libs/hwui/thread/Signal.h56
-rw-r--r--libs/hwui/thread/Task.h63
-rw-r--r--libs/hwui/thread/TaskManager.cpp124
-rw-r--r--libs/hwui/thread/TaskManager.h106
-rw-r--r--libs/hwui/thread/TaskProcessor.h72
-rw-r--r--libs/hwui/utils/Blur.cpp152
-rw-r--r--libs/hwui/utils/Blur.h37
-rw-r--r--libs/hwui/utils/Compare.h36
-rw-r--r--libs/hwui/utils/Pair.h58
-rw-r--r--libs/hwui/utils/Timing.h42
86 files changed, 9633 insertions, 5955 deletions
diff --git a/libs/androidfw/BackupHelpers.cpp b/libs/androidfw/BackupHelpers.cpp
index dcf41b70d792..b8d3f48e3347 100644
--- a/libs/androidfw/BackupHelpers.cpp
+++ b/libs/androidfw/BackupHelpers.cpp
@@ -553,7 +553,7 @@ int write_tarfile(const String8& packageName, const String8& domain,
if (buf == NULL) {
ALOGE("Out of mem allocating transfer buffer");
err = ENOMEM;
- goto cleanup;
+ goto done;
}
// Magic fields for the ustar file format
diff --git a/libs/androidfw/Input.cpp b/libs/androidfw/Input.cpp
index 05b62bbb831f..eca692a4b547 100644
--- a/libs/androidfw/Input.cpp
+++ b/libs/androidfw/Input.cpp
@@ -72,6 +72,8 @@ bool KeyEvent::hasDefaultAction(int32_t keyCode) {
case AKEYCODE_MEDIA_RECORD:
case AKEYCODE_MEDIA_FAST_FORWARD:
case AKEYCODE_MUTE:
+ case AKEYCODE_BRIGHTNESS_DOWN:
+ case AKEYCODE_BRIGHTNESS_UP:
return true;
}
@@ -108,6 +110,8 @@ bool KeyEvent::isSystemKey(int32_t keyCode) {
case AKEYCODE_CAMERA:
case AKEYCODE_FOCUS:
case AKEYCODE_SEARCH:
+ case AKEYCODE_BRIGHTNESS_DOWN:
+ case AKEYCODE_BRIGHTNESS_UP:
return true;
}
diff --git a/libs/androidfw/InputDevice.cpp b/libs/androidfw/InputDevice.cpp
index fe891cb2879a..f7420523f176 100644
--- a/libs/androidfw/InputDevice.cpp
+++ b/libs/androidfw/InputDevice.cpp
@@ -172,8 +172,8 @@ void InputDeviceInfo::addSource(uint32_t source) {
}
void InputDeviceInfo::addMotionRange(int32_t axis, uint32_t source, float min, float max,
- float flat, float fuzz) {
- MotionRange range = { axis, source, min, max, flat, fuzz };
+ float flat, float fuzz, float resolution) {
+ MotionRange range = { axis, source, min, max, flat, fuzz, resolution };
mMotionRanges.add(range);
}
diff --git a/libs/androidfw/InputTransport.cpp b/libs/androidfw/InputTransport.cpp
index 351c6662eb22..498389eac3f6 100644
--- a/libs/androidfw/InputTransport.cpp
+++ b/libs/androidfw/InputTransport.cpp
@@ -219,6 +219,11 @@ status_t InputChannel::receiveMessage(InputMessage* msg) {
return OK;
}
+sp<InputChannel> InputChannel::dup() const {
+ int fd = ::dup(getFd());
+ return fd >= 0 ? new InputChannel(getName(), fd) : NULL;
+}
+
// --- InputPublisher ---
diff --git a/libs/diskusage/Android.mk b/libs/diskusage/Android.mk
deleted file mode 100644
index d54f8adf6059..000000000000
--- a/libs/diskusage/Android.mk
+++ /dev/null
@@ -1,24 +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.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libdiskusage
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := dirsize.c
-
-include $(BUILD_STATIC_LIBRARY) \ No newline at end of file
diff --git a/libs/diskusage/MODULE_LICENSE_APACHE2 b/libs/diskusage/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29bb2d1..000000000000
--- a/libs/diskusage/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/libs/diskusage/dirsize.c b/libs/diskusage/dirsize.c
deleted file mode 100644
index 6703783b5bbb..000000000000
--- a/libs/diskusage/dirsize.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- *
- * Copyright (C) 2008, 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 <dirent.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-
-#include <diskusage/dirsize.h>
-
-int64_t stat_size(struct stat *s)
-{
- int64_t blksize = s->st_blksize;
- int64_t size = s->st_size;
-
- if (blksize) {
- /* round up to filesystem block size */
- size = (size + blksize - 1) & (~(blksize - 1));
- }
-
- return size;
-}
-
-int64_t calculate_dir_size(int dfd)
-{
- int64_t size = 0;
- struct stat s;
- DIR *d;
- struct dirent *de;
-
- d = fdopendir(dfd);
- if (d == NULL) {
- close(dfd);
- return 0;
- }
-
- while ((de = readdir(d))) {
- const char *name = de->d_name;
- if (fstatat(dfd, name, &s, AT_SYMLINK_NOFOLLOW) == 0) {
- size += stat_size(&s);
- }
- if (de->d_type == DT_DIR) {
- int subfd;
-
- /* always skip "." and ".." */
- if (name[0] == '.') {
- if (name[1] == 0)
- continue;
- if ((name[1] == '.') && (name[2] == 0))
- continue;
- }
-
- subfd = openat(dfd, name, O_RDONLY | O_DIRECTORY);
- if (subfd >= 0) {
- size += calculate_dir_size(subfd);
- }
- }
- }
- closedir(d);
- return size;
-}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 549edd2bf7b3..7b59bf24576f 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -5,15 +5,20 @@ include $(CLEAR_VARS)
# defined in the current device/board configuration
ifeq ($(USE_OPENGL_RENDERER),true)
LOCAL_SRC_FILES:= \
+ utils/Blur.cpp \
utils/SortedListImpl.cpp \
+ thread/TaskManager.cpp \
font/CacheTexture.cpp \
font/Font.cpp \
FontRenderer.cpp \
GammaFontRenderer.cpp \
Caches.cpp \
+ DisplayList.cpp \
+ DeferredDisplayList.cpp \
DisplayListLogBuffer.cpp \
DisplayListRenderer.cpp \
Dither.cpp \
+ Extensions.cpp \
FboCache.cpp \
GradientCache.cpp \
Layer.cpp \
@@ -21,14 +26,14 @@ ifeq ($(USE_OPENGL_RENDERER),true)
LayerRenderer.cpp \
Matrix.cpp \
OpenGLRenderer.cpp \
- PathRenderer.cpp \
Patch.cpp \
PatchCache.cpp \
PathCache.cpp \
+ PathTessellator.cpp \
Program.cpp \
ProgramCache.cpp \
+ RenderBufferCache.cpp \
ResourceCache.cpp \
- ShapeCache.cpp \
SkiaColorFilter.cpp \
SkiaShader.cpp \
Snapshot.cpp \
@@ -36,18 +41,24 @@ ifeq ($(USE_OPENGL_RENDERER),true)
TextureCache.cpp \
TextDropShadowCache.cpp
+ intermediates := $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,)
+
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
$(LOCAL_PATH)/../../include/utils \
external/skia/include/core \
external/skia/include/effects \
external/skia/include/images \
+ external/skia/src/core \
external/skia/src/ports \
- external/skia/include/utils
+ external/skia/include/utils \
+ $(intermediates) \
+ frameworks/rs/cpp \
+ frameworks/rs
LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DGL_GLEXT_PROTOTYPES
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
- LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2 libskia libui
+ LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libGLESv2 libskia libui libRS libRScpp
LOCAL_MODULE := libhwui
LOCAL_MODULE_TAGS := optional
@@ -61,5 +72,5 @@ ifeq ($(USE_OPENGL_RENDERER),true)
include $(BUILD_SHARED_LIBRARY)
- include $(call all-makefiles-under,$(LOCAL_PATH))
+ include $(call all-makefiles-under,$(LOCAL_PATH))
endif
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index e7085b0cb46c..57d1a4f27a55 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -47,12 +47,12 @@ namespace uirenderer {
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
-Caches::Caches(): Singleton<Caches>(), mInitialized(false) {
+Caches::Caches(): Singleton<Caches>(), mExtensions(Extensions::getInstance()), mInitialized(false) {
init();
initFont();
- initExtensions();
initConstraints();
initProperties();
+ initExtensions();
mDebugLevel = readDebugLevel();
ALOGD("Enabling debug mode %d", mDebugLevel);
@@ -89,6 +89,10 @@ void Caches::init() {
mFunctorsCount = 0;
+ debugLayersUpdates = false;
+ debugOverdraw = false;
+ debugStencilClip = kStencilHide;
+
mInitialized = true;
}
@@ -97,8 +101,9 @@ void Caches::initFont() {
}
void Caches::initExtensions() {
- if (extensions.hasDebugMarker()) {
+ if (mExtensions.hasDebugMarker()) {
eventMark = glInsertEventMarkerEXT;
+
startMark = glPushGroupMarkerEXT;
endMark = glPopGroupMarkerEXT;
} else {
@@ -107,7 +112,7 @@ void Caches::initExtensions() {
endMark = endMarkNull;
}
- if (extensions.hasDebugLabel()) {
+ if (mExtensions.hasDebugLabel() && (drawDeferDisabled || drawReorderDisabled)) {
setLabel = glLabelObjectEXT;
getLabel = glGetObjectLabelEXT;
} else {
@@ -126,7 +131,11 @@ void Caches::initConstraints() {
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
}
-void Caches::initProperties() {
+bool Caches::initProperties() {
+ bool prevDebugLayersUpdates = debugLayersUpdates;
+ bool prevDebugOverdraw = debugOverdraw;
+ StencilClipDebug prevDebugStencilClip = debugStencilClip;
+
char property[PROPERTY_VALUE_MAX];
if (property_get(PROPERTY_DEBUG_LAYERS_UPDATES, property, NULL) > 0) {
INIT_LOGD(" Layers updates debug enabled: %s", property);
@@ -141,6 +150,38 @@ void Caches::initProperties() {
} else {
debugOverdraw = false;
}
+
+ // See Properties.h for valid values
+ if (property_get(PROPERTY_DEBUG_STENCIL_CLIP, property, NULL) > 0) {
+ INIT_LOGD(" Stencil clip debug enabled: %s", property);
+ if (!strcmp(property, "hide")) {
+ debugStencilClip = kStencilHide;
+ } else if (!strcmp(property, "highlight")) {
+ debugStencilClip = kStencilShowHighlight;
+ } else if (!strcmp(property, "region")) {
+ debugStencilClip = kStencilShowRegion;
+ }
+ } else {
+ debugStencilClip = kStencilHide;
+ }
+
+ if (property_get(PROPERTY_DISABLE_DRAW_DEFER, property, "false")) {
+ drawDeferDisabled = !strcasecmp(property, "true");
+ INIT_LOGD(" Draw defer %s", drawDeferDisabled ? "disabled" : "enabled");
+ } else {
+ INIT_LOGD(" Draw defer enabled");
+ }
+
+ if (property_get(PROPERTY_DISABLE_DRAW_REORDER, property, "false")) {
+ drawReorderDisabled = !strcasecmp(property, "true");
+ INIT_LOGD(" Draw reorder %s", drawReorderDisabled ? "disabled" : "enabled");
+ } else {
+ INIT_LOGD(" Draw reorder enabled");
+ }
+
+ return (prevDebugLayersUpdates != debugLayersUpdates) ||
+ (prevDebugOverdraw != debugOverdraw) ||
+ (prevDebugStencilClip != debugStencilClip);
}
void Caches::terminate() {
@@ -177,20 +218,12 @@ void Caches::dumpMemoryUsage(String8 &log) {
textureCache.getSize(), textureCache.getMaxSize());
log.appendFormat(" LayerCache %8d / %8d\n",
layerCache.getSize(), layerCache.getMaxSize());
+ log.appendFormat(" RenderBufferCache %8d / %8d\n",
+ renderBufferCache.getSize(), renderBufferCache.getMaxSize());
log.appendFormat(" GradientCache %8d / %8d\n",
gradientCache.getSize(), gradientCache.getMaxSize());
log.appendFormat(" PathCache %8d / %8d\n",
pathCache.getSize(), pathCache.getMaxSize());
- log.appendFormat(" CircleShapeCache %8d / %8d\n",
- circleShapeCache.getSize(), circleShapeCache.getMaxSize());
- log.appendFormat(" OvalShapeCache %8d / %8d\n",
- ovalShapeCache.getSize(), ovalShapeCache.getMaxSize());
- log.appendFormat(" RoundRectShapeCache %8d / %8d\n",
- roundRectShapeCache.getSize(), roundRectShapeCache.getMaxSize());
- log.appendFormat(" RectShapeCache %8d / %8d\n",
- rectShapeCache.getSize(), rectShapeCache.getMaxSize());
- log.appendFormat(" ArcShapeCache %8d / %8d\n",
- arcShapeCache.getSize(), arcShapeCache.getMaxSize());
log.appendFormat(" TextDropShadowCache %8d / %8d\n", dropShadowCache.getSize(),
dropShadowCache.getMaxSize());
for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
@@ -206,14 +239,10 @@ void Caches::dumpMemoryUsage(String8 &log) {
uint32_t total = 0;
total += textureCache.getSize();
total += layerCache.getSize();
+ total += renderBufferCache.getSize();
total += gradientCache.getSize();
total += pathCache.getSize();
total += dropShadowCache.getSize();
- total += roundRectShapeCache.getSize();
- total += circleShapeCache.getSize();
- total += ovalShapeCache.getSize();
- total += rectShapeCache.getSize();
- total += arcShapeCache.getSize();
for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
total += fontRenderer->getFontRendererSize(i);
}
@@ -281,14 +310,11 @@ void Caches::flush(FlushMode mode) {
fontRenderer->flush();
textureCache.flush();
pathCache.clear();
- roundRectShapeCache.clear();
- circleShapeCache.clear();
- ovalShapeCache.clear();
- rectShapeCache.clear();
- arcShapeCache.clear();
+ tasks.stop();
// fall through
case kFlushMode_Layers:
layerCache.clear();
+ renderBufferCache.clear();
break;
}
@@ -352,11 +378,12 @@ void Caches::bindPositionVertexPointer(bool force, GLvoid* vertices, GLsizei str
}
}
-void Caches::bindTexCoordsVertexPointer(bool force, GLvoid* vertices) {
- if (force || vertices != mCurrentTexCoordsPointer) {
+void Caches::bindTexCoordsVertexPointer(bool force, GLvoid* vertices, GLsizei stride) {
+ if (force || vertices != mCurrentTexCoordsPointer || stride != mCurrentTexCoordsStride) {
GLuint slot = currentProgram->texCoords;
- glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, gMeshStride, vertices);
+ glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, stride, vertices);
mCurrentTexCoordsPointer = vertices;
+ mCurrentTexCoordsStride = stride;
}
}
@@ -377,7 +404,7 @@ void Caches::enableTexCoordsVertexArray() {
}
}
-void Caches::disbaleTexCoordsVertexArray() {
+void Caches::disableTexCoordsVertexArray() {
if (mTexCoordsArrayEnabled) {
glDisableVertexAttribArray(Program::kBindingTexCoords);
mTexCoordsArrayEnabled = false;
@@ -460,14 +487,14 @@ void Caches::resetScissor() {
// Tiling
///////////////////////////////////////////////////////////////////////////////
-void Caches::startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool opaque) {
- if (extensions.hasTiledRendering() && !debugOverdraw) {
- glStartTilingQCOM(x, y, width, height, (opaque ? GL_NONE : GL_COLOR_BUFFER_BIT0_QCOM));
+void Caches::startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool discard) {
+ if (mExtensions.hasTiledRendering() && !debugOverdraw) {
+ glStartTilingQCOM(x, y, width, height, (discard ? GL_NONE : GL_COLOR_BUFFER_BIT0_QCOM));
}
}
void Caches::endTiling() {
- if (extensions.hasTiledRendering() && !debugOverdraw) {
+ if (mExtensions.hasTiledRendering() && !debugOverdraw) {
glEndTilingQCOM(GL_COLOR_BUFFER_BIT0_QCOM);
}
}
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index ad1ff6fa56be..63836c1c7dea 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -25,15 +25,17 @@
#include <cutils/compiler.h>
-#include "Extensions.h"
+#include "thread/TaskProcessor.h"
+#include "thread/TaskManager.h"
+
#include "FontRenderer.h"
#include "GammaFontRenderer.h"
#include "TextureCache.h"
#include "LayerCache.h"
+#include "RenderBufferCache.h"
#include "GradientCache.h"
#include "PatchCache.h"
#include "ProgramCache.h"
-#include "ShapeCache.h"
#include "PathCache.h"
#include "TextDropShadowCache.h"
#include "FboCache.h"
@@ -66,7 +68,6 @@ static const TextureVertex gMeshVertices[] = {
static const GLsizei gMeshStride = sizeof(TextureVertex);
static const GLsizei gVertexStride = sizeof(Vertex);
static const GLsizei gAlphaVertexStride = sizeof(AlphaVertex);
-static const GLsizei gAAVertexStride = sizeof(AAVertex);
static const GLsizei gMeshTextureOffset = 2 * sizeof(float);
static const GLsizei gVertexAlphaOffset = 2 * sizeof(float);
static const GLsizei gVertexAAWidthOffset = 2 * sizeof(float);
@@ -115,6 +116,11 @@ public:
void init();
/**
+ * Initialize global system properties.
+ */
+ bool initProperties();
+
+ /**
* Flush the cache.
*
* @param mode Indicates how much of the cache should be flushed
@@ -179,7 +185,7 @@ public:
* Binds an attrib to the specified float vertex pointer.
* Assumes a stride of gMeshStride and a size of 2.
*/
- void bindTexCoordsVertexPointer(bool force, GLvoid* vertices);
+ void bindTexCoordsVertexPointer(bool force, GLvoid* vertices, GLsizei stride = gMeshStride);
/**
* Resets the vertex pointers.
@@ -188,7 +194,7 @@ public:
void resetTexCoordsVertexPointer();
void enableTexCoordsVertexArray();
- void disbaleTexCoordsVertexArray();
+ void disableTexCoordsVertexArray();
/**
* Activate the specified texture unit. The texture unit must
@@ -210,7 +216,7 @@ public:
bool disableScissor();
void setScissorEnabled(bool enabled);
- void startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool opaque);
+ void startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool discard);
void endTiling();
/**
@@ -236,27 +242,32 @@ public:
Program* currentProgram;
bool scissorEnabled;
+ bool drawDeferDisabled;
+ bool drawReorderDisabled;
+
// VBO to draw with
GLuint meshBuffer;
- // GL extensions
- Extensions extensions;
-
// Misc
GLint maxTextureSize;
+
+ // Debugging
bool debugLayersUpdates;
bool debugOverdraw;
+ enum StencilClipDebug {
+ kStencilHide,
+ kStencilShowHighlight,
+ kStencilShowRegion
+ };
+ StencilClipDebug debugStencilClip;
+
TextureCache textureCache;
LayerCache layerCache;
+ RenderBufferCache renderBufferCache;
GradientCache gradientCache;
ProgramCache programCache;
PathCache pathCache;
- RoundRectShapeCache roundRectShapeCache;
- CircleShapeCache circleShapeCache;
- OvalShapeCache ovalShapeCache;
- RectShapeCache rectShapeCache;
- ArcShapeCache arcShapeCache;
PatchCache patchCache;
TextDropShadowCache dropShadowCache;
FboCache fboCache;
@@ -264,10 +275,10 @@ public:
GammaFontRenderer* fontRenderer;
+ TaskManager tasks;
+
Dither dither;
-#if STENCIL_BUFFER_SIZE
Stencil stencil;
-#endif
// Debug methods
PFNGLINSERTEVENTMARKEREXTPROC eventMark;
@@ -281,7 +292,6 @@ private:
void initFont();
void initExtensions();
void initConstraints();
- void initProperties();
static void eventMarkNull(GLsizei length, const GLchar* marker) { }
static void startMarkNull(GLsizei length, const GLchar* marker) { }
@@ -300,6 +310,7 @@ private:
void* mCurrentPositionPointer;
GLsizei mCurrentPositionStride;
void* mCurrentTexCoordsPointer;
+ GLsizei mCurrentTexCoordsStride;
bool mTexCoordsArrayEnabled;
@@ -310,6 +321,8 @@ private:
GLint mScissorWidth;
GLint mScissorHeight;
+ Extensions& mExtensions;
+
// Used to render layers
TextureVertex* mRegionMesh;
GLuint mRegionMeshIndices;
diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h
index 6795ac3205d1..46beb94fecd2 100644
--- a/libs/hwui/Debug.h
+++ b/libs/hwui/Debug.h
@@ -35,12 +35,22 @@
// Turn on to enable layers debugging when rendered as regions
#define DEBUG_LAYERS_AS_REGIONS 0
+// Turn on to enable debugging when the clip is not a rect
+#define DEBUG_CLIP_REGIONS 0
+
// Turn on to display debug info about vertex/fragment shaders
#define DEBUG_PROGRAMS 0
// Turn on to display info about layers
#define DEBUG_LAYERS 0
+// Turn on to display info about render buffers
+#define DEBUG_RENDER_BUFFERS 0
+
+// Turn on to make stencil operations easier to debug
+// (writes 255 instead of 1 in the buffer, forces 8 bit stencil)
+#define DEBUG_STENCIL 0
+
// Turn on to display debug info about 9patch objects
#define DEBUG_PATCHES 0
// Turn on to "explode" 9patch objects
@@ -54,7 +64,7 @@
#define DEBUG_PATCHES_EMPTY_VERTICES 0
// Turn on to display debug info about shapes
-#define DEBUG_SHAPES 0
+#define DEBUG_PATHS 0
// Turn on to display debug info about textures
#define DEBUG_TEXTURES 0
@@ -65,6 +75,9 @@
// Turn on to enable additional debugging in the font renderers
#define DEBUG_FONT_RENDERER 0
+// Turn on to log draw operation batching and deferral information
+#define DEBUG_DEFER 0
+
// Turn on to dump display list state
#define DEBUG_DISPLAY_LIST 0
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
new file mode 100644
index 000000000000..fe51bf956d8c
--- /dev/null
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -0,0 +1,406 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
+
+#include <SkCanvas.h>
+
+#include <utils/Trace.h>
+
+#include "Debug.h"
+#include "DisplayListOp.h"
+#include "OpenGLRenderer.h"
+
+#if DEBUG_DEFER
+ #define DEFER_LOGD(...) ALOGD(__VA_ARGS__)
+#else
+ #define DEFER_LOGD(...)
+#endif
+
+namespace android {
+namespace uirenderer {
+
+// Depth of the save stack at the beginning of batch playback at flush time
+#define FLUSH_SAVE_STACK_DEPTH 2
+
+/////////////////////////////////////////////////////////////////////////////////
+// Operation Batches
+/////////////////////////////////////////////////////////////////////////////////
+
+class DrawOpBatch {
+public:
+ DrawOpBatch() { mOps.clear(); }
+
+ virtual ~DrawOpBatch() { mOps.clear(); }
+
+ void add(DrawOp* op) {
+ // NOTE: ignore empty bounds special case, since we don't merge across those ops
+ mBounds.unionWith(op->state.mBounds);
+ mOps.add(op);
+ }
+
+ virtual bool intersects(Rect& rect) {
+ if (!rect.intersects(mBounds)) return false;
+
+ for (unsigned int i = 0; i < mOps.size(); i++) {
+ if (rect.intersects(mOps[i]->state.mBounds)) {
+#if DEBUG_DEFER
+ DEFER_LOGD("op intersects with op %p with bounds %f %f %f %f:", mOps[i],
+ mOps[i]->state.mBounds.left, mOps[i]->state.mBounds.top,
+ mOps[i]->state.mBounds.right, mOps[i]->state.mBounds.bottom);
+ mOps[i]->output(2);
+#endif
+ return true;
+ }
+ }
+ return false;
+ }
+
+ virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
+ DEFER_LOGD("replaying draw batch %p", this);
+
+ status_t status = DrawGlInfo::kStatusDone;
+ DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
+ for (unsigned int i = 0; i < mOps.size(); i++) {
+ DrawOp* op = mOps[i];
+
+ renderer.restoreDisplayState(op->state);
+
+#if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
+ renderer.eventMark(op->name());
+#endif
+ status |= op->applyDraw(renderer, dirty, 0);
+ logBuffer.writeCommand(0, op->name());
+ }
+ return status;
+ }
+
+ inline int count() const { return mOps.size(); }
+private:
+ Vector<DrawOp*> mOps;
+ Rect mBounds;
+};
+
+class StateOpBatch : public DrawOpBatch {
+public:
+ // creates a single operation batch
+ StateOpBatch(StateOp* op) : mOp(op) {}
+
+ bool intersects(Rect& rect) {
+ // if something checks for intersection, it's trying to go backwards across a state op,
+ // something not currently supported - state ops are always barriers
+ CRASH();
+ return false;
+ }
+
+ virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
+ DEFER_LOGD("replaying state op batch %p", this);
+ renderer.restoreDisplayState(mOp->state);
+
+ // use invalid save count because it won't be used at flush time - RestoreToCountOp is the
+ // only one to use it, and we don't use that class at flush time, instead calling
+ // renderer.restoreToCount directly
+ int saveCount = -1;
+ mOp->applyState(renderer, saveCount);
+ return DrawGlInfo::kStatusDone;
+ }
+
+private:
+ const StateOp* mOp;
+};
+
+class RestoreToCountBatch : public DrawOpBatch {
+public:
+ RestoreToCountBatch(StateOp* op, int restoreCount) : mOp(op), mRestoreCount(restoreCount) {}
+
+ bool intersects(Rect& rect) {
+ // if something checks for intersection, it's trying to go backwards across a state op,
+ // something not currently supported - state ops are always barriers
+ CRASH();
+ return false;
+ }
+
+ virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
+ DEFER_LOGD("batch %p restoring to count %d", this, mRestoreCount);
+
+ renderer.restoreDisplayState(mOp->state);
+ renderer.restoreToCount(mRestoreCount);
+ return DrawGlInfo::kStatusDone;
+ }
+
+private:
+ // we use the state storage for the RestoreToCountOp, but don't replay the op itself
+ const StateOp* mOp;
+ /*
+ * The count used here represents the flush() time saveCount. This is as opposed to the
+ * DisplayList record time, or defer() time values (which are RestoreToCountOp's mCount, and
+ * (saveCount + mCount) respectively). Since the count is different from the original
+ * RestoreToCountOp, we don't store a pointer to the op, as elsewhere.
+ */
+ const int mRestoreCount;
+};
+
+/////////////////////////////////////////////////////////////////////////////////
+// DeferredDisplayList
+/////////////////////////////////////////////////////////////////////////////////
+
+void DeferredDisplayList::resetBatchingState() {
+ for (int i = 0; i < kOpBatch_Count; i++) {
+ mBatchIndices[i] = -1;
+ }
+}
+
+void DeferredDisplayList::clear() {
+ resetBatchingState();
+ mComplexClipStackStart = -1;
+
+ for (unsigned int i = 0; i < mBatches.size(); i++) {
+ delete mBatches[i];
+ }
+ mBatches.clear();
+ mSaveStack.clear();
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+// Operation adding
+/////////////////////////////////////////////////////////////////////////////////
+
+int DeferredDisplayList::getStateOpDeferFlags() const {
+ // For both clipOp and save(Layer)Op, we don't want to save drawing info, and only want to save
+ // the clip if we aren't recording a complex clip (and can thus trust it to be a rect)
+ return recordingComplexClip() ? 0 : kStateDeferFlag_Clip;
+}
+
+int DeferredDisplayList::getDrawOpDeferFlags() const {
+ return kStateDeferFlag_Draw | getStateOpDeferFlags();
+}
+
+/**
+ * When an clipping operation occurs that could cause a complex clip, record the operation and all
+ * subsequent clipOps, save/restores (if the clip flag is set). During a flush, instead of loading
+ * the clip from deferred state, we play back all of the relevant state operations that generated
+ * the complex clip.
+ *
+ * Note that we don't need to record the associated restore operation, since operations at defer
+ * time record whether they should store the renderer's current clip
+ */
+void DeferredDisplayList::addClip(OpenGLRenderer& renderer, ClipOp* op) {
+ if (recordingComplexClip() || op->canCauseComplexClip() || !renderer.hasRectToRectTransform()) {
+ DEFER_LOGD("%p Received complex clip operation %p", this, op);
+
+ // NOTE: defer clip op before setting mComplexClipStackStart so previous clip is recorded
+ storeStateOpBarrier(renderer, op);
+
+ if (!recordingComplexClip()) {
+ mComplexClipStackStart = renderer.getSaveCount() - 1;
+ DEFER_LOGD(" Starting complex clip region, start is %d", mComplexClipStackStart);
+ }
+ }
+}
+
+/**
+ * For now, we record save layer operations as barriers in the batch list, preventing drawing
+ * operations from reordering around the saveLayer and it's associated restore()
+ *
+ * In the future, we should send saveLayer commands (if they can be played out of order) and their
+ * contained drawing operations to a seperate list of batches, so that they may draw at the
+ * 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.
+ */
+void DeferredDisplayList::addSaveLayer(OpenGLRenderer& renderer,
+ SaveLayerOp* op, int newSaveCount) {
+ DEFER_LOGD("%p adding saveLayerOp %p, flags %x, new count %d",
+ this, op, op->getFlags(), newSaveCount);
+
+ storeStateOpBarrier(renderer, op);
+ mSaveStack.push(newSaveCount);
+}
+
+/**
+ * Takes save op and it's return value - the new save count - and stores it into the stream as a
+ * barrier if it's needed to properly modify a complex clip
+ */
+void DeferredDisplayList::addSave(OpenGLRenderer& renderer, SaveOp* op, int newSaveCount) {
+ 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)) {
+ // 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);
+ }
+}
+
+/**
+ * saveLayer() commands must be associated with a restoreToCount batch that will clean up and draw
+ * the layer in the deferred list
+ *
+ * other save() commands which occur as children of a snapshot with complex clip will be deferred,
+ * and must be restored
+ *
+ * Either will act as a barrier to draw operation reordering, as we want to play back layer
+ * save/restore and complex canvas modifications (including save/restore) in order.
+ */
+void DeferredDisplayList::addRestoreToCount(OpenGLRenderer& renderer, StateOp* op,
+ int newSaveCount) {
+ DEFER_LOGD("%p addRestoreToCount %d", this, newSaveCount);
+
+ if (recordingComplexClip() && newSaveCount <= mComplexClipStackStart) {
+ mComplexClipStackStart = -1;
+ resetBatchingState();
+ }
+
+ if (mSaveStack.isEmpty() || newSaveCount > mSaveStack.top()) {
+ return;
+ }
+
+ while (!mSaveStack.isEmpty() && mSaveStack.top() >= newSaveCount) mSaveStack.pop();
+
+ storeRestoreToCountBarrier(renderer, op, mSaveStack.size() + FLUSH_SAVE_STACK_DEPTH);
+}
+
+void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
+ if (renderer.storeDisplayState(op->state, getDrawOpDeferFlags())) {
+ return; // quick rejected
+ }
+
+ op->onDrawOpDeferred(renderer);
+
+ if (CC_UNLIKELY(renderer.getCaches().drawReorderDisabled)) {
+ // TODO: elegant way to reuse batches?
+ DrawOpBatch* b = new DrawOpBatch();
+ b->add(op);
+ mBatches.add(b);
+ return;
+ }
+
+ // disallowReorder isn't set, so find the latest batch of the new op's type, and try to merge
+ // the new op into it
+ DrawOpBatch* targetBatch = NULL;
+ int batchId = op->getBatchId();
+
+ if (!mBatches.isEmpty()) {
+ if (op->state.mBounds.isEmpty()) {
+ // don't know the bounds for op, so add to last batch and start from scratch on next op
+ mBatches.top()->add(op);
+ for (int i = 0; i < kOpBatch_Count; i++) {
+ mBatchIndices[i] = -1;
+ }
+#if DEBUG_DEFER
+ DEFER_LOGD("Warning: Encountered op with empty bounds, resetting batches");
+ op->output(2);
+#endif
+ return;
+ }
+
+ if (batchId >= 0 && mBatchIndices[batchId] != -1) {
+ int targetIndex = mBatchIndices[batchId];
+ targetBatch = mBatches[targetIndex];
+ // iterate back toward target to see if anything drawn since should overlap the new op
+ for (int i = mBatches.size() - 1; i > targetIndex; i--) {
+ DrawOpBatch* overBatch = mBatches[i];
+ if (overBatch->intersects(op->state.mBounds)) {
+ targetBatch = NULL;
+#if DEBUG_DEFER
+ DEFER_LOGD("op couldn't join batch %d, was intersected by batch %d",
+ targetIndex, i);
+ op->output(2);
+#endif
+ break;
+ }
+ }
+ }
+ }
+ if (!targetBatch) {
+ targetBatch = new DrawOpBatch();
+ mBatches.add(targetBatch);
+ if (batchId >= 0) {
+ mBatchIndices[batchId] = mBatches.size() - 1;
+ }
+ }
+ targetBatch->add(op);
+}
+
+void DeferredDisplayList::storeStateOpBarrier(OpenGLRenderer& renderer, StateOp* op) {
+ DEFER_LOGD("%p adding state op barrier at pos %d", this, mBatches.size());
+
+ renderer.storeDisplayState(op->state, getStateOpDeferFlags());
+ mBatches.add(new StateOpBatch(op));
+ resetBatchingState();
+}
+
+void DeferredDisplayList::storeRestoreToCountBarrier(OpenGLRenderer& renderer, StateOp* op,
+ int newSaveCount) {
+ DEFER_LOGD("%p adding restore to count %d barrier, pos %d",
+ 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
+ renderer.storeDisplayState(op->state, getStateOpDeferFlags());
+ mBatches.add(new RestoreToCountBatch(op, newSaveCount));
+ resetBatchingState();
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+// Replay / flush
+/////////////////////////////////////////////////////////////////////////////////
+
+static status_t replayBatchList(Vector<DrawOpBatch*>& batchList,
+ OpenGLRenderer& renderer, Rect& dirty) {
+ status_t status = DrawGlInfo::kStatusDone;
+
+ int opCount = 0;
+ for (unsigned int i = 0; i < batchList.size(); i++) {
+ status |= batchList[i]->replay(renderer, dirty);
+ opCount += batchList[i]->count();
+ }
+ DEFER_LOGD("--flushed, drew %d batches (total %d ops)", batchList.size(), opCount);
+ return status;
+}
+
+status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) {
+ ATRACE_NAME("flush drawing commands");
+ status_t status = DrawGlInfo::kStatusDone;
+
+ if (isEmpty()) return status; // nothing to flush
+ renderer.restoreToCount(1);
+
+ DEFER_LOGD("--flushing");
+ renderer.eventMark("Flush");
+
+ // save and restore (with draw modifiers) so that reordering doesn't affect final state
+ DrawModifiers restoreDrawModifiers = renderer.getDrawModifiers();
+ renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+
+ // NOTE: depth of the save stack at this point, before playback, should be reflected in
+ // FLUSH_SAVE_STACK_DEPTH, so that save/restores match up correctly
+ status |= replayBatchList(mBatches, renderer, dirty);
+
+ renderer.restoreToCount(1);
+ renderer.setDrawModifiers(restoreDrawModifiers);
+
+ DEFER_LOGD("--flush complete, returning %x", status);
+
+ clear();
+ return status;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
new file mode 100644
index 000000000000..653f315c69fb
--- /dev/null
+++ b/libs/hwui/DeferredDisplayList.h
@@ -0,0 +1,106 @@
+/*
+ * 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_DEFERRED_DISPLAY_LIST_H
+#define ANDROID_HWUI_DEFERRED_DISPLAY_LIST_H
+
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+
+#include "Matrix.h"
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+class ClipOp;
+class DrawOp;
+class SaveOp;
+class SaveLayerOp;
+class StateOp;
+class DrawOpBatch;
+class OpenGLRenderer;
+
+class DeferredDisplayList {
+public:
+ DeferredDisplayList() { clear(); }
+ ~DeferredDisplayList() { clear(); }
+
+ enum OpBatchId {
+ kOpBatch_None = -1, // Don't batch
+ kOpBatch_Bitmap,
+ kOpBatch_Patch,
+ kOpBatch_AlphaVertices,
+ kOpBatch_Vertices,
+ kOpBatch_AlphaMaskTexture,
+ kOpBatch_Text,
+ kOpBatch_ColorText,
+
+ kOpBatch_Count, // Add other batch ids before this
+ };
+
+ bool isEmpty() { return mBatches.isEmpty(); }
+
+ /**
+ * Plays back all of the draw ops recorded into batches to the renderer.
+ * Adjusts the state of the renderer as necessary, and restores it when complete
+ */
+ status_t flush(OpenGLRenderer& renderer, Rect& dirty);
+
+ void addClip(OpenGLRenderer& renderer, ClipOp* op);
+ void addSaveLayer(OpenGLRenderer& renderer, SaveLayerOp* op, int newSaveCount);
+ void addSave(OpenGLRenderer& renderer, SaveOp* op, int newSaveCount);
+ void addRestoreToCount(OpenGLRenderer& renderer, StateOp* op, int newSaveCount);
+
+ /**
+ * Add a draw op into the DeferredDisplayList, reordering as needed (for performance) if
+ * disallowReorder is false, respecting draw order when overlaps occur
+ */
+ void addDrawOp(OpenGLRenderer& renderer, DrawOp* op);
+
+private:
+ /**
+ * Resets the batching back-pointers, creating a barrier in the operation stream so that no ops
+ * added in the future will be inserted into a batch that already exist.
+ */
+ void resetBatchingState();
+
+ void clear();
+
+ void storeStateOpBarrier(OpenGLRenderer& renderer, StateOp* op);
+ void storeRestoreToCountBarrier(OpenGLRenderer& renderer, StateOp* op, int newSaveCount);
+
+ bool recordingComplexClip() const { return mComplexClipStackStart >= 0; }
+
+ int getStateOpDeferFlags() const;
+ int getDrawOpDeferFlags() const;
+
+ /**
+ * 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;
+ int mComplexClipStackStart;
+
+ Vector<DrawOpBatch*> mBatches;
+ int mBatchIndices[kOpBatch_Count];
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_DEFERRED_DISPLAY_LIST_H
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
new file mode 100644
index 000000000000..36c95f90a403
--- /dev/null
+++ b/libs/hwui/DisplayList.cpp
@@ -0,0 +1,524 @@
+/*
+ * 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 <SkCanvas.h>
+
+#include "Debug.h"
+#include "DisplayList.h"
+#include "DisplayListOp.h"
+#include "DisplayListLogBuffer.h"
+
+namespace android {
+namespace uirenderer {
+
+void DisplayList::outputLogBuffer(int fd) {
+ DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
+ if (logBuffer.isEmpty()) {
+ return;
+ }
+
+ FILE *file = fdopen(fd, "a");
+
+ fprintf(file, "\nRecent DisplayList operations\n");
+ logBuffer.outputCommands(file);
+
+ String8 cachesLog;
+ Caches::getInstance().dumpMemoryUsage(cachesLog);
+ fprintf(file, "\nCaches:\n%s", cachesLog.string());
+ fprintf(file, "\n");
+
+ fflush(file);
+}
+
+DisplayList::DisplayList(const DisplayListRenderer& recorder) :
+ mTransformMatrix(NULL), mTransformCamera(NULL), mTransformMatrix3D(NULL),
+ mStaticMatrix(NULL), mAnimationMatrix(NULL) {
+
+ initFromDisplayListRenderer(recorder);
+}
+
+DisplayList::~DisplayList() {
+ clearResources();
+}
+
+void DisplayList::destroyDisplayListDeferred(DisplayList* displayList) {
+ if (displayList) {
+ DISPLAY_LIST_LOGD("Deferring display list destruction");
+ Caches::getInstance().deleteDisplayListDeferred(displayList);
+ }
+}
+
+void DisplayList::clearResources() {
+ mDisplayListData = NULL;
+
+ mClipRectOp = NULL;
+ mSaveLayerOp = NULL;
+ mSaveOp = NULL;
+ mRestoreToCountOp = NULL;
+
+ delete mTransformMatrix;
+ delete mTransformCamera;
+ delete mTransformMatrix3D;
+ delete mStaticMatrix;
+ delete mAnimationMatrix;
+
+ mTransformMatrix = NULL;
+ mTransformCamera = NULL;
+ mTransformMatrix3D = NULL;
+ mStaticMatrix = NULL;
+ mAnimationMatrix = NULL;
+
+ Caches& caches = Caches::getInstance();
+ caches.unregisterFunctors(mFunctorCount);
+ caches.resourceCache.lock();
+
+ for (size_t i = 0; i < mBitmapResources.size(); i++) {
+ caches.resourceCache.decrementRefcountLocked(mBitmapResources.itemAt(i));
+ }
+
+ for (size_t i = 0; i < mOwnedBitmapResources.size(); i++) {
+ SkBitmap* bitmap = mOwnedBitmapResources.itemAt(i);
+ caches.resourceCache.decrementRefcountLocked(bitmap);
+ caches.resourceCache.destructorLocked(bitmap);
+ }
+
+ for (size_t i = 0; i < mFilterResources.size(); i++) {
+ caches.resourceCache.decrementRefcountLocked(mFilterResources.itemAt(i));
+ }
+
+ for (size_t i = 0; i < mShaders.size(); i++) {
+ caches.resourceCache.decrementRefcountLocked(mShaders.itemAt(i));
+ caches.resourceCache.destructorLocked(mShaders.itemAt(i));
+ }
+
+ for (size_t i = 0; i < mSourcePaths.size(); i++) {
+ caches.resourceCache.decrementRefcountLocked(mSourcePaths.itemAt(i));
+ }
+
+ for (size_t i = 0; i < mLayers.size(); i++) {
+ caches.resourceCache.decrementRefcountLocked(mLayers.itemAt(i));
+ }
+
+ caches.resourceCache.unlock();
+
+ for (size_t i = 0; i < mPaints.size(); i++) {
+ delete mPaints.itemAt(i);
+ }
+
+ for (size_t i = 0; i < mRegions.size(); i++) {
+ delete mRegions.itemAt(i);
+ }
+
+ for (size_t i = 0; i < mPaths.size(); i++) {
+ delete mPaths.itemAt(i);
+ }
+
+ for (size_t i = 0; i < mMatrices.size(); i++) {
+ delete mMatrices.itemAt(i);
+ }
+
+ mBitmapResources.clear();
+ mOwnedBitmapResources.clear();
+ mFilterResources.clear();
+ mShaders.clear();
+ mSourcePaths.clear();
+ mPaints.clear();
+ mRegions.clear();
+ mPaths.clear();
+ mMatrices.clear();
+ mLayers.clear();
+}
+
+void DisplayList::reset() {
+ clearResources();
+ init();
+}
+
+void DisplayList::initFromDisplayListRenderer(const DisplayListRenderer& recorder, bool reusing) {
+ if (reusing) {
+ // re-using display list - clear out previous allocations
+ clearResources();
+ }
+
+ init();
+
+ mDisplayListData = recorder.getDisplayListData();
+ mSize = mDisplayListData->allocator.usedSize();
+
+ if (mSize == 0) {
+ return;
+ }
+
+ // allocate reusable ops for state-deferral
+ LinearAllocator& alloc = mDisplayListData->allocator;
+ mClipRectOp = new (alloc) ClipRectOp();
+ mSaveLayerOp = new (alloc) SaveLayerOp();
+ mSaveOp = new (alloc) SaveOp();
+ mRestoreToCountOp = new (alloc) RestoreToCountOp();
+
+ mFunctorCount = recorder.getFunctorCount();
+
+ Caches& caches = Caches::getInstance();
+ caches.registerFunctors(mFunctorCount);
+ caches.resourceCache.lock();
+
+ const Vector<SkBitmap*>& bitmapResources = recorder.getBitmapResources();
+ for (size_t i = 0; i < bitmapResources.size(); i++) {
+ SkBitmap* resource = bitmapResources.itemAt(i);
+ mBitmapResources.add(resource);
+ caches.resourceCache.incrementRefcountLocked(resource);
+ }
+
+ const Vector<SkBitmap*> &ownedBitmapResources = recorder.getOwnedBitmapResources();
+ for (size_t i = 0; i < ownedBitmapResources.size(); i++) {
+ SkBitmap* resource = ownedBitmapResources.itemAt(i);
+ mOwnedBitmapResources.add(resource);
+ caches.resourceCache.incrementRefcountLocked(resource);
+ }
+
+ const Vector<SkiaColorFilter*>& filterResources = recorder.getFilterResources();
+ for (size_t i = 0; i < filterResources.size(); i++) {
+ SkiaColorFilter* resource = filterResources.itemAt(i);
+ mFilterResources.add(resource);
+ caches.resourceCache.incrementRefcountLocked(resource);
+ }
+
+ const Vector<SkiaShader*>& shaders = recorder.getShaders();
+ for (size_t i = 0; i < shaders.size(); i++) {
+ SkiaShader* resource = shaders.itemAt(i);
+ mShaders.add(resource);
+ caches.resourceCache.incrementRefcountLocked(resource);
+ }
+
+ const SortedVector<SkPath*>& sourcePaths = recorder.getSourcePaths();
+ for (size_t i = 0; i < sourcePaths.size(); i++) {
+ mSourcePaths.add(sourcePaths.itemAt(i));
+ caches.resourceCache.incrementRefcountLocked(sourcePaths.itemAt(i));
+ }
+
+ const Vector<Layer*>& layers = recorder.getLayers();
+ for (size_t i = 0; i < layers.size(); i++) {
+ mLayers.add(layers.itemAt(i));
+ caches.resourceCache.incrementRefcountLocked(layers.itemAt(i));
+ }
+
+ caches.resourceCache.unlock();
+
+ mPaints.appendVector(recorder.getPaints());
+ mRegions.appendVector(recorder.getRegions());
+ mPaths.appendVector(recorder.getPaths());
+ mMatrices.appendVector(recorder.getMatrices());
+}
+
+void DisplayList::init() {
+ mSize = 0;
+ mIsRenderable = true;
+ mFunctorCount = 0;
+ mLeft = 0;
+ mTop = 0;
+ mRight = 0;
+ mBottom = 0;
+ mClipChildren = true;
+ mAlpha = 1;
+ mHasOverlappingRendering = true;
+ mTranslationX = 0;
+ mTranslationY = 0;
+ mRotation = 0;
+ mRotationX = 0;
+ mRotationY= 0;
+ mScaleX = 1;
+ mScaleY = 1;
+ mPivotX = 0;
+ mPivotY = 0;
+ mCameraDistance = 0;
+ mMatrixDirty = false;
+ mMatrixFlags = 0;
+ mPrevWidth = -1;
+ mPrevHeight = -1;
+ mWidth = 0;
+ mHeight = 0;
+ mPivotExplicitlySet = false;
+ mCaching = false;
+}
+
+size_t DisplayList::getSize() {
+ return mSize;
+}
+
+/**
+ * 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.
+ */
+void DisplayList::output(uint32_t level) {
+ ALOGD("%*sStart display list (%p, %s, render=%d)", (level - 1) * 2, "", this,
+ mName.string(), isRenderable());
+ ALOGD("%*s%s %d", level * 2, "", "Save",
+ SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+
+ outputViewProperties(level);
+ int flags = DisplayListOp::kOpLogFlag_Recurse;
+ for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
+ mDisplayListData->displayListOps[i]->output(level, flags);
+ }
+
+ ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, mName.string());
+}
+
+float DisplayList::getPivotX() {
+ updateMatrix();
+ return mPivotX;
+}
+
+float DisplayList::getPivotY() {
+ updateMatrix();
+ return mPivotY;
+}
+
+void DisplayList::updateMatrix() {
+ if (mMatrixDirty) {
+ if (!mTransformMatrix) {
+ mTransformMatrix = new SkMatrix();
+ }
+ if (mMatrixFlags == 0 || mMatrixFlags == TRANSLATION) {
+ mTransformMatrix->reset();
+ } else {
+ if (!mPivotExplicitlySet) {
+ if (mWidth != mPrevWidth || mHeight != mPrevHeight) {
+ mPrevWidth = mWidth;
+ mPrevHeight = mHeight;
+ mPivotX = mPrevWidth / 2;
+ mPivotY = mPrevHeight / 2;
+ }
+ }
+ if ((mMatrixFlags & ROTATION_3D) == 0) {
+ mTransformMatrix->setTranslate(mTranslationX, mTranslationY);
+ mTransformMatrix->preRotate(mRotation, mPivotX, mPivotY);
+ mTransformMatrix->preScale(mScaleX, mScaleY, mPivotX, mPivotY);
+ } else {
+ if (!mTransformCamera) {
+ mTransformCamera = new Sk3DView();
+ mTransformMatrix3D = new SkMatrix();
+ }
+ mTransformMatrix->reset();
+ mTransformCamera->save();
+ mTransformMatrix->preScale(mScaleX, mScaleY, mPivotX, mPivotY);
+ mTransformCamera->rotateX(mRotationX);
+ mTransformCamera->rotateY(mRotationY);
+ mTransformCamera->rotateZ(-mRotation);
+ mTransformCamera->getMatrix(mTransformMatrix3D);
+ mTransformMatrix3D->preTranslate(-mPivotX, -mPivotY);
+ mTransformMatrix3D->postTranslate(mPivotX + mTranslationX,
+ mPivotY + mTranslationY);
+ mTransformMatrix->postConcat(*mTransformMatrix3D);
+ mTransformCamera->restore();
+ }
+ }
+ mMatrixDirty = false;
+ }
+}
+
+void DisplayList::outputViewProperties(const int level) {
+ updateMatrix();
+ if (mLeft != 0 || mTop != 0) {
+ ALOGD("%*sTranslate (left, top) %d, %d", level * 2, "", mLeft, mTop);
+ }
+ if (mStaticMatrix) {
+ ALOGD("%*sConcatMatrix (static) %p: " MATRIX_STRING,
+ level * 2, "", mStaticMatrix, MATRIX_ARGS(mStaticMatrix));
+ }
+ if (mAnimationMatrix) {
+ ALOGD("%*sConcatMatrix (animation) %p: " MATRIX_STRING,
+ level * 2, "", mAnimationMatrix, MATRIX_ARGS(mStaticMatrix));
+ }
+ if (mMatrixFlags != 0) {
+ if (mMatrixFlags == TRANSLATION) {
+ ALOGD("%*sTranslate %f, %f", level * 2, "", mTranslationX, mTranslationY);
+ } else {
+ ALOGD("%*sConcatMatrix %p: " MATRIX_STRING,
+ level * 2, "", mTransformMatrix, MATRIX_ARGS(mTransformMatrix));
+ }
+ }
+ if (mAlpha < 1) {
+ if (mCaching) {
+ ALOGD("%*sSetOverrideLayerAlpha %.2f", level * 2, "", mAlpha);
+ } else if (!mHasOverlappingRendering) {
+ ALOGD("%*sScaleAlpha %.2f", level * 2, "", mAlpha);
+ } else {
+ int flags = SkCanvas::kHasAlphaLayer_SaveFlag;
+ if (mClipChildren) {
+ flags |= SkCanvas::kClipToLayer_SaveFlag;
+ }
+ ALOGD("%*sSaveLayerAlpha %.2f, %.2f, %.2f, %.2f, %d, 0x%x", level * 2, "",
+ (float) 0, (float) 0, (float) mRight - mLeft, (float) mBottom - mTop,
+ (int)(mAlpha * 255), flags);
+ }
+ }
+ if (mClipChildren && !mCaching) {
+ ALOGD("%*sClipRect %.2f, %.2f, %.2f, %.2f", level * 2, "", 0.0f, 0.0f,
+ (float) mRight - mLeft, (float) mBottom - mTop);
+ }
+}
+
+/*
+ * For property operations, we pass a savecount of 0, since the operations aren't part of the
+ * displaylist, and thus don't have to compensate for the record-time/playback-time discrepancy in
+ * base saveCount (i.e., how RestoreToCount uses saveCount + mCount)
+ */
+#define PROPERTY_SAVECOUNT 0
+
+template <class T>
+void DisplayList::setViewProperties(OpenGLRenderer& renderer, T& handler,
+ const int level) {
+#if DEBUG_DISPLAY_LIST
+ outputViewProperties(level);
+#endif
+ updateMatrix();
+ if (mLeft != 0 || mTop != 0) {
+ renderer.translate(mLeft, mTop);
+ }
+ if (mStaticMatrix) {
+ renderer.concatMatrix(mStaticMatrix);
+ } else if (mAnimationMatrix) {
+ renderer.concatMatrix(mAnimationMatrix);
+ }
+ if (mMatrixFlags != 0) {
+ if (mMatrixFlags == TRANSLATION) {
+ renderer.translate(mTranslationX, mTranslationY);
+ } else {
+ renderer.concatMatrix(mTransformMatrix);
+ }
+ }
+ if (mAlpha < 1) {
+ if (mCaching) {
+ renderer.setOverrideLayerAlpha(mAlpha);
+ } else if (!mHasOverlappingRendering) {
+ renderer.scaleAlpha(mAlpha);
+ } else {
+ // TODO: should be able to store the size of a DL at record time and not
+ // have to pass it into this call. In fact, this information might be in the
+ // location/size info that we store with the new native transform data.
+ int saveFlags = SkCanvas::kHasAlphaLayer_SaveFlag;
+ if (mClipChildren) {
+ saveFlags |= SkCanvas::kClipToLayer_SaveFlag;
+ }
+ handler(mSaveLayerOp->reinit(0, 0, mRight - mLeft, mBottom - mTop,
+ mAlpha * 255, SkXfermode::kSrcOver_Mode, saveFlags), PROPERTY_SAVECOUNT);
+ }
+ }
+ if (mClipChildren && !mCaching) {
+ handler(mClipRectOp->reinit(0, 0, mRight - mLeft, mBottom - mTop, SkRegion::kIntersect_Op),
+ PROPERTY_SAVECOUNT);
+ }
+}
+
+class DeferOperationHandler {
+public:
+ DeferOperationHandler(DeferStateStruct& deferStruct, int level)
+ : mDeferStruct(deferStruct), mLevel(level) {}
+ inline void operator()(DisplayListOp* operation, int saveCount) {
+ operation->defer(mDeferStruct, saveCount, mLevel);
+ }
+private:
+ DeferStateStruct& mDeferStruct;
+ const int mLevel;
+};
+
+void DisplayList::defer(DeferStateStruct& deferStruct, const int level) {
+ DeferOperationHandler handler(deferStruct, level);
+ iterate<DeferOperationHandler>(deferStruct.mRenderer, handler, level);
+}
+
+class ReplayOperationHandler {
+public:
+ ReplayOperationHandler(ReplayStateStruct& replayStruct, int level)
+ : mReplayStruct(replayStruct), mLevel(level) {}
+ inline void operator()(DisplayListOp* operation, int saveCount) {
+#if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
+ mReplayStruct.mRenderer.eventMark(operation->name());
+#endif
+ operation->replay(mReplayStruct, saveCount, mLevel);
+ }
+private:
+ ReplayStateStruct& mReplayStruct;
+ const int mLevel;
+};
+
+void DisplayList::replay(ReplayStateStruct& replayStruct, const int level) {
+ ReplayOperationHandler handler(replayStruct, level);
+
+ replayStruct.mRenderer.startMark(mName.string());
+ iterate<ReplayOperationHandler>(replayStruct.mRenderer, handler, level);
+ replayStruct.mRenderer.endMark();
+
+ DISPLAY_LIST_LOGD("%*sDone (%p, %s), returning %d", level * 2, "", this, mName.string(),
+ replayStruct.mDrawGlStatus);
+}
+
+/**
+ * This function serves both defer and replay modes, and will organize the displayList's component
+ * operations for a single frame:
+ *
+ * Every 'simple' operation that affects just the matrix and alpha (or other factors of
+ * DeferredDisplayState) may be issued directly to the renderer, but complex operations (with custom
+ * defer logic) and operations in displayListOps are issued through the 'handler' which handles the
+ * defer vs replay logic, per operation
+ */
+template <class T>
+void DisplayList::iterate(OpenGLRenderer& renderer, T& handler, const int level) {
+ if (mSize == 0 || mAlpha <= 0) {
+ DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", level * 2, "", this, mName.string());
+ return;
+ }
+
+#if DEBUG_DISPLAY_LIST
+ Rect* clipRect = renderer.getClipRect();
+ DISPLAY_LIST_LOGD("%*sStart display list (%p, %s), clipRect: %.0f, %.f, %.0f, %.0f",
+ level * 2, "", this, mName.string(), clipRect->left, clipRect->top,
+ clipRect->right, clipRect->bottom);
+#endif
+
+ int restoreTo = renderer.getSaveCount();
+ handler(mSaveOp->reinit(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
+ PROPERTY_SAVECOUNT);
+
+ DISPLAY_LIST_LOGD("%*sSave %d %d", (level + 1) * 2, "",
+ SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag, restoreTo);
+
+ setViewProperties<T>(renderer, handler, level + 1);
+
+ if (renderer.quickRejectNoScissor(0, 0, mWidth, mHeight)) {
+ DISPLAY_LIST_LOGD("%*sRestoreToCount %d", level * 2, "", restoreTo);
+ handler(mRestoreToCountOp->reinit(restoreTo), PROPERTY_SAVECOUNT);
+ return;
+ }
+
+ DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
+ int saveCount = renderer.getSaveCount() - 1;
+ for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
+ DisplayListOp *op = mDisplayListData->displayListOps[i];
+
+ handler(op, saveCount);
+ logBuffer.writeCommand(level, op->name());
+ }
+
+ DISPLAY_LIST_LOGD("%*sRestoreToCount %d", (level + 1) * 2, "", restoreTo);
+ handler(mRestoreToCountOp->reinit(restoreTo), PROPERTY_SAVECOUNT);
+ renderer.restoreToCount(restoreTo);
+ renderer.setOverrideLayerAlpha(1.0f);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
new file mode 100644
index 000000000000..84f20abe0249
--- /dev/null
+++ b/libs/hwui/DisplayList.h
@@ -0,0 +1,543 @@
+/*
+ * 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_DISPLAY_LIST_H
+#define ANDROID_HWUI_DISPLAY_LIST_H
+
+#ifndef LOG_TAG
+ #define LOG_TAG "OpenGLRenderer"
+#endif
+
+#include <SkCamera.h>
+#include <SkMatrix.h>
+
+#include <private/hwui/DrawGlInfo.h>
+
+#include <utils/RefBase.h>
+#include <utils/SortedVector.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <cutils/compiler.h>
+
+#include "utils/LinearAllocator.h"
+
+#include "Debug.h"
+
+#define TRANSLATION 0x0001
+#define ROTATION 0x0002
+#define ROTATION_3D 0x0004
+#define SCALE 0x0008
+#define PIVOT 0x0010
+
+class SkBitmap;
+class SkPaint;
+class SkPath;
+class SkRegion;
+
+namespace android {
+namespace uirenderer {
+
+class DeferredDisplayList;
+class DisplayListOp;
+class DisplayListRenderer;
+class OpenGLRenderer;
+class Rect;
+class Layer;
+class SkiaColorFilter;
+class SkiaShader;
+
+class ClipRectOp;
+class SaveLayerOp;
+class SaveOp;
+class RestoreToCountOp;
+
+struct DeferStateStruct {
+ DeferStateStruct(DeferredDisplayList& deferredList, OpenGLRenderer& renderer, int replayFlags)
+ : mDeferredList(deferredList), mRenderer(renderer), mReplayFlags(replayFlags) {}
+ DeferredDisplayList& mDeferredList;
+ OpenGLRenderer& mRenderer;
+ const int mReplayFlags;
+};
+
+struct ReplayStateStruct {
+ ReplayStateStruct(OpenGLRenderer& renderer, Rect& dirty, int replayFlags)
+ : mRenderer(renderer), mDirty(dirty), mReplayFlags(replayFlags),
+ mDrawGlStatus(DrawGlInfo::kStatusDone) {}
+ OpenGLRenderer& mRenderer;
+ Rect& mDirty;
+ const int mReplayFlags;
+ status_t mDrawGlStatus;
+};
+
+/**
+ * Refcounted structure that holds data used in display list stream
+ */
+class DisplayListData : public LightRefBase<DisplayListData> {
+public:
+ LinearAllocator allocator;
+ Vector<DisplayListOp*> displayListOps;
+};
+
+/**
+ * Replays recorded drawing commands.
+ */
+class DisplayList {
+public:
+ DisplayList(const DisplayListRenderer& recorder);
+ ANDROID_API ~DisplayList();
+
+ // See flags defined in DisplayList.java
+ enum ReplayFlag {
+ kReplayFlag_ClipChildren = 0x1
+ };
+
+
+ ANDROID_API size_t getSize();
+ ANDROID_API static void destroyDisplayListDeferred(DisplayList* displayList);
+ ANDROID_API static void outputLogBuffer(int fd);
+
+ void initFromDisplayListRenderer(const DisplayListRenderer& recorder, bool reusing = false);
+
+
+ void defer(DeferStateStruct& deferStruct, const int level);
+ void replay(ReplayStateStruct& replayStruct, const int level);
+
+ void output(uint32_t level = 0);
+
+ ANDROID_API void reset();
+
+ void setRenderable(bool renderable) {
+ mIsRenderable = renderable;
+ }
+
+ bool isRenderable() const {
+ return mIsRenderable;
+ }
+
+ void setName(const char* name) {
+ if (name) {
+ mName.setTo(name);
+ }
+ }
+
+ const char* getName() const {
+ return mName.string();
+ }
+
+ void setClipChildren(bool clipChildren) {
+ mClipChildren = clipChildren;
+ }
+
+ void setStaticMatrix(SkMatrix* matrix) {
+ delete mStaticMatrix;
+ mStaticMatrix = new SkMatrix(*matrix);
+ }
+
+ // Can return NULL
+ SkMatrix* getStaticMatrix() {
+ return mStaticMatrix;
+ }
+
+ void setAnimationMatrix(SkMatrix* matrix) {
+ delete mAnimationMatrix;
+ if (matrix) {
+ mAnimationMatrix = new SkMatrix(*matrix);
+ } else {
+ mAnimationMatrix = NULL;
+ }
+ }
+
+ void setAlpha(float alpha) {
+ alpha = fminf(1.0f, fmaxf(0.0f, alpha));
+ if (alpha != mAlpha) {
+ mAlpha = alpha;
+ }
+ }
+
+ float getAlpha() const {
+ return mAlpha;
+ }
+
+ void setHasOverlappingRendering(bool hasOverlappingRendering) {
+ mHasOverlappingRendering = hasOverlappingRendering;
+ }
+
+ bool hasOverlappingRendering() const {
+ return mHasOverlappingRendering;
+ }
+
+ void setTranslationX(float translationX) {
+ if (translationX != mTranslationX) {
+ mTranslationX = translationX;
+ mMatrixDirty = true;
+ if (mTranslationX == 0.0f && mTranslationY == 0.0f) {
+ mMatrixFlags &= ~TRANSLATION;
+ } else {
+ mMatrixFlags |= TRANSLATION;
+ }
+ }
+ }
+
+ float getTranslationX() const {
+ return mTranslationX;
+ }
+
+ void setTranslationY(float translationY) {
+ if (translationY != mTranslationY) {
+ mTranslationY = translationY;
+ mMatrixDirty = true;
+ if (mTranslationX == 0.0f && mTranslationY == 0.0f) {
+ mMatrixFlags &= ~TRANSLATION;
+ } else {
+ mMatrixFlags |= TRANSLATION;
+ }
+ }
+ }
+
+ float getTranslationY() const {
+ return mTranslationY;
+ }
+
+ void setRotation(float rotation) {
+ if (rotation != mRotation) {
+ mRotation = rotation;
+ mMatrixDirty = true;
+ if (mRotation == 0.0f) {
+ mMatrixFlags &= ~ROTATION;
+ } else {
+ mMatrixFlags |= ROTATION;
+ }
+ }
+ }
+
+ float getRotation() const {
+ return mRotation;
+ }
+
+ void setRotationX(float rotationX) {
+ if (rotationX != mRotationX) {
+ mRotationX = rotationX;
+ mMatrixDirty = true;
+ if (mRotationX == 0.0f && mRotationY == 0.0f) {
+ mMatrixFlags &= ~ROTATION_3D;
+ } else {
+ mMatrixFlags |= ROTATION_3D;
+ }
+ }
+ }
+
+ float getRotationX() const {
+ return mRotationX;
+ }
+
+ void setRotationY(float rotationY) {
+ if (rotationY != mRotationY) {
+ mRotationY = rotationY;
+ mMatrixDirty = true;
+ if (mRotationX == 0.0f && mRotationY == 0.0f) {
+ mMatrixFlags &= ~ROTATION_3D;
+ } else {
+ mMatrixFlags |= ROTATION_3D;
+ }
+ }
+ }
+
+ float getRotationY() const {
+ return mRotationY;
+ }
+
+ void setScaleX(float scaleX) {
+ if (scaleX != mScaleX) {
+ mScaleX = scaleX;
+ mMatrixDirty = true;
+ if (mScaleX == 1.0f && mScaleY == 1.0f) {
+ mMatrixFlags &= ~SCALE;
+ } else {
+ mMatrixFlags |= SCALE;
+ }
+ }
+ }
+
+ float getScaleX() const {
+ return mScaleX;
+ }
+
+ void setScaleY(float scaleY) {
+ if (scaleY != mScaleY) {
+ mScaleY = scaleY;
+ mMatrixDirty = true;
+ if (mScaleX == 1.0f && mScaleY == 1.0f) {
+ mMatrixFlags &= ~SCALE;
+ } else {
+ mMatrixFlags |= SCALE;
+ }
+ }
+ }
+
+ float getScaleY() const {
+ return mScaleY;
+ }
+
+ void setPivotX(float pivotX) {
+ mPivotX = pivotX;
+ mMatrixDirty = true;
+ if (mPivotX == 0.0f && mPivotY == 0.0f) {
+ mMatrixFlags &= ~PIVOT;
+ } else {
+ mMatrixFlags |= PIVOT;
+ }
+ mPivotExplicitlySet = true;
+ }
+
+ ANDROID_API float getPivotX();
+
+ void setPivotY(float pivotY) {
+ mPivotY = pivotY;
+ mMatrixDirty = true;
+ if (mPivotX == 0.0f && mPivotY == 0.0f) {
+ mMatrixFlags &= ~PIVOT;
+ } else {
+ mMatrixFlags |= PIVOT;
+ }
+ mPivotExplicitlySet = true;
+ }
+
+ ANDROID_API float getPivotY();
+
+ void setCameraDistance(float distance) {
+ if (distance != mCameraDistance) {
+ mCameraDistance = distance;
+ mMatrixDirty = true;
+ if (!mTransformCamera) {
+ mTransformCamera = new Sk3DView();
+ mTransformMatrix3D = new SkMatrix();
+ }
+ mTransformCamera->setCameraLocation(0, 0, distance);
+ }
+ }
+
+ float getCameraDistance() const {
+ return mCameraDistance;
+ }
+
+ void setLeft(int left) {
+ if (left != mLeft) {
+ mLeft = left;
+ mWidth = mRight - mLeft;
+ if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
+ mMatrixDirty = true;
+ }
+ }
+ }
+
+ float getLeft() const {
+ return mLeft;
+ }
+
+ void setTop(int top) {
+ if (top != mTop) {
+ mTop = top;
+ mHeight = mBottom - mTop;
+ if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
+ mMatrixDirty = true;
+ }
+ }
+ }
+
+ float getTop() const {
+ return mTop;
+ }
+
+ void setRight(int right) {
+ if (right != mRight) {
+ mRight = right;
+ mWidth = mRight - mLeft;
+ if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
+ mMatrixDirty = true;
+ }
+ }
+ }
+
+ float getRight() const {
+ return mRight;
+ }
+
+ void setBottom(int bottom) {
+ if (bottom != mBottom) {
+ mBottom = bottom;
+ mHeight = mBottom - mTop;
+ if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
+ mMatrixDirty = true;
+ }
+ }
+ }
+
+ float getBottom() const {
+ return mBottom;
+ }
+
+ void setLeftTop(int left, int top) {
+ if (left != mLeft || top != mTop) {
+ mLeft = left;
+ mTop = top;
+ mWidth = mRight - mLeft;
+ mHeight = mBottom - mTop;
+ if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
+ mMatrixDirty = true;
+ }
+ }
+ }
+
+ void setLeftTopRightBottom(int left, int top, int right, int bottom) {
+ if (left != mLeft || top != mTop || right != mRight || bottom != mBottom) {
+ mLeft = left;
+ mTop = top;
+ mRight = right;
+ mBottom = bottom;
+ mWidth = mRight - mLeft;
+ mHeight = mBottom - mTop;
+ if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
+ mMatrixDirty = true;
+ }
+ }
+ }
+
+ void offsetLeftRight(float offset) {
+ if (offset != 0) {
+ mLeft += offset;
+ mRight += offset;
+ if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
+ mMatrixDirty = true;
+ }
+ }
+ }
+
+ void offsetTopBottom(float offset) {
+ if (offset != 0) {
+ mTop += offset;
+ mBottom += offset;
+ if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
+ mMatrixDirty = true;
+ }
+ }
+ }
+
+ void setCaching(bool caching) {
+ mCaching = caching;
+ }
+
+ int getWidth() {
+ return mWidth;
+ }
+
+ int getHeight() {
+ return mHeight;
+ }
+
+private:
+ void outputViewProperties(const int level);
+
+ template <class T>
+ inline void setViewProperties(OpenGLRenderer& renderer, T& handler, const int level);
+
+ template <class T>
+ inline void iterate(OpenGLRenderer& renderer, T& handler, const int level);
+
+ void init();
+
+ void clearResources();
+
+ void updateMatrix();
+
+ class TextContainer {
+ public:
+ size_t length() const {
+ return mByteLength;
+ }
+
+ const char* text() const {
+ return (const char*) mText;
+ }
+
+ size_t mByteLength;
+ const char* mText;
+ };
+
+ Vector<SkBitmap*> mBitmapResources;
+ Vector<SkBitmap*> mOwnedBitmapResources;
+ Vector<SkiaColorFilter*> mFilterResources;
+
+ Vector<SkPaint*> mPaints;
+ Vector<SkPath*> mPaths;
+ SortedVector<SkPath*> mSourcePaths;
+ Vector<SkRegion*> mRegions;
+ Vector<SkMatrix*> mMatrices;
+ Vector<SkiaShader*> mShaders;
+ Vector<Layer*> mLayers;
+
+ sp<DisplayListData> mDisplayListData;
+
+ size_t mSize;
+
+ bool mIsRenderable;
+ uint32_t mFunctorCount;
+
+ String8 mName;
+
+ // View properties
+ bool mClipChildren;
+ float mAlpha;
+ bool mHasOverlappingRendering;
+ float mTranslationX, mTranslationY;
+ float mRotation, mRotationX, mRotationY;
+ float mScaleX, mScaleY;
+ float mPivotX, mPivotY;
+ float mCameraDistance;
+ int mLeft, mTop, mRight, mBottom;
+ int mWidth, mHeight;
+ int mPrevWidth, mPrevHeight;
+ bool mPivotExplicitlySet;
+ bool mMatrixDirty;
+ bool mMatrixIsIdentity;
+ uint32_t mMatrixFlags;
+ SkMatrix* mTransformMatrix;
+ Sk3DView* mTransformCamera;
+ SkMatrix* mTransformMatrix3D;
+ SkMatrix* mStaticMatrix;
+ SkMatrix* mAnimationMatrix;
+ bool mCaching;
+
+ /**
+ * State operations - needed to defer displayList property operations (for example, when setting
+ * an alpha causes a SaveLayerAlpha to occur). These operations point into mDisplayListData's
+ * allocation, or null if uninitialized.
+ *
+ * These are initialized (via friend constructors) when a displayList is issued in either replay
+ * or deferred mode. If replaying, the ops are not used until the next frame. If deferring, the
+ * ops may be stored in the DeferredDisplayList to be played back a second time.
+ *
+ * They should be used at most once per frame (one call to iterate)
+ */
+ ClipRectOp* mClipRectOp;
+ SaveLayerOp* mSaveLayerOp;
+ SaveOp* mSaveOp;
+ RestoreToCountOp* mRestoreToCountOp;
+}; // class DisplayList
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_OPENGL_RENDERER_H
diff --git a/libs/hwui/DisplayListLogBuffer.cpp b/libs/hwui/DisplayListLogBuffer.cpp
index f204644d5ec0..f039fcd9571f 100644
--- a/libs/hwui/DisplayListLogBuffer.cpp
+++ b/libs/hwui/DisplayListLogBuffer.cpp
@@ -18,9 +18,8 @@
// BUFFER_SIZE size must be one more than a multiple of COMMAND_SIZE to ensure
// that mStart always points at the next command, not just the next item
-#define COMMAND_SIZE 2
#define NUM_COMMANDS 50
-#define BUFFER_SIZE ((NUM_COMMANDS * COMMAND_SIZE) + 1)
+#define BUFFER_SIZE ((NUM_COMMANDS) + 1)
/**
* DisplayListLogBuffer is a utility class which logs the most recent display
@@ -57,7 +56,7 @@ namespace uirenderer {
DisplayListLogBuffer::DisplayListLogBuffer() {
- mBufferFirst = (int*) malloc(BUFFER_SIZE * sizeof(int));
+ mBufferFirst = (OpLog*) malloc(BUFFER_SIZE * sizeof(OpLog));
mStart = mBufferFirst;
mBufferLast = mBufferFirst + BUFFER_SIZE - 1;
mEnd = mStart;
@@ -71,42 +70,30 @@ DisplayListLogBuffer::~DisplayListLogBuffer() {
* Called from DisplayListRenderer to output the current buffer into the
* specified FILE. This only happens in a dumpsys/bugreport operation.
*/
-void DisplayListLogBuffer::outputCommands(FILE *file, const char* opNames[])
+void DisplayListLogBuffer::outputCommands(FILE *file)
{
- int *tmpBufferPtr = mStart;
+ OpLog* tmpBufferPtr = mStart;
while (true) {
if (tmpBufferPtr == mEnd) {
break;
}
- int level = *tmpBufferPtr++;
+ OpLog* nextOp = tmpBufferPtr++;
if (tmpBufferPtr > mBufferLast) {
tmpBufferPtr = mBufferFirst;
}
- int op = *tmpBufferPtr++;
- if (tmpBufferPtr > mBufferLast) {
- tmpBufferPtr = mBufferFirst;
- }
- uint32_t count = (level + 1) * 2;
- char indent[count + 1];
- for (uint32_t i = 0; i < count; i++) {
- indent[i] = ' ';
- }
- indent[count] = '\0';
- fprintf(file, "%s%s\n", indent, opNames[op]);
- }
-}
-void DisplayListLogBuffer::writeCommand(int level, int op) {
- writeInt(level);
- writeInt(op);
+ fprintf(file, "%*s%s\n", tmpBufferPtr->level*2, "", tmpBufferPtr->label);
+ }
}
/**
- * Store the given value in the buffer and increment/wrap the mEnd
- * and mStart values as appropriate.
+ * Store the given level and label in the buffer and increment/wrap the mEnd
+ * and mStart values as appropriate. Label should point to static memory.
*/
-void DisplayListLogBuffer::writeInt(int value) {
- *((int*)mEnd) = value;
+void DisplayListLogBuffer::writeCommand(int level, const char* label) {
+ mEnd->level = level;
+ mEnd->label = label;
+
if (mEnd == mBufferLast) {
mEnd = mBufferFirst;
} else {
diff --git a/libs/hwui/DisplayListLogBuffer.h b/libs/hwui/DisplayListLogBuffer.h
index 5d689bb82363..c884789e7f40 100644
--- a/libs/hwui/DisplayListLogBuffer.h
+++ b/libs/hwui/DisplayListLogBuffer.h
@@ -31,19 +31,23 @@ class DisplayListLogBuffer: public Singleton<DisplayListLogBuffer> {
friend class Singleton<DisplayListLogBuffer>;
public:
- void writeCommand(int level, int op);
- void writeInt(int value);
- void outputCommands(FILE *file, const char* opNames[]);
+ void writeCommand(int level, const char* label);
+ void outputCommands(FILE *file);
bool isEmpty() {
return (mStart == mEnd);
}
+ struct OpLog {
+ int level;
+ const char* label;
+ };
+
private:
- int *mBufferFirst; // where the memory starts
- int* mStart; // where the current command stream starts
- int* mEnd; // where the current commands end
- int* mBufferLast; // where the buffer memory ends
+ OpLog* mBufferFirst; // where the memory starts
+ OpLog* mStart; // where the current command stream starts
+ OpLog* mEnd; // where the current commands end
+ OpLog* mBufferLast; // where the buffer memory ends
};
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
new file mode 100644
index 000000000000..a5dee9f3dc9d
--- /dev/null
+++ b/libs/hwui/DisplayListOp.h
@@ -0,0 +1,1321 @@
+/*
+ * 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_DISPLAY_OPERATION_H
+#define ANDROID_HWUI_DISPLAY_OPERATION_H
+
+#ifndef LOG_TAG
+ #define LOG_TAG "OpenGLRenderer"
+#endif
+
+#include <SkXfermode.h>
+
+#include <private/hwui/DrawGlInfo.h>
+
+#include "OpenGLRenderer.h"
+#include "DeferredDisplayList.h"
+#include "DisplayListRenderer.h"
+#include "utils/LinearAllocator.h"
+
+#define CRASH() do { \
+ *(int *)(uintptr_t)0xbbadbeef = 0; \
+ ((void(*)())0)(); /* More reliable, but doesn't say BBADBEEF */ \
+} while(false)
+
+#define MATRIX_STRING "[%.2f %.2f %.2f] [%.2f %.2f %.2f] [%.2f %.2f %.2f]"
+#define MATRIX_ARGS(m) \
+ m->get(0), m->get(1), m->get(2), \
+ m->get(3), m->get(4), m->get(5), \
+ m->get(6), m->get(7), m->get(8)
+#define RECT_STRING "%.2f %.2f %.2f %.2f"
+#define RECT_ARGS(r) \
+ r.left, r.top, r.right, r.bottom
+
+// Use OP_LOG for logging with arglist, OP_LOGS if just printing char*
+#define OP_LOGS(s) OP_LOG("%s", s)
+#define OP_LOG(s, ...) ALOGD( "%*s" s, level * 2, "", __VA_ARGS__ )
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Structure for storing canvas operations when they are recorded into a DisplayList, so that they
+ * may be replayed to an OpenGLRenderer.
+ *
+ * To avoid individual memory allocations, DisplayListOps may only be allocated into a
+ * LinearAllocator's managed memory buffers. Each pointer held by a DisplayListOp is either a
+ * pointer into memory also allocated in the LinearAllocator (mostly for text and float buffers) or
+ * references a externally refcounted object (Sk... and Skia... objects). ~DisplayListOp() is
+ * never called as LinearAllocators are simply discarded, so no memory management should be done in
+ * this class.
+ */
+class DisplayListOp {
+public:
+ // These objects should always be allocated with a LinearAllocator, and never destroyed/deleted.
+ // standard new() intentionally not implemented, and delete/deconstructor should never be used.
+ virtual ~DisplayListOp() { CRASH(); }
+ static void operator delete(void* ptr) { CRASH(); }
+ /** static void* operator new(size_t size); PURPOSELY OMITTED **/
+ static void* operator new(size_t size, LinearAllocator& allocator) {
+ return allocator.alloc(size);
+ }
+
+ enum OpLogFlag {
+ kOpLogFlag_Recurse = 0x1,
+ kOpLogFlag_JSON = 0x2 // TODO: add?
+ };
+
+ virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level) = 0;
+
+ virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level) = 0;
+
+ virtual void output(int level, uint32_t logFlags = 0) = 0;
+
+ // NOTE: it would be nice to declare constants and overriding the implementation in each op to
+ // point at the constants, but that seems to require a .cpp file
+ virtual const char* name() = 0;
+
+ /**
+ * Stores the relevant canvas state of the object between deferral and replay (if the canvas
+ * state supports being stored) See OpenGLRenderer::simpleClipAndState()
+ *
+ * TODO: don't reserve space for StateOps that won't be deferred
+ */
+ DeferredDisplayState state;
+
+};
+
+class StateOp : public DisplayListOp {
+public:
+ StateOp() {};
+
+ virtual ~StateOp() {}
+
+ virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level) {
+ // default behavior only affects immediate, deferrable state, issue directly to renderer
+ applyState(deferStruct.mRenderer, saveCount);
+ }
+
+ /**
+ * State operations are applied directly to the renderer, but can cause the deferred drawing op
+ * list to flush
+ */
+ virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level) {
+ applyState(replayStruct.mRenderer, saveCount);
+ }
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const = 0;
+};
+
+class DrawOp : public DisplayListOp {
+public:
+ DrawOp(SkPaint* paint)
+ : mPaint(paint), mQuickRejected(false) {}
+
+ virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level) {
+ if (mQuickRejected &&
+ CC_LIKELY(deferStruct.mReplayFlags & DisplayList::kReplayFlag_ClipChildren)) {
+ return;
+ }
+
+ if (!getLocalBounds(state.mBounds)) {
+ // empty bounds signify bounds can't be calculated
+ state.mBounds.setEmpty();
+ }
+
+ deferStruct.mDeferredList.addDrawOp(deferStruct.mRenderer, this);
+ }
+
+ virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level) {
+ if (mQuickRejected &&
+ CC_LIKELY(replayStruct.mReplayFlags & DisplayList::kReplayFlag_ClipChildren)) {
+ return;
+ }
+
+ replayStruct.mDrawGlStatus |= applyDraw(replayStruct.mRenderer, replayStruct.mDirty, level);
+ }
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) = 0;
+
+ virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
+ }
+
+ // returns true if bounds exist
+ virtual bool getLocalBounds(Rect& localBounds) { return false; }
+
+ // TODO: better refine localbounds usage
+ void setQuickRejected(bool quickRejected) { mQuickRejected = quickRejected; }
+ bool getQuickRejected() { return mQuickRejected; }
+
+ /** Batching disabled by default, turned on for individual ops */
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ return DeferredDisplayList::kOpBatch_None;
+ }
+
+ float strokeWidthOutset() {
+ float width = mPaint->getStrokeWidth();
+ if (width == 0) return 0.5f; // account for hairline
+ return width * 0.5f;
+ }
+
+protected:
+ SkPaint* getPaint(OpenGLRenderer& renderer) {
+ return renderer.filterPaint(mPaint);
+ }
+
+ SkPaint* mPaint; // should be accessed via getPaint() when applying
+ bool mQuickRejected;
+};
+
+class DrawBoundedOp : public DrawOp {
+public:
+ DrawBoundedOp(float left, float top, float right, float bottom, SkPaint* paint)
+ : DrawOp(paint), mLocalBounds(left, top, right, bottom) {}
+
+ // Calculates bounds as smallest rect encompassing all points
+ // NOTE: requires at least 1 vertex, and doesn't account for stroke size (should be handled in
+ // subclass' constructor)
+ DrawBoundedOp(const float* points, int count, SkPaint* paint)
+ : DrawOp(paint), mLocalBounds(points[0], points[1], points[0], points[1]) {
+ for (int i = 2; i < count; i += 2) {
+ mLocalBounds.left = fminf(mLocalBounds.left, points[i]);
+ mLocalBounds.right = fmaxf(mLocalBounds.right, points[i]);
+ mLocalBounds.top = fminf(mLocalBounds.top, points[i + 1]);
+ mLocalBounds.bottom = fmaxf(mLocalBounds.bottom, points[i + 1]);
+ }
+ }
+
+ // default empty constructor for bounds, to be overridden in child constructor body
+ DrawBoundedOp(SkPaint* paint)
+ : DrawOp(paint) {}
+
+ bool getLocalBounds(Rect& localBounds) {
+ localBounds.set(mLocalBounds);
+ return true;
+ }
+
+protected:
+ Rect mLocalBounds; // displayed area in LOCAL coord. doesn't incorporate stroke, so check paint
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// STATE OPERATIONS - these may affect the state of the canvas/renderer, but do
+// not directly draw or alter output
+///////////////////////////////////////////////////////////////////////////////
+
+class SaveOp : public StateOp {
+ friend class DisplayList; // give DisplayList private constructor/reinit access
+public:
+ SaveOp(int flags)
+ : mFlags(flags) {}
+
+ virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level) {
+ int newSaveCount = deferStruct.mRenderer.save(mFlags);
+ deferStruct.mDeferredList.addSave(deferStruct.mRenderer, this, newSaveCount);
+ }
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.save(mFlags);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Save flags %x", mFlags);
+ }
+
+ virtual const char* name() { return "Save"; }
+
+ int getFlags() const { return mFlags; }
+private:
+ SaveOp() {}
+ DisplayListOp* reinit(int flags) {
+ mFlags = flags;
+ return this;
+ }
+
+ int mFlags;
+};
+
+class RestoreToCountOp : public StateOp {
+ friend class DisplayList; // give DisplayList private constructor/reinit access
+public:
+ RestoreToCountOp(int count)
+ : mCount(count) {}
+
+ virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level) {
+ deferStruct.mDeferredList.addRestoreToCount(deferStruct.mRenderer,
+ this, saveCount + mCount);
+ deferStruct.mRenderer.restoreToCount(saveCount + mCount);
+ }
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.restoreToCount(saveCount + mCount);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Restore to count %d", mCount);
+ }
+
+ virtual const char* name() { return "RestoreToCount"; }
+
+private:
+ RestoreToCountOp() {}
+ DisplayListOp* reinit(int count) {
+ mCount = count;
+ return this;
+ }
+
+ int mCount;
+};
+
+class SaveLayerOp : public StateOp {
+ friend class DisplayList; // give DisplayList private constructor/reinit access
+public:
+ SaveLayerOp(float left, float top, float right, float bottom,
+ int alpha, SkXfermode::Mode mode, int flags)
+ : mArea(left, top, right, bottom), mAlpha(alpha), mMode(mode), mFlags(flags) {}
+
+ virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level) {
+ // NOTE: don't bother with actual saveLayer, instead issuing it at flush time
+ int newSaveCount = deferStruct.mRenderer.getSaveCount();
+ deferStruct.mDeferredList.addSaveLayer(deferStruct.mRenderer, this, newSaveCount);
+
+ // NOTE: don't issue full saveLayer, since that has side effects/is costly. instead just
+ // setup the snapshot for deferral, and re-issue the op at flush time
+ deferStruct.mRenderer.saveLayerDeferred(mArea.left, mArea.top, mArea.right, mArea.bottom,
+ mAlpha, mMode, mFlags);
+ }
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.saveLayer(mArea.left, mArea.top, mArea.right, mArea.bottom, mAlpha, mMode, mFlags);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("SaveLayer%s of area " RECT_STRING,
+ (isSaveLayerAlpha() ? "Alpha" : ""),RECT_ARGS(mArea));
+ }
+
+ virtual const char* name() { return isSaveLayerAlpha() ? "SaveLayerAlpha" : "SaveLayer"; }
+
+ int getFlags() { return mFlags; }
+
+private:
+ // Special case, reserved for direct DisplayList usage
+ SaveLayerOp() {}
+ DisplayListOp* reinit(float left, float top, float right, float bottom,
+ int alpha, SkXfermode::Mode mode, int flags) {
+ mArea.set(left, top, right, bottom);
+ mAlpha = alpha;
+ mMode = mode;
+ mFlags = flags;
+ return this;
+ }
+
+ bool isSaveLayerAlpha() { return mAlpha < 255 && mMode == SkXfermode::kSrcOver_Mode; }
+ Rect mArea;
+ int mAlpha;
+ SkXfermode::Mode mMode;
+ int mFlags;
+};
+
+class TranslateOp : public StateOp {
+public:
+ TranslateOp(float dx, float dy)
+ : mDx(dx), mDy(dy) {}
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.translate(mDx, mDy);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Translate by %f %f", mDx, mDy);
+ }
+
+ virtual const char* name() { return "Translate"; }
+
+private:
+ float mDx;
+ float mDy;
+};
+
+class RotateOp : public StateOp {
+public:
+ RotateOp(float degrees)
+ : mDegrees(degrees) {}
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.rotate(mDegrees);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Rotate by %f degrees", mDegrees);
+ }
+
+ virtual const char* name() { return "Rotate"; }
+
+private:
+ float mDegrees;
+};
+
+class ScaleOp : public StateOp {
+public:
+ ScaleOp(float sx, float sy)
+ : mSx(sx), mSy(sy) {}
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.scale(mSx, mSy);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Scale by %f %f", mSx, mSy);
+ }
+
+ virtual const char* name() { return "Scale"; }
+
+private:
+ float mSx;
+ float mSy;
+};
+
+class SkewOp : public StateOp {
+public:
+ SkewOp(float sx, float sy)
+ : mSx(sx), mSy(sy) {}
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.skew(mSx, mSy);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Skew by %f %f", mSx, mSy);
+ }
+
+ virtual const char* name() { return "Skew"; }
+
+private:
+ float mSx;
+ float mSy;
+};
+
+class SetMatrixOp : public StateOp {
+public:
+ SetMatrixOp(SkMatrix* matrix)
+ : mMatrix(matrix) {}
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.setMatrix(mMatrix);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("SetMatrix " MATRIX_STRING, MATRIX_ARGS(mMatrix));
+ }
+
+ virtual const char* name() { return "SetMatrix"; }
+
+private:
+ SkMatrix* mMatrix;
+};
+
+class ConcatMatrixOp : public StateOp {
+public:
+ ConcatMatrixOp(SkMatrix* matrix)
+ : mMatrix(matrix) {}
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.concatMatrix(mMatrix);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("ConcatMatrix " MATRIX_STRING, MATRIX_ARGS(mMatrix));
+ }
+
+ virtual const char* name() { return "ConcatMatrix"; }
+
+private:
+ SkMatrix* mMatrix;
+};
+
+class ClipOp : public StateOp {
+public:
+ ClipOp(SkRegion::Op op) : mOp(op) {}
+
+ virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level) {
+ // NOTE: must defer op BEFORE applying state, since it may read clip
+ deferStruct.mDeferredList.addClip(deferStruct.mRenderer, this);
+
+ // TODO: Can we avoid applying complex clips at defer time?
+ applyState(deferStruct.mRenderer, saveCount);
+ }
+
+ bool canCauseComplexClip() {
+ return ((mOp != SkRegion::kIntersect_Op) && (mOp != SkRegion::kReplace_Op)) || !isRect();
+ }
+
+protected:
+ ClipOp() {}
+ virtual bool isRect() { return false; }
+
+ SkRegion::Op mOp;
+};
+
+class ClipRectOp : public ClipOp {
+ friend class DisplayList; // give DisplayList private constructor/reinit access
+public:
+ ClipRectOp(float left, float top, float right, float bottom, SkRegion::Op op)
+ : ClipOp(op), mArea(left, top, right, bottom) {}
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.clipRect(mArea.left, mArea.top, mArea.right, mArea.bottom, mOp);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("ClipRect " RECT_STRING, RECT_ARGS(mArea));
+ }
+
+ virtual const char* name() { return "ClipRect"; }
+
+protected:
+ virtual bool isRect() { return true; }
+
+private:
+ ClipRectOp() {}
+ DisplayListOp* reinit(float left, float top, float right, float bottom, SkRegion::Op op) {
+ mOp = op;
+ mArea.set(left, top, right, bottom);
+ return this;
+ }
+
+ Rect mArea;
+};
+
+class ClipPathOp : public ClipOp {
+public:
+ ClipPathOp(SkPath* path, SkRegion::Op op)
+ : ClipOp(op), mPath(path) {}
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.clipPath(mPath, mOp);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ SkRect bounds = mPath->getBounds();
+ OP_LOG("ClipPath bounds " RECT_STRING,
+ bounds.left(), bounds.top(), bounds.right(), bounds.bottom());
+ }
+
+ virtual const char* name() { return "ClipPath"; }
+
+private:
+ SkPath* mPath;
+};
+
+class ClipRegionOp : public ClipOp {
+public:
+ ClipRegionOp(SkRegion* region, SkRegion::Op op)
+ : ClipOp(op), mRegion(region) {}
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.clipRegion(mRegion, mOp);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ SkIRect bounds = mRegion->getBounds();
+ OP_LOG("ClipRegion bounds %d %d %d %d",
+ bounds.left(), bounds.top(), bounds.right(), bounds.bottom());
+ }
+
+ virtual const char* name() { return "ClipRegion"; }
+
+private:
+ SkRegion* mRegion;
+ SkRegion::Op mOp;
+};
+
+class ResetShaderOp : public StateOp {
+public:
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.resetShader();
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOGS("ResetShader");
+ }
+
+ virtual const char* name() { return "ResetShader"; }
+};
+
+class SetupShaderOp : public StateOp {
+public:
+ SetupShaderOp(SkiaShader* shader)
+ : mShader(shader) {}
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.setupShader(mShader);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("SetupShader, shader %p", mShader);
+ }
+
+ virtual const char* name() { return "SetupShader"; }
+
+private:
+ SkiaShader* mShader;
+};
+
+class ResetColorFilterOp : public StateOp {
+public:
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.resetColorFilter();
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOGS("ResetColorFilter");
+ }
+
+ virtual const char* name() { return "ResetColorFilter"; }
+};
+
+class SetupColorFilterOp : public StateOp {
+public:
+ SetupColorFilterOp(SkiaColorFilter* colorFilter)
+ : mColorFilter(colorFilter) {}
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.setupColorFilter(mColorFilter);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("SetupColorFilter, filter %p", mColorFilter);
+ }
+
+ virtual const char* name() { return "SetupColorFilter"; }
+
+private:
+ SkiaColorFilter* mColorFilter;
+};
+
+class ResetShadowOp : public StateOp {
+public:
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.resetShadow();
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOGS("ResetShadow");
+ }
+
+ virtual const char* name() { return "ResetShadow"; }
+};
+
+class SetupShadowOp : public StateOp {
+public:
+ SetupShadowOp(float radius, float dx, float dy, int color)
+ : mRadius(radius), mDx(dx), mDy(dy), mColor(color) {}
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.setupShadow(mRadius, mDx, mDy, mColor);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("SetupShadow, radius %f, %f, %f, color %#x", mRadius, mDx, mDy, mColor);
+ }
+
+ virtual const char* name() { return "SetupShadow"; }
+
+private:
+ float mRadius;
+ float mDx;
+ float mDy;
+ int mColor;
+};
+
+class ResetPaintFilterOp : public StateOp {
+public:
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.resetPaintFilter();
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOGS("ResetPaintFilter");
+ }
+
+ virtual const char* name() { return "ResetPaintFilter"; }
+};
+
+class SetupPaintFilterOp : public StateOp {
+public:
+ SetupPaintFilterOp(int clearBits, int setBits)
+ : mClearBits(clearBits), mSetBits(setBits) {}
+
+ virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
+ renderer.setupPaintFilter(mClearBits, mSetBits);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("SetupPaintFilter, clear %#x, set %#x", mClearBits, mSetBits);
+ }
+
+ virtual const char* name() { return "SetupPaintFilter"; }
+
+private:
+ int mClearBits;
+ int mSetBits;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// DRAW OPERATIONS - these are operations that can draw to the canvas's device
+///////////////////////////////////////////////////////////////////////////////
+
+class DrawBitmapOp : public DrawBoundedOp {
+public:
+ DrawBitmapOp(SkBitmap* bitmap, float left, float top, SkPaint* paint)
+ : DrawBoundedOp(left, top, left + bitmap->width(), top + bitmap->height(),
+ paint),
+ mBitmap(bitmap) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawBitmap(mBitmap, mLocalBounds.left, mLocalBounds.top,
+ getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw bitmap %p at %f %f", mBitmap, mLocalBounds.left, mLocalBounds.top);
+ }
+
+ virtual const char* name() { return "DrawBitmap"; }
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ return DeferredDisplayList::kOpBatch_Bitmap;
+ }
+
+protected:
+ SkBitmap* mBitmap;
+};
+
+class DrawBitmapMatrixOp : public DrawBoundedOp {
+public:
+ DrawBitmapMatrixOp(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint)
+ : DrawBoundedOp(paint), mBitmap(bitmap), mMatrix(matrix) {
+ mLocalBounds.set(0, 0, bitmap->width(), bitmap->height());
+ const mat4 transform(*matrix);
+ transform.mapRect(mLocalBounds);
+ }
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawBitmap(mBitmap, mMatrix, getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw bitmap %p matrix " MATRIX_STRING, mBitmap, MATRIX_ARGS(mMatrix));
+ }
+
+ virtual const char* name() { return "DrawBitmap"; }
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ return DeferredDisplayList::kOpBatch_Bitmap;
+ }
+
+private:
+ SkBitmap* mBitmap;
+ SkMatrix* mMatrix;
+};
+
+class DrawBitmapRectOp : public DrawBoundedOp {
+public:
+ DrawBitmapRectOp(SkBitmap* bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom,
+ float dstLeft, float dstTop, float dstRight, float dstBottom, SkPaint* paint)
+ : DrawBoundedOp(dstLeft, dstTop, dstRight, dstBottom, paint),
+ mBitmap(bitmap), mSrc(srcLeft, srcTop, srcRight, srcBottom) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawBitmap(mBitmap, mSrc.left, mSrc.top, mSrc.right, mSrc.bottom,
+ mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom,
+ getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw bitmap %p src="RECT_STRING", dst="RECT_STRING,
+ mBitmap, RECT_ARGS(mSrc), RECT_ARGS(mLocalBounds));
+ }
+
+ virtual const char* name() { return "DrawBitmapRect"; }
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ return DeferredDisplayList::kOpBatch_Bitmap;
+ }
+
+private:
+ SkBitmap* mBitmap;
+ Rect mSrc;
+};
+
+class DrawBitmapDataOp : public DrawBitmapOp {
+public:
+ DrawBitmapDataOp(SkBitmap* bitmap, float left, float top, SkPaint* paint)
+ : DrawBitmapOp(bitmap, left, top, paint) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawBitmapData(mBitmap, mLocalBounds.left,
+ mLocalBounds.top, getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw bitmap %p", mBitmap);
+ }
+
+ virtual const char* name() { return "DrawBitmapData"; }
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ return DeferredDisplayList::kOpBatch_Bitmap;
+ }
+};
+
+class DrawBitmapMeshOp : public DrawBoundedOp {
+public:
+ DrawBitmapMeshOp(SkBitmap* bitmap, int meshWidth, int meshHeight,
+ float* vertices, int* colors, SkPaint* paint)
+ : DrawBoundedOp(vertices, 2 * (meshWidth + 1) * (meshHeight + 1), paint),
+ mBitmap(bitmap), mMeshWidth(meshWidth), mMeshHeight(meshHeight),
+ mVertices(vertices), mColors(colors) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawBitmapMesh(mBitmap, mMeshWidth, mMeshHeight,
+ mVertices, mColors, getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw bitmap %p mesh %d x %d", mBitmap, mMeshWidth, mMeshHeight);
+ }
+
+ virtual const char* name() { return "DrawBitmapMesh"; }
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ return DeferredDisplayList::kOpBatch_Bitmap;
+ }
+
+private:
+ SkBitmap* mBitmap;
+ int mMeshWidth;
+ int mMeshHeight;
+ float* mVertices;
+ int* mColors;
+};
+
+class DrawPatchOp : public DrawBoundedOp {
+public:
+ DrawPatchOp(SkBitmap* bitmap, const int32_t* xDivs,
+ const int32_t* yDivs, const uint32_t* colors, uint32_t width, uint32_t height,
+ int8_t numColors, float left, float top, float right, float bottom,
+ int alpha, SkXfermode::Mode mode)
+ : DrawBoundedOp(left, top, right, bottom, 0),
+ mBitmap(bitmap), mxDivs(xDivs), myDivs(yDivs),
+ mColors(colors), mxDivsCount(width), myDivsCount(height),
+ mNumColors(numColors), mAlpha(alpha), mMode(mode) {};
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ // NOTE: not calling the virtual method, which takes a paint
+ return renderer.drawPatch(mBitmap, mxDivs, myDivs, mColors,
+ mxDivsCount, myDivsCount, mNumColors,
+ mLocalBounds.left, mLocalBounds.top,
+ mLocalBounds.right, mLocalBounds.bottom, mAlpha, mMode);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw patch "RECT_STRING, RECT_ARGS(mLocalBounds));
+ }
+
+ virtual const char* name() { return "DrawPatch"; }
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ return DeferredDisplayList::kOpBatch_Patch;
+ }
+
+private:
+ SkBitmap* mBitmap;
+ const int32_t* mxDivs;
+ const int32_t* myDivs;
+ const uint32_t* mColors;
+ uint32_t mxDivsCount;
+ uint32_t myDivsCount;
+ int8_t mNumColors;
+ int mAlpha;
+ SkXfermode::Mode mMode;
+};
+
+class DrawColorOp : public DrawOp {
+public:
+ DrawColorOp(int color, SkXfermode::Mode mode)
+ : DrawOp(0), mColor(color), mMode(mode) {};
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawColor(mColor, mMode);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw color %#x, mode %d", mColor, mMode);
+ }
+
+ virtual const char* name() { return "DrawColor"; }
+
+private:
+ int mColor;
+ SkXfermode::Mode mMode;
+};
+
+class DrawStrokableOp : public DrawBoundedOp {
+public:
+ DrawStrokableOp(float left, float top, float right, float bottom, SkPaint* paint)
+ : DrawBoundedOp(left, top, right, bottom, paint) {};
+
+ bool getLocalBounds(Rect& localBounds) {
+ localBounds.set(mLocalBounds);
+ if (mPaint && mPaint->getStyle() != SkPaint::kFill_Style) {
+ localBounds.outset(strokeWidthOutset());
+ }
+ return true;
+ }
+
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ if (mPaint->getPathEffect()) {
+ return DeferredDisplayList::kOpBatch_AlphaMaskTexture;
+ }
+ return mPaint->isAntiAlias() ?
+ DeferredDisplayList::kOpBatch_AlphaVertices :
+ DeferredDisplayList::kOpBatch_Vertices;
+ }
+};
+
+class DrawRectOp : public DrawStrokableOp {
+public:
+ DrawRectOp(float left, float top, float right, float bottom, SkPaint* paint)
+ : DrawStrokableOp(left, top, right, bottom, paint) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawRect(mLocalBounds.left, mLocalBounds.top,
+ mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw Rect "RECT_STRING, RECT_ARGS(mLocalBounds));
+ }
+
+ virtual const char* name() { return "DrawRect"; }
+};
+
+class DrawRectsOp : public DrawBoundedOp {
+public:
+ DrawRectsOp(const float* rects, int count, SkPaint* paint)
+ : DrawBoundedOp(rects, count, paint),
+ mRects(rects), mCount(count) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawRects(mRects, mCount, getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw Rects count %d", mCount);
+ }
+
+ virtual const char* name() { return "DrawRects"; }
+
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ return DeferredDisplayList::kOpBatch_Vertices;
+ }
+
+private:
+ const float* mRects;
+ int mCount;
+};
+
+class DrawRoundRectOp : public DrawStrokableOp {
+public:
+ DrawRoundRectOp(float left, float top, float right, float bottom,
+ float rx, float ry, SkPaint* paint)
+ : DrawStrokableOp(left, top, right, bottom, paint), mRx(rx), mRy(ry) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawRoundRect(mLocalBounds.left, mLocalBounds.top,
+ mLocalBounds.right, mLocalBounds.bottom, mRx, mRy, getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw RoundRect "RECT_STRING", rx %f, ry %f", RECT_ARGS(mLocalBounds), mRx, mRy);
+ }
+
+ virtual const char* name() { return "DrawRoundRect"; }
+
+private:
+ float mRx;
+ float mRy;
+};
+
+class DrawCircleOp : public DrawStrokableOp {
+public:
+ DrawCircleOp(float x, float y, float radius, SkPaint* paint)
+ : DrawStrokableOp(x - radius, y - radius, x + radius, y + radius, paint),
+ mX(x), mY(y), mRadius(radius) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawCircle(mX, mY, mRadius, getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw Circle x %f, y %f, r %f", mX, mY, mRadius);
+ }
+
+ virtual const char* name() { return "DrawCircle"; }
+
+private:
+ float mX;
+ float mY;
+ float mRadius;
+};
+
+class DrawOvalOp : public DrawStrokableOp {
+public:
+ DrawOvalOp(float left, float top, float right, float bottom, SkPaint* paint)
+ : DrawStrokableOp(left, top, right, bottom, paint) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawOval(mLocalBounds.left, mLocalBounds.top,
+ mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw Oval "RECT_STRING, RECT_ARGS(mLocalBounds));
+ }
+
+ virtual const char* name() { return "DrawOval"; }
+};
+
+class DrawArcOp : public DrawStrokableOp {
+public:
+ DrawArcOp(float left, float top, float right, float bottom,
+ float startAngle, float sweepAngle, bool useCenter, SkPaint* paint)
+ : DrawStrokableOp(left, top, right, bottom, paint),
+ mStartAngle(startAngle), mSweepAngle(sweepAngle), mUseCenter(useCenter) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawArc(mLocalBounds.left, mLocalBounds.top,
+ mLocalBounds.right, mLocalBounds.bottom,
+ mStartAngle, mSweepAngle, mUseCenter, getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw Arc "RECT_STRING", start %f, sweep %f, useCenter %d",
+ RECT_ARGS(mLocalBounds), mStartAngle, mSweepAngle, mUseCenter);
+ }
+
+ virtual const char* name() { return "DrawArc"; }
+
+private:
+ float mStartAngle;
+ float mSweepAngle;
+ bool mUseCenter;
+};
+
+class DrawPathOp : public DrawBoundedOp {
+public:
+ DrawPathOp(SkPath* path, SkPaint* paint)
+ : DrawBoundedOp(paint), mPath(path) {
+ float left, top, offset;
+ uint32_t width, height;
+ PathCache::computePathBounds(path, paint, left, top, offset, width, height);
+ left -= offset;
+ top -= offset;
+ mLocalBounds.set(left, top, left + width, top + height);
+ }
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawPath(mPath, getPaint(renderer));
+ }
+
+ virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
+ SkPaint* paint = getPaint(renderer);
+ renderer.getCaches().pathCache.precache(mPath, paint);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw Path %p in "RECT_STRING, mPath, RECT_ARGS(mLocalBounds));
+ }
+
+ virtual const char* name() { return "DrawPath"; }
+
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ return DeferredDisplayList::kOpBatch_AlphaMaskTexture;
+ }
+private:
+ SkPath* mPath;
+};
+
+class DrawLinesOp : public DrawBoundedOp {
+public:
+ DrawLinesOp(float* points, int count, SkPaint* paint)
+ : DrawBoundedOp(points, count, paint),
+ mPoints(points), mCount(count) {
+ mLocalBounds.outset(strokeWidthOutset());
+ }
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawLines(mPoints, mCount, getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw Lines count %d", mCount);
+ }
+
+ virtual const char* name() { return "DrawLines"; }
+
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ return mPaint->isAntiAlias() ?
+ DeferredDisplayList::kOpBatch_AlphaVertices :
+ DeferredDisplayList::kOpBatch_Vertices;
+ }
+
+protected:
+ float* mPoints;
+ int mCount;
+};
+
+class DrawPointsOp : public DrawLinesOp {
+public:
+ DrawPointsOp(float* points, int count, SkPaint* paint)
+ : DrawLinesOp(points, count, paint) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawPoints(mPoints, mCount, getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw Points count %d", mCount);
+ }
+
+ virtual const char* name() { return "DrawPoints"; }
+};
+
+class DrawSomeTextOp : public DrawOp {
+public:
+ DrawSomeTextOp(const char* text, int bytesCount, int count, SkPaint* paint)
+ : DrawOp(paint), mText(text), mBytesCount(bytesCount), mCount(count) {};
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw some text, %d bytes", mBytesCount);
+ }
+
+ virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
+ SkPaint* paint = getPaint(renderer);
+ FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint);
+ fontRenderer.precache(paint, mText, mCount, mat4::identity());
+ }
+
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ return mPaint->getColor() == 0xff000000 ?
+ DeferredDisplayList::kOpBatch_Text :
+ DeferredDisplayList::kOpBatch_ColorText;
+ }
+protected:
+ const char* mText;
+ int mBytesCount;
+ int mCount;
+};
+
+class DrawTextOnPathOp : public DrawSomeTextOp {
+public:
+ DrawTextOnPathOp(const char* text, int bytesCount, int count,
+ SkPath* path, float hOffset, float vOffset, SkPaint* paint)
+ : DrawSomeTextOp(text, bytesCount, count, paint),
+ mPath(path), mHOffset(hOffset), mVOffset(vOffset) {
+ /* TODO: inherit from DrawBounded and init mLocalBounds */
+ }
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawTextOnPath(mText, mBytesCount, mCount, mPath,
+ mHOffset, mVOffset, getPaint(renderer));
+ }
+
+ virtual const char* name() { return "DrawTextOnPath"; }
+
+private:
+ SkPath* mPath;
+ float mHOffset;
+ float mVOffset;
+};
+
+class DrawPosTextOp : public DrawSomeTextOp {
+public:
+ DrawPosTextOp(const char* text, int bytesCount, int count,
+ const float* positions, SkPaint* paint)
+ : DrawSomeTextOp(text, bytesCount, count, paint), mPositions(positions) {
+ /* TODO: inherit from DrawBounded and init mLocalBounds */
+ }
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawPosText(mText, mBytesCount, mCount, mPositions, getPaint(renderer));
+ }
+
+ virtual const char* name() { return "DrawPosText"; }
+
+private:
+ const float* mPositions;
+};
+
+class DrawTextOp : public DrawBoundedOp {
+public:
+ DrawTextOp(const char* text, int bytesCount, int count, float x, float y,
+ const float* positions, SkPaint* paint, float length)
+ : DrawBoundedOp(paint), mText(text), mBytesCount(bytesCount), mCount(count),
+ mX(x), mY(y), mPositions(positions), mLength(length) {
+ // duplicates bounds calculation from OpenGLRenderer::drawText, but doesn't alter mX
+ SkPaint::FontMetrics metrics;
+ paint->getFontMetrics(&metrics, 0.0f);
+ switch (paint->getTextAlign()) {
+ case SkPaint::kCenter_Align:
+ x -= length / 2.0f;
+ break;
+ case SkPaint::kRight_Align:
+ x -= length;
+ break;
+ default:
+ break;
+ }
+ mLocalBounds.set(x, mY + metrics.fTop, x + length, mY + metrics.fBottom);
+ memset(&mPrecacheTransform.data[0], 0xff, 16 * sizeof(float));
+ }
+
+ /*
+ * When this method is invoked the state field is initialized to have the
+ * final rendering state. We can thus use it to process data as it will be
+ * used at draw time.
+ */
+ virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
+ SkPaint* paint = getPaint(renderer);
+ FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint);
+ const mat4& transform = renderer.findBestFontTransform(state.mMatrix);
+ if (mPrecacheTransform != transform) {
+ fontRenderer.precache(paint, mText, mCount, transform);
+ mPrecacheTransform = transform;
+ }
+ }
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawText(mText, mBytesCount, mCount, mX, mY,
+ mPositions, getPaint(renderer), mLength);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw Text of count %d, bytes %d", mCount, mBytesCount);
+ }
+
+ virtual const char* name() { return "DrawText"; }
+
+ virtual DeferredDisplayList::OpBatchId getBatchId() {
+ return mPaint->getColor() == 0xff000000 ?
+ DeferredDisplayList::kOpBatch_Text :
+ DeferredDisplayList::kOpBatch_ColorText;
+ }
+
+private:
+ const char* mText;
+ int mBytesCount;
+ int mCount;
+ float mX;
+ float mY;
+ const float* mPositions;
+ float mLength;
+ mat4 mPrecacheTransform;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// SPECIAL DRAW OPERATIONS
+///////////////////////////////////////////////////////////////////////////////
+
+class DrawFunctorOp : public DrawOp {
+public:
+ DrawFunctorOp(Functor* functor)
+ : DrawOp(0), mFunctor(functor) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ renderer.startMark("GL functor");
+ status_t ret = renderer.callDrawGLFunction(mFunctor, dirty);
+ renderer.endMark();
+ return ret;
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw Functor %p", mFunctor);
+ }
+
+ virtual const char* name() { return "DrawFunctor"; }
+
+private:
+ Functor* mFunctor;
+};
+
+class DrawDisplayListOp : public DrawBoundedOp {
+public:
+ DrawDisplayListOp(DisplayList* displayList, int flags)
+ : DrawBoundedOp(0, 0, displayList->getWidth(), displayList->getHeight(), 0),
+ mDisplayList(displayList), mFlags(flags) {}
+
+ virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level) {
+ if (mDisplayList && mDisplayList->isRenderable()) {
+ mDisplayList->defer(deferStruct, level + 1);
+ }
+ }
+virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level) {
+ if (mDisplayList && mDisplayList->isRenderable()) {
+ mDisplayList->replay(replayStruct, level + 1);
+ }
+ }
+
+ // NOT USED since replay() is overridden
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return DrawGlInfo::kStatusDone;
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw Display List %p, flags %#x", mDisplayList, mFlags);
+ if (mDisplayList && (logFlags & kOpLogFlag_Recurse)) {
+ mDisplayList->output(level + 1);
+ }
+ }
+
+ virtual const char* name() { return "DrawDisplayList"; }
+
+private:
+ DisplayList* mDisplayList;
+ int mFlags;
+};
+
+class DrawLayerOp : public DrawOp {
+public:
+ DrawLayerOp(Layer* layer, float x, float y)
+ : DrawOp(0), mLayer(layer), mX(x), mY(y) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) {
+ return renderer.drawLayer(mLayer, mX, mY);
+ }
+
+ virtual void output(int level, uint32_t logFlags) {
+ OP_LOG("Draw Layer %p at %f %f", mLayer, mX, mY);
+ }
+
+ virtual const char* name() { return "DrawLayer"; }
+
+private:
+ Layer* mLayer;
+ float mX;
+ float mY;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_DISPLAY_OPERATION_H
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 7a38b4088593..0b8f7e6ab09a 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -17,1338 +17,22 @@
#define LOG_TAG "OpenGLRenderer"
#include <SkCamera.h>
+#include <SkCanvas.h>
#include <private/hwui/DrawGlInfo.h>
+#include "DisplayList.h"
+#include "DeferredDisplayList.h"
#include "DisplayListLogBuffer.h"
+#include "DisplayListOp.h"
#include "DisplayListRenderer.h"
#include "Caches.h"
namespace android {
namespace uirenderer {
-///////////////////////////////////////////////////////////////////////////////
-// Display list
-///////////////////////////////////////////////////////////////////////////////
-
-const char* DisplayList::OP_NAMES[] = {
- "Save",
- "Restore",
- "RestoreToCount",
- "SaveLayer",
- "SaveLayerAlpha",
- "Translate",
- "Rotate",
- "Scale",
- "Skew",
- "SetMatrix",
- "ConcatMatrix",
- "ClipRect",
- "DrawDisplayList",
- "DrawLayer",
- "DrawBitmap",
- "DrawBitmapMatrix",
- "DrawBitmapRect",
- "DrawBitmapData",
- "DrawBitmapMesh",
- "DrawPatch",
- "DrawColor",
- "DrawRect",
- "DrawRoundRect",
- "DrawCircle",
- "DrawOval",
- "DrawArc",
- "DrawPath",
- "DrawLines",
- "DrawPoints",
- "DrawTextOnPath",
- "DrawPosText",
- "DrawText",
- "ResetShader",
- "SetupShader",
- "ResetColorFilter",
- "SetupColorFilter",
- "ResetShadow",
- "SetupShadow",
- "ResetPaintFilter",
- "SetupPaintFilter",
- "DrawGLFunction"
-};
-
-void DisplayList::outputLogBuffer(int fd) {
- DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
- if (logBuffer.isEmpty()) {
- return;
- }
-
- FILE *file = fdopen(fd, "a");
-
- fprintf(file, "\nRecent DisplayList operations\n");
- logBuffer.outputCommands(file, OP_NAMES);
-
- String8 cachesLog;
- Caches::getInstance().dumpMemoryUsage(cachesLog);
- fprintf(file, "\nCaches:\n%s", cachesLog.string());
- fprintf(file, "\n");
-
- fflush(file);
-}
-
-DisplayList::DisplayList(const DisplayListRenderer& recorder) :
- mTransformMatrix(NULL), mTransformCamera(NULL), mTransformMatrix3D(NULL),
- mStaticMatrix(NULL), mAnimationMatrix(NULL) {
-
- initFromDisplayListRenderer(recorder);
-}
-
-DisplayList::~DisplayList() {
- clearResources();
-}
-
-void DisplayList::destroyDisplayListDeferred(DisplayList* displayList) {
- if (displayList) {
- DISPLAY_LIST_LOGD("Deferring display list destruction");
- Caches::getInstance().deleteDisplayListDeferred(displayList);
- }
-}
-
-void DisplayList::clearResources() {
- sk_free((void*) mReader.base());
- mReader.setMemory(NULL, 0);
-
- delete mTransformMatrix;
- delete mTransformCamera;
- delete mTransformMatrix3D;
- delete mStaticMatrix;
- delete mAnimationMatrix;
-
- mTransformMatrix = NULL;
- mTransformCamera = NULL;
- mTransformMatrix3D = NULL;
- mStaticMatrix = NULL;
- mAnimationMatrix = NULL;
-
- Caches& caches = Caches::getInstance();
- caches.unregisterFunctors(mFunctorCount);
- caches.resourceCache.lock();
-
- for (size_t i = 0; i < mBitmapResources.size(); i++) {
- caches.resourceCache.decrementRefcountLocked(mBitmapResources.itemAt(i));
- }
-
- for (size_t i = 0; i < mOwnedBitmapResources.size(); i++) {
- SkBitmap* bitmap = mOwnedBitmapResources.itemAt(i);
- caches.resourceCache.decrementRefcountLocked(bitmap);
- caches.resourceCache.destructorLocked(bitmap);
- }
-
- for (size_t i = 0; i < mFilterResources.size(); i++) {
- caches.resourceCache.decrementRefcountLocked(mFilterResources.itemAt(i));
- }
-
- for (size_t i = 0; i < mShaders.size(); i++) {
- caches.resourceCache.decrementRefcountLocked(mShaders.itemAt(i));
- caches.resourceCache.destructorLocked(mShaders.itemAt(i));
- }
-
- for (size_t i = 0; i < mSourcePaths.size(); i++) {
- caches.resourceCache.decrementRefcountLocked(mSourcePaths.itemAt(i));
- }
-
- for (size_t i = 0; i < mLayers.size(); i++) {
- caches.resourceCache.decrementRefcountLocked(mLayers.itemAt(i));
- }
-
- caches.resourceCache.unlock();
-
- for (size_t i = 0; i < mPaints.size(); i++) {
- delete mPaints.itemAt(i);
- }
-
- for (size_t i = 0; i < mPaths.size(); i++) {
- SkPath* path = mPaths.itemAt(i);
- caches.pathCache.remove(path);
- delete path;
- }
-
- for (size_t i = 0; i < mMatrices.size(); i++) {
- delete mMatrices.itemAt(i);
- }
-
- mBitmapResources.clear();
- mOwnedBitmapResources.clear();
- mFilterResources.clear();
- mShaders.clear();
- mSourcePaths.clear();
- mPaints.clear();
- mPaths.clear();
- mMatrices.clear();
- mLayers.clear();
-}
-
-void DisplayList::reset() {
- clearResources();
- init();
-}
-
-void DisplayList::initFromDisplayListRenderer(const DisplayListRenderer& recorder, bool reusing) {
-
- if (reusing) {
- // re-using display list - clear out previous allocations
- clearResources();
- }
-
- init();
-
- const SkWriter32& writer = recorder.writeStream();
- if (writer.size() == 0) {
- return;
- }
-
- mSize = writer.size();
- void* buffer = sk_malloc_throw(mSize);
- writer.flatten(buffer);
- mReader.setMemory(buffer, mSize);
-
- mFunctorCount = recorder.getFunctorCount();
-
- Caches& caches = Caches::getInstance();
- caches.registerFunctors(mFunctorCount);
- caches.resourceCache.lock();
-
- const Vector<SkBitmap*>& bitmapResources = recorder.getBitmapResources();
- for (size_t i = 0; i < bitmapResources.size(); i++) {
- SkBitmap* resource = bitmapResources.itemAt(i);
- mBitmapResources.add(resource);
- caches.resourceCache.incrementRefcountLocked(resource);
- }
-
- const Vector<SkBitmap*> &ownedBitmapResources = recorder.getOwnedBitmapResources();
- for (size_t i = 0; i < ownedBitmapResources.size(); i++) {
- SkBitmap* resource = ownedBitmapResources.itemAt(i);
- mOwnedBitmapResources.add(resource);
- caches.resourceCache.incrementRefcountLocked(resource);
- }
-
- const Vector<SkiaColorFilter*>& filterResources = recorder.getFilterResources();
- for (size_t i = 0; i < filterResources.size(); i++) {
- SkiaColorFilter* resource = filterResources.itemAt(i);
- mFilterResources.add(resource);
- caches.resourceCache.incrementRefcountLocked(resource);
- }
-
- const Vector<SkiaShader*>& shaders = recorder.getShaders();
- for (size_t i = 0; i < shaders.size(); i++) {
- SkiaShader* resource = shaders.itemAt(i);
- mShaders.add(resource);
- caches.resourceCache.incrementRefcountLocked(resource);
- }
-
- const SortedVector<SkPath*>& sourcePaths = recorder.getSourcePaths();
- for (size_t i = 0; i < sourcePaths.size(); i++) {
- mSourcePaths.add(sourcePaths.itemAt(i));
- caches.resourceCache.incrementRefcountLocked(sourcePaths.itemAt(i));
- }
-
- const Vector<Layer*>& layers = recorder.getLayers();
- for (size_t i = 0; i < layers.size(); i++) {
- mLayers.add(layers.itemAt(i));
- caches.resourceCache.incrementRefcountLocked(layers.itemAt(i));
- }
-
- caches.resourceCache.unlock();
-
- const Vector<SkPaint*>& paints = recorder.getPaints();
- for (size_t i = 0; i < paints.size(); i++) {
- mPaints.add(paints.itemAt(i));
- }
-
- const Vector<SkPath*>& paths = recorder.getPaths();
- for (size_t i = 0; i < paths.size(); i++) {
- mPaths.add(paths.itemAt(i));
- }
-
- const Vector<SkMatrix*>& matrices = recorder.getMatrices();
- for (size_t i = 0; i < matrices.size(); i++) {
- mMatrices.add(matrices.itemAt(i));
- }
-}
-
-void DisplayList::init() {
- mSize = 0;
- mIsRenderable = true;
- mFunctorCount = 0;
- mLeft = 0;
- mTop = 0;
- mRight = 0;
- mBottom = 0;
- mClipChildren = true;
- mAlpha = 1;
- mMultipliedAlpha = 255;
- mHasOverlappingRendering = true;
- mTranslationX = 0;
- mTranslationY = 0;
- mRotation = 0;
- mRotationX = 0;
- mRotationY= 0;
- mScaleX = 1;
- mScaleY = 1;
- mPivotX = 0;
- mPivotY = 0;
- mCameraDistance = 0;
- mMatrixDirty = false;
- mMatrixFlags = 0;
- mPrevWidth = -1;
- mPrevHeight = -1;
- mWidth = 0;
- mHeight = 0;
- mPivotExplicitlySet = false;
- mCaching = false;
-}
-
-size_t DisplayList::getSize() {
- return mSize;
-}
-
-/**
- * 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.
- */
-void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) {
- TextContainer text;
-
- uint32_t count = (level + 1) * 2;
- char indent[count + 1];
- for (uint32_t i = 0; i < count; i++) {
- indent[i] = ' ';
- }
- indent[count] = '\0';
- ALOGD("%sStart display list (%p, %s, render=%d)", (char*) indent + 2, this,
- mName.string(), isRenderable());
-
- ALOGD("%s%s %d", indent, "Save", SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- int saveCount = renderer.getSaveCount() - 1;
-
- outputViewProperties(renderer, (char*) indent);
- mReader.rewind();
-
- while (!mReader.eof()) {
- int op = mReader.readInt();
- if (op & OP_MAY_BE_SKIPPED_MASK) {
- int skip = mReader.readInt();
- ALOGD("%sSkip %d", (char*) indent, skip);
- op &= ~OP_MAY_BE_SKIPPED_MASK;
- }
-
- switch (op) {
- case DrawGLFunction: {
- Functor *functor = (Functor *) getInt();
- ALOGD("%s%s %p", (char*) indent, OP_NAMES[op], functor);
- }
- break;
- case Save: {
- int rendererNum = getInt();
- ALOGD("%s%s %d", (char*) indent, OP_NAMES[op], rendererNum);
- }
- break;
- case Restore: {
- ALOGD("%s%s", (char*) indent, OP_NAMES[op]);
- }
- break;
- case RestoreToCount: {
- int restoreCount = saveCount + getInt();
- ALOGD("%s%s %d", (char*) indent, OP_NAMES[op], restoreCount);
- }
- break;
- case SaveLayer: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- SkPaint* paint = getPaint(renderer);
- int flags = getInt();
- ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %p, 0x%x", (char*) indent,
- OP_NAMES[op], f1, f2, f3, f4, paint, flags);
- }
- break;
- case SaveLayerAlpha: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- int alpha = getInt();
- int flags = getInt();
- ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %d, 0x%x", (char*) indent,
- OP_NAMES[op], f1, f2, f3, f4, alpha, flags);
- }
- break;
- case Translate: {
- float f1 = getFloat();
- float f2 = getFloat();
- ALOGD("%s%s %.2f, %.2f", (char*) indent, OP_NAMES[op], f1, f2);
- }
- break;
- case Rotate: {
- float rotation = getFloat();
- ALOGD("%s%s %.2f", (char*) indent, OP_NAMES[op], rotation);
- }
- break;
- case Scale: {
- float sx = getFloat();
- float sy = getFloat();
- ALOGD("%s%s %.2f, %.2f", (char*) indent, OP_NAMES[op], sx, sy);
- }
- break;
- case Skew: {
- float sx = getFloat();
- float sy = getFloat();
- ALOGD("%s%s %.2f, %.2f", (char*) indent, OP_NAMES[op], sx, sy);
- }
- break;
- case SetMatrix: {
- SkMatrix* matrix = getMatrix();
- ALOGD("%s%s %p", (char*) indent, OP_NAMES[op], matrix);
- }
- break;
- case ConcatMatrix: {
- SkMatrix* matrix = getMatrix();
- ALOGD("%s%s new concat %p: [%f, %f, %f] [%f, %f, %f] [%f, %f, %f]",
- (char*) indent, OP_NAMES[op], matrix, matrix->get(0), matrix->get(1),
- matrix->get(2), matrix->get(3), matrix->get(4), matrix->get(5),
- matrix->get(6), matrix->get(7), matrix->get(8));
- }
- break;
- case ClipRect: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- int regionOp = getInt();
- ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %d", (char*) indent, OP_NAMES[op],
- f1, f2, f3, f4, regionOp);
- }
- break;
- case DrawDisplayList: {
- DisplayList* displayList = getDisplayList();
- int32_t flags = getInt();
- ALOGD("%s%s %p, %dx%d, 0x%x %d", (char*) indent, OP_NAMES[op],
- displayList, mWidth, mHeight, flags, level + 1);
- renderer.outputDisplayList(displayList, level + 1);
- }
- break;
- case DrawLayer: {
- Layer* layer = (Layer*) getInt();
- float x = getFloat();
- float y = getFloat();
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %p, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
- layer, x, y, paint);
- }
- break;
- case DrawBitmap: {
- SkBitmap* bitmap = getBitmap();
- float x = getFloat();
- float y = getFloat();
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %p, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
- bitmap, x, y, paint);
- }
- break;
- case DrawBitmapMatrix: {
- SkBitmap* bitmap = getBitmap();
- SkMatrix* matrix = getMatrix();
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %p, %p, %p", (char*) indent, OP_NAMES[op],
- bitmap, matrix, paint);
- }
- break;
- case DrawBitmapRect: {
- SkBitmap* bitmap = getBitmap();
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- float f5 = getFloat();
- float f6 = getFloat();
- float f7 = getFloat();
- float f8 = getFloat();
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %p, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %p",
- (char*) indent, OP_NAMES[op], bitmap, f1, f2, f3, f4, f5, f6, f7, f8, paint);
- }
- break;
- case DrawBitmapData: {
- SkBitmap* bitmap = getBitmapData();
- float x = getFloat();
- float y = getFloat();
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %.2f, %.2f, %p", (char*) indent, OP_NAMES[op], x, y, paint);
- }
- break;
- case DrawBitmapMesh: {
- int verticesCount = 0;
- uint32_t colorsCount = 0;
- SkBitmap* bitmap = getBitmap();
- uint32_t meshWidth = getInt();
- uint32_t meshHeight = getInt();
- float* vertices = getFloats(verticesCount);
- bool hasColors = getInt();
- int* colors = hasColors ? getInts(colorsCount) : NULL;
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s", (char*) indent, OP_NAMES[op]);
- }
- break;
- case DrawPatch: {
- int32_t* xDivs = NULL;
- int32_t* yDivs = NULL;
- uint32_t* colors = NULL;
- uint32_t xDivsCount = 0;
- uint32_t yDivsCount = 0;
- int8_t numColors = 0;
- SkBitmap* bitmap = getBitmap();
- xDivs = getInts(xDivsCount);
- yDivs = getInts(yDivsCount);
- colors = getUInts(numColors);
- float left = getFloat();
- float top = getFloat();
- float right = getFloat();
- float bottom = getFloat();
- int alpha = getInt();
- SkXfermode::Mode mode = (SkXfermode::Mode) getInt();
- ALOGD("%s%s %.2f, %.2f, %.2f, %.2f", (char*) indent, OP_NAMES[op],
- left, top, right, bottom);
- }
- break;
- case DrawColor: {
- int color = getInt();
- int xferMode = getInt();
- ALOGD("%s%s 0x%x %d", (char*) indent, OP_NAMES[op], color, xferMode);
- }
- break;
- case DrawRect: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
- f1, f2, f3, f4, paint);
- }
- break;
- case DrawRoundRect: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- float f5 = getFloat();
- float f6 = getFloat();
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %p",
- (char*) indent, OP_NAMES[op], f1, f2, f3, f4, f5, f6, paint);
- }
- break;
- case DrawCircle: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %.2f, %.2f, %.2f, %p",
- (char*) indent, OP_NAMES[op], f1, f2, f3, paint);
- }
- break;
- case DrawOval: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %p",
- (char*) indent, OP_NAMES[op], f1, f2, f3, f4, paint);
- }
- break;
- case DrawArc: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- float f5 = getFloat();
- float f6 = getFloat();
- int i1 = getInt();
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %d, %p",
- (char*) indent, OP_NAMES[op], f1, f2, f3, f4, f5, f6, i1, paint);
- }
- break;
- case DrawPath: {
- SkPath* path = getPath();
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %p, %p", (char*) indent, OP_NAMES[op], path, paint);
- }
- break;
- case DrawLines: {
- int count = 0;
- float* points = getFloats(count);
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s", (char*) indent, OP_NAMES[op]);
- }
- break;
- case DrawPoints: {
- int count = 0;
- float* points = getFloats(count);
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s", (char*) indent, OP_NAMES[op]);
- }
- break;
- case DrawTextOnPath: {
- getText(&text);
- int32_t count = getInt();
- SkPath* path = getPath();
- float hOffset = getFloat();
- float vOffset = getFloat();
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %s, %d, %d, %p", (char*) indent, OP_NAMES[op],
- text.text(), text.length(), count, paint);
- }
- break;
- case DrawPosText: {
- getText(&text);
- int count = getInt();
- int positionsCount = 0;
- float* positions = getFloats(positionsCount);
- SkPaint* paint = getPaint(renderer);
- ALOGD("%s%s %s, %d, %d, %p", (char*) indent, OP_NAMES[op],
- text.text(), text.length(), count, paint);
- }
- break;
- case DrawText: {
- getText(&text);
- int32_t count = getInt();
- float x = getFloat();
- float y = getFloat();
- int32_t positionsCount = 0;
- float* positions = getFloats(positionsCount);
- SkPaint* paint = getPaint(renderer);
- float length = getFloat();
- ALOGD("%s%s %s, %d, %d, %p", (char*) indent, OP_NAMES[op],
- text.text(), text.length(), count, paint);
- }
- break;
- case ResetShader: {
- ALOGD("%s%s", (char*) indent, OP_NAMES[op]);
- }
- break;
- case SetupShader: {
- SkiaShader* shader = getShader();
- ALOGD("%s%s %p", (char*) indent, OP_NAMES[op], shader);
- }
- break;
- case ResetColorFilter: {
- ALOGD("%s%s", (char*) indent, OP_NAMES[op]);
- }
- break;
- case SetupColorFilter: {
- SkiaColorFilter *colorFilter = getColorFilter();
- ALOGD("%s%s %p", (char*) indent, OP_NAMES[op], colorFilter);
- }
- break;
- case ResetShadow: {
- ALOGD("%s%s", (char*) indent, OP_NAMES[op]);
- }
- break;
- case SetupShadow: {
- float radius = getFloat();
- float dx = getFloat();
- float dy = getFloat();
- int color = getInt();
- ALOGD("%s%s %.2f, %.2f, %.2f, 0x%x", (char*) indent, OP_NAMES[op],
- radius, dx, dy, color);
- }
- break;
- case ResetPaintFilter: {
- ALOGD("%s%s", (char*) indent, OP_NAMES[op]);
- }
- break;
- case SetupPaintFilter: {
- int clearBits = getInt();
- int setBits = getInt();
- ALOGD("%s%s 0x%x, 0x%x", (char*) indent, OP_NAMES[op], clearBits, setBits);
- }
- break;
- default:
- ALOGD("Display List error: op not handled: %s%s",
- (char*) indent, OP_NAMES[op]);
- break;
- }
- }
- ALOGD("%sDone (%p, %s)", (char*) indent + 2, this, mName.string());
-}
-
-void DisplayList::updateMatrix() {
- if (mMatrixDirty) {
- if (!mTransformMatrix) {
- mTransformMatrix = new SkMatrix();
- }
- if (mMatrixFlags == 0 || mMatrixFlags == TRANSLATION) {
- mTransformMatrix->reset();
- } else {
- if (!mPivotExplicitlySet) {
- if (mWidth != mPrevWidth || mHeight != mPrevHeight) {
- mPrevWidth = mWidth;
- mPrevHeight = mHeight;
- mPivotX = mPrevWidth / 2;
- mPivotY = mPrevHeight / 2;
- }
- }
- if ((mMatrixFlags & ROTATION_3D) == 0) {
- mTransformMatrix->setTranslate(mTranslationX, mTranslationY);
- mTransformMatrix->preRotate(mRotation, mPivotX, mPivotY);
- mTransformMatrix->preScale(mScaleX, mScaleY, mPivotX, mPivotY);
- } else {
- if (!mTransformCamera) {
- mTransformCamera = new Sk3DView();
- mTransformMatrix3D = new SkMatrix();
- }
- mTransformMatrix->reset();
- mTransformCamera->save();
- mTransformMatrix->preScale(mScaleX, mScaleY, mPivotX, mPivotY);
- mTransformCamera->rotateX(mRotationX);
- mTransformCamera->rotateY(mRotationY);
- mTransformCamera->rotateZ(-mRotation);
- mTransformCamera->getMatrix(mTransformMatrix3D);
- mTransformMatrix3D->preTranslate(-mPivotX, -mPivotY);
- mTransformMatrix3D->postTranslate(mPivotX + mTranslationX,
- mPivotY + mTranslationY);
- mTransformMatrix->postConcat(*mTransformMatrix3D);
- mTransformCamera->restore();
- }
- }
- mMatrixDirty = false;
- }
-}
-
-void DisplayList::outputViewProperties(OpenGLRenderer& renderer, char* indent) {
- updateMatrix();
- if (mLeft != 0 || mTop != 0) {
- ALOGD("%s%s %d, %d", indent, "Translate (left, top)", mLeft, mTop);
- }
- if (mStaticMatrix) {
- ALOGD("%s%s %p: [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f]",
- indent, "ConcatMatrix (static)", mStaticMatrix,
- mStaticMatrix->get(0), mStaticMatrix->get(1),
- mStaticMatrix->get(2), mStaticMatrix->get(3),
- mStaticMatrix->get(4), mStaticMatrix->get(5),
- mStaticMatrix->get(6), mStaticMatrix->get(7),
- mStaticMatrix->get(8));
- }
- if (mAnimationMatrix) {
- ALOGD("%s%s %p: [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f]",
- indent, "ConcatMatrix (animation)", mAnimationMatrix,
- mAnimationMatrix->get(0), mAnimationMatrix->get(1),
- mAnimationMatrix->get(2), mAnimationMatrix->get(3),
- mAnimationMatrix->get(4), mAnimationMatrix->get(5),
- mAnimationMatrix->get(6), mAnimationMatrix->get(7),
- mAnimationMatrix->get(8));
- }
- if (mMatrixFlags != 0) {
- if (mMatrixFlags == TRANSLATION) {
- ALOGD("%s%s %f, %f", indent, "Translate", mTranslationX, mTranslationY);
- } else {
- ALOGD("%s%s %p: [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f]",
- indent, "ConcatMatrix", mTransformMatrix,
- mTransformMatrix->get(0), mTransformMatrix->get(1),
- mTransformMatrix->get(2), mTransformMatrix->get(3),
- mTransformMatrix->get(4), mTransformMatrix->get(5),
- mTransformMatrix->get(6), mTransformMatrix->get(7),
- mTransformMatrix->get(8));
- }
- }
- if (mAlpha < 1 && !mCaching) {
- if (!mHasOverlappingRendering) {
- ALOGD("%s%s %.2f", indent, "SetAlpha", mAlpha);
- } else {
- int flags = SkCanvas::kHasAlphaLayer_SaveFlag;
- if (mClipChildren) {
- flags |= SkCanvas::kClipToLayer_SaveFlag;
- }
- ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %d, 0x%x", indent, "SaveLayerAlpha",
- (float) 0, (float) 0, (float) mRight - mLeft, (float) mBottom - mTop,
- mMultipliedAlpha, flags);
- }
- }
- if (mClipChildren) {
- ALOGD("%s%s %.2f, %.2f, %.2f, %.2f", indent, "ClipRect", 0.0f, 0.0f,
- (float) mRight - mLeft, (float) mBottom - mTop);
- }
-}
-
-void DisplayList::setViewProperties(OpenGLRenderer& renderer, uint32_t level) {
-#if DEBUG_DISPLAY_LIST
- uint32_t count = (level + 1) * 2;
- char indent[count + 1];
- for (uint32_t i = 0; i < count; i++) {
- indent[i] = ' ';
- }
- indent[count] = '\0';
-#endif
- updateMatrix();
- if (mLeft != 0 || mTop != 0) {
- DISPLAY_LIST_LOGD("%s%s %d, %d", indent, "Translate (left, top)", mLeft, mTop);
- renderer.translate(mLeft, mTop);
- }
- if (mStaticMatrix) {
- DISPLAY_LIST_LOGD(
- "%s%s %p: [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f]",
- indent, "ConcatMatrix (static)", mStaticMatrix,
- mStaticMatrix->get(0), mStaticMatrix->get(1),
- mStaticMatrix->get(2), mStaticMatrix->get(3),
- mStaticMatrix->get(4), mStaticMatrix->get(5),
- mStaticMatrix->get(6), mStaticMatrix->get(7),
- mStaticMatrix->get(8));
- renderer.concatMatrix(mStaticMatrix);
- } else if (mAnimationMatrix) {
- DISPLAY_LIST_LOGD(
- "%s%s %p: [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f]",
- indent, "ConcatMatrix (animation)", mAnimationMatrix,
- mAnimationMatrix->get(0), mAnimationMatrix->get(1),
- mAnimationMatrix->get(2), mAnimationMatrix->get(3),
- mAnimationMatrix->get(4), mAnimationMatrix->get(5),
- mAnimationMatrix->get(6), mAnimationMatrix->get(7),
- mAnimationMatrix->get(8));
- renderer.concatMatrix(mAnimationMatrix);
- }
- if (mMatrixFlags != 0) {
- if (mMatrixFlags == TRANSLATION) {
- DISPLAY_LIST_LOGD("%s%s %f, %f", indent, "Translate", mTranslationX, mTranslationY);
- renderer.translate(mTranslationX, mTranslationY);
- } else {
- DISPLAY_LIST_LOGD(
- "%s%s %p: [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f]",
- indent, "ConcatMatrix", mTransformMatrix,
- mTransformMatrix->get(0), mTransformMatrix->get(1),
- mTransformMatrix->get(2), mTransformMatrix->get(3),
- mTransformMatrix->get(4), mTransformMatrix->get(5),
- mTransformMatrix->get(6), mTransformMatrix->get(7),
- mTransformMatrix->get(8));
- renderer.concatMatrix(mTransformMatrix);
- }
- }
- if (mAlpha < 1 && !mCaching) {
- if (!mHasOverlappingRendering) {
- DISPLAY_LIST_LOGD("%s%s %.2f", indent, "SetAlpha", mAlpha);
- renderer.setAlpha(mAlpha);
- } else {
- // TODO: should be able to store the size of a DL at record time and not
- // have to pass it into this call. In fact, this information might be in the
- // location/size info that we store with the new native transform data.
- int flags = SkCanvas::kHasAlphaLayer_SaveFlag;
- if (mClipChildren) {
- flags |= SkCanvas::kClipToLayer_SaveFlag;
- }
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %d, 0x%x", indent, "SaveLayerAlpha",
- (float) 0, (float) 0, (float) mRight - mLeft, (float) mBottom - mTop,
- mMultipliedAlpha, flags);
- renderer.saveLayerAlpha(0, 0, mRight - mLeft, mBottom - mTop,
- mMultipliedAlpha, flags);
- }
- }
- if (mClipChildren) {
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f", indent, "ClipRect", 0.0f, 0.0f,
- (float) mRight - mLeft, (float) mBottom - mTop);
- renderer.clipRect(0, 0, mRight - mLeft, mBottom - mTop,
- SkRegion::kIntersect_Op);
- }
-}
-
-/**
- * Changes to replay(), specifically those involving opcode or parameter changes, should be mimicked
- * in the output() function, since that function processes the same list of opcodes for the
- * purposes of logging display list info for a given view.
- */
-status_t DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, int32_t flags, uint32_t level) {
- status_t drawGlStatus = DrawGlInfo::kStatusDone;
- TextContainer text;
- mReader.rewind();
-
-#if DEBUG_DISPLAY_LIST
- uint32_t count = (level + 1) * 2;
- char indent[count + 1];
- for (uint32_t i = 0; i < count; i++) {
- indent[i] = ' ';
- }
- indent[count] = '\0';
- Rect* clipRect = renderer.getClipRect();
- DISPLAY_LIST_LOGD("%sStart display list (%p, %s), clipRect: %.0f, %.f, %.0f, %.0f",
- (char*) indent + 2, this, mName.string(), clipRect->left, clipRect->top,
- clipRect->right, clipRect->bottom);
-#endif
-
- renderer.startMark(mName.string());
-
- int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- DISPLAY_LIST_LOGD("%s%s %d %d", indent, "Save",
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag, restoreTo);
- setViewProperties(renderer, level);
-
- if (renderer.quickRejectNoScissor(0, 0, mWidth, mHeight)) {
- DISPLAY_LIST_LOGD("%s%s %d", (char*) indent, "RestoreToCount", restoreTo);
- renderer.restoreToCount(restoreTo);
- renderer.endMark();
- return drawGlStatus;
- }
-
- DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
- int saveCount = renderer.getSaveCount() - 1;
-
- while (!mReader.eof()) {
- int op = mReader.readInt();
- if (op & OP_MAY_BE_SKIPPED_MASK) {
- int32_t skip = mReader.readInt();
- if (CC_LIKELY(flags & kReplayFlag_ClipChildren)) {
- mReader.skip(skip);
- DISPLAY_LIST_LOGD("%s%s skipping %d bytes", (char*) indent,
- OP_NAMES[op & ~OP_MAY_BE_SKIPPED_MASK], skip);
- continue;
- } else {
- op &= ~OP_MAY_BE_SKIPPED_MASK;
- }
- }
- logBuffer.writeCommand(level, op);
-
-#if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
- Caches::getInstance().eventMark(strlen(OP_NAMES[op]), OP_NAMES[op]);
-#endif
-
- switch (op) {
- case DrawGLFunction: {
- Functor *functor = (Functor *) getInt();
- DISPLAY_LIST_LOGD("%s%s %p", (char*) indent, OP_NAMES[op], functor);
- renderer.startMark("GL functor");
- drawGlStatus |= renderer.callDrawGLFunction(functor, dirty);
- renderer.endMark();
- }
- break;
- case Save: {
- int32_t rendererNum = getInt();
- DISPLAY_LIST_LOGD("%s%s %d", (char*) indent, OP_NAMES[op], rendererNum);
- renderer.save(rendererNum);
- }
- break;
- case Restore: {
- DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
- renderer.restore();
- }
- break;
- case RestoreToCount: {
- int32_t restoreCount = saveCount + getInt();
- DISPLAY_LIST_LOGD("%s%s %d", (char*) indent, OP_NAMES[op], restoreCount);
- renderer.restoreToCount(restoreCount);
- }
- break;
- case SaveLayer: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- SkPaint* paint = getPaint(renderer);
- int32_t flags = getInt();
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %p, 0x%x", (char*) indent,
- OP_NAMES[op], f1, f2, f3, f4, paint, flags);
- renderer.saveLayer(f1, f2, f3, f4, paint, flags);
- }
- break;
- case SaveLayerAlpha: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- int32_t alpha = getInt();
- int32_t flags = getInt();
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %d, 0x%x", (char*) indent,
- OP_NAMES[op], f1, f2, f3, f4, alpha, flags);
- renderer.saveLayerAlpha(f1, f2, f3, f4, alpha, flags);
- }
- break;
- case Translate: {
- float f1 = getFloat();
- float f2 = getFloat();
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f", (char*) indent, OP_NAMES[op], f1, f2);
- renderer.translate(f1, f2);
- }
- break;
- case Rotate: {
- float rotation = getFloat();
- DISPLAY_LIST_LOGD("%s%s %.2f", (char*) indent, OP_NAMES[op], rotation);
- renderer.rotate(rotation);
- }
- break;
- case Scale: {
- float sx = getFloat();
- float sy = getFloat();
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f", (char*) indent, OP_NAMES[op], sx, sy);
- renderer.scale(sx, sy);
- }
- break;
- case Skew: {
- float sx = getFloat();
- float sy = getFloat();
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f", (char*) indent, OP_NAMES[op], sx, sy);
- renderer.skew(sx, sy);
- }
- break;
- case SetMatrix: {
- SkMatrix* matrix = getMatrix();
- DISPLAY_LIST_LOGD("%s%s %p", (char*) indent, OP_NAMES[op], matrix);
- renderer.setMatrix(matrix);
- }
- break;
- case ConcatMatrix: {
- SkMatrix* matrix = getMatrix();
- DISPLAY_LIST_LOGD(
- "%s%s %p: [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f]",
- (char*) indent, OP_NAMES[op], matrix,
- matrix->get(0), matrix->get(1), matrix->get(2),
- matrix->get(3), matrix->get(4), matrix->get(5),
- matrix->get(6), matrix->get(7), matrix->get(8));
- renderer.concatMatrix(matrix);
- }
- break;
- case ClipRect: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- int32_t regionOp = getInt();
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %d", (char*) indent, OP_NAMES[op],
- f1, f2, f3, f4, regionOp);
- renderer.clipRect(f1, f2, f3, f4, (SkRegion::Op) regionOp);
- }
- break;
- case DrawDisplayList: {
- DisplayList* displayList = getDisplayList();
- int32_t flags = getInt();
- DISPLAY_LIST_LOGD("%s%s %p, %dx%d, 0x%x %d", (char*) indent, OP_NAMES[op],
- displayList, mWidth, mHeight, flags, level + 1);
- drawGlStatus |= renderer.drawDisplayList(displayList, dirty, flags, level + 1);
- }
- break;
- case DrawLayer: {
- int oldAlpha = -1;
- Layer* layer = (Layer*) getInt();
- float x = getFloat();
- float y = getFloat();
- SkPaint* paint = getPaint(renderer);
- if (mCaching && mMultipliedAlpha < 255) {
- oldAlpha = layer->getAlpha();
- layer->setAlpha(mMultipliedAlpha);
- }
- DISPLAY_LIST_LOGD("%s%s %p, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
- layer, x, y, paint);
- drawGlStatus |= renderer.drawLayer(layer, x, y, paint);
- if (oldAlpha >= 0) {
- layer->setAlpha(oldAlpha);
- }
- }
- break;
- case DrawBitmap: {
- int oldAlpha = -1;
- SkBitmap* bitmap = getBitmap();
- float x = getFloat();
- float y = getFloat();
- SkPaint* paint = getPaint(renderer);
- if (mCaching && mMultipliedAlpha < 255) {
- oldAlpha = paint->getAlpha();
- paint->setAlpha(mMultipliedAlpha);
- }
- DISPLAY_LIST_LOGD("%s%s %p, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
- bitmap, x, y, paint);
- drawGlStatus |= renderer.drawBitmap(bitmap, x, y, paint);
- if (oldAlpha >= 0) {
- paint->setAlpha(oldAlpha);
- }
- }
- break;
- case DrawBitmapMatrix: {
- SkBitmap* bitmap = getBitmap();
- SkMatrix* matrix = getMatrix();
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s %p, %p, %p", (char*) indent, OP_NAMES[op],
- bitmap, matrix, paint);
- drawGlStatus |= renderer.drawBitmap(bitmap, matrix, paint);
- }
- break;
- case DrawBitmapRect: {
- SkBitmap* bitmap = getBitmap();
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- float f5 = getFloat();
- float f6 = getFloat();
- float f7 = getFloat();
- float f8 = getFloat();
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s %p, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %p",
- (char*) indent, OP_NAMES[op], bitmap,
- f1, f2, f3, f4, f5, f6, f7, f8,paint);
- drawGlStatus |= renderer.drawBitmap(bitmap, f1, f2, f3, f4, f5, f6, f7, f8, paint);
- }
- break;
- case DrawBitmapData: {
- SkBitmap* bitmap = getBitmapData();
- float x = getFloat();
- float y = getFloat();
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s %p, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
- bitmap, x, y, paint);
- drawGlStatus |= renderer.drawBitmap(bitmap, x, y, paint);
- }
- break;
- case DrawBitmapMesh: {
- int32_t verticesCount = 0;
- uint32_t colorsCount = 0;
-
- SkBitmap* bitmap = getBitmap();
- uint32_t meshWidth = getInt();
- uint32_t meshHeight = getInt();
- float* vertices = getFloats(verticesCount);
- bool hasColors = getInt();
- int32_t* colors = hasColors ? getInts(colorsCount) : NULL;
- SkPaint* paint = getPaint(renderer);
-
- DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
- drawGlStatus |= renderer.drawBitmapMesh(bitmap, meshWidth, meshHeight, vertices,
- colors, paint);
- }
- break;
- case DrawPatch: {
- int32_t* xDivs = NULL;
- int32_t* yDivs = NULL;
- uint32_t* colors = NULL;
- uint32_t xDivsCount = 0;
- uint32_t yDivsCount = 0;
- int8_t numColors = 0;
-
- SkBitmap* bitmap = getBitmap();
-
- xDivs = getInts(xDivsCount);
- yDivs = getInts(yDivsCount);
- colors = getUInts(numColors);
-
- float left = getFloat();
- float top = getFloat();
- float right = getFloat();
- float bottom = getFloat();
-
- int alpha = getInt();
- SkXfermode::Mode mode = (SkXfermode::Mode) getInt();
-
- DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
- drawGlStatus |= renderer.drawPatch(bitmap, xDivs, yDivs, colors,
- xDivsCount, yDivsCount, numColors, left, top, right, bottom,
- alpha, mode);
- }
- break;
- case DrawColor: {
- int32_t color = getInt();
- int32_t xferMode = getInt();
- DISPLAY_LIST_LOGD("%s%s 0x%x %d", (char*) indent, OP_NAMES[op], color, xferMode);
- drawGlStatus |= renderer.drawColor(color, (SkXfermode::Mode) xferMode);
- }
- break;
- case DrawRect: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
- f1, f2, f3, f4, paint);
- drawGlStatus |= renderer.drawRect(f1, f2, f3, f4, paint);
- }
- break;
- case DrawRoundRect: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- float f5 = getFloat();
- float f6 = getFloat();
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %p",
- (char*) indent, OP_NAMES[op], f1, f2, f3, f4, f5, f6, paint);
- drawGlStatus |= renderer.drawRoundRect(f1, f2, f3, f4, f5, f6, paint);
- }
- break;
- case DrawCircle: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %p",
- (char*) indent, OP_NAMES[op], f1, f2, f3, paint);
- drawGlStatus |= renderer.drawCircle(f1, f2, f3, paint);
- }
- break;
- case DrawOval: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %p",
- (char*) indent, OP_NAMES[op], f1, f2, f3, f4, paint);
- drawGlStatus |= renderer.drawOval(f1, f2, f3, f4, paint);
- }
- break;
- case DrawArc: {
- float f1 = getFloat();
- float f2 = getFloat();
- float f3 = getFloat();
- float f4 = getFloat();
- float f5 = getFloat();
- float f6 = getFloat();
- int32_t i1 = getInt();
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %d, %p",
- (char*) indent, OP_NAMES[op], f1, f2, f3, f4, f5, f6, i1, paint);
- drawGlStatus |= renderer.drawArc(f1, f2, f3, f4, f5, f6, i1 == 1, paint);
- }
- break;
- case DrawPath: {
- SkPath* path = getPath();
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s %p, %p", (char*) indent, OP_NAMES[op], path, paint);
- drawGlStatus |= renderer.drawPath(path, paint);
- }
- break;
- case DrawLines: {
- int32_t count = 0;
- float* points = getFloats(count);
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
- drawGlStatus |= renderer.drawLines(points, count, paint);
- }
- break;
- case DrawPoints: {
- int32_t count = 0;
- float* points = getFloats(count);
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
- drawGlStatus |= renderer.drawPoints(points, count, paint);
- }
- break;
- case DrawTextOnPath: {
- getText(&text);
- int32_t count = getInt();
- SkPath* path = getPath();
- float hOffset = getFloat();
- float vOffset = getFloat();
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s %s, %d, %d, %p", (char*) indent, OP_NAMES[op],
- text.text(), text.length(), count, paint);
- drawGlStatus |= renderer.drawTextOnPath(text.text(), text.length(), count, path,
- hOffset, vOffset, paint);
- }
- break;
- case DrawPosText: {
- getText(&text);
- int32_t count = getInt();
- int32_t positionsCount = 0;
- float* positions = getFloats(positionsCount);
- SkPaint* paint = getPaint(renderer);
- DISPLAY_LIST_LOGD("%s%s %s, %d, %d, %p", (char*) indent,
- OP_NAMES[op], text.text(), text.length(), count, paint);
- drawGlStatus |= renderer.drawPosText(text.text(), text.length(), count,
- positions, paint);
- }
- break;
- case DrawText: {
- getText(&text);
- int32_t count = getInt();
- float x = getFloat();
- float y = getFloat();
- int32_t positionsCount = 0;
- float* positions = getFloats(positionsCount);
- SkPaint* paint = getPaint(renderer);
- float length = getFloat();
- DISPLAY_LIST_LOGD("%s%s %s, %d, %d, %.2f, %.2f, %p, %.2f", (char*) indent,
- OP_NAMES[op], text.text(), text.length(), count, x, y, paint, length);
- drawGlStatus |= renderer.drawText(text.text(), text.length(), count,
- x, y, positions, paint, length);
- }
- break;
- case ResetShader: {
- DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
- renderer.resetShader();
- }
- break;
- case SetupShader: {
- SkiaShader* shader = getShader();
- DISPLAY_LIST_LOGD("%s%s %p", (char*) indent, OP_NAMES[op], shader);
- renderer.setupShader(shader);
- }
- break;
- case ResetColorFilter: {
- DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
- renderer.resetColorFilter();
- }
- break;
- case SetupColorFilter: {
- SkiaColorFilter *colorFilter = getColorFilter();
- DISPLAY_LIST_LOGD("%s%s %p", (char*) indent, OP_NAMES[op], colorFilter);
- renderer.setupColorFilter(colorFilter);
- }
- break;
- case ResetShadow: {
- DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
- renderer.resetShadow();
- }
- break;
- case SetupShadow: {
- float radius = getFloat();
- float dx = getFloat();
- float dy = getFloat();
- int32_t color = getInt();
- DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, 0x%x", (char*) indent, OP_NAMES[op],
- radius, dx, dy, color);
- renderer.setupShadow(radius, dx, dy, color);
- }
- break;
- case ResetPaintFilter: {
- DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
- renderer.resetPaintFilter();
- }
- break;
- case SetupPaintFilter: {
- int32_t clearBits = getInt();
- int32_t setBits = getInt();
- DISPLAY_LIST_LOGD("%s%s 0x%x, 0x%x", (char*) indent, OP_NAMES[op],
- clearBits, setBits);
- renderer.setupPaintFilter(clearBits, setBits);
- }
- break;
- default:
- DISPLAY_LIST_LOGD("Display List error: op not handled: %s%s",
- (char*) indent, OP_NAMES[op]);
- break;
- }
- }
-
- DISPLAY_LIST_LOGD("%s%s %d", (char*) indent, "RestoreToCount", restoreTo);
- renderer.restoreToCount(restoreTo);
- renderer.endMark();
-
- DISPLAY_LIST_LOGD("%sDone (%p, %s), returning %d", (char*) indent + 2, this, mName.string(),
- drawGlStatus);
- return drawGlStatus;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Base structure
-///////////////////////////////////////////////////////////////////////////////
-
DisplayListRenderer::DisplayListRenderer():
- mCaches(Caches::getInstance()), mWriter(MIN_WRITER_SIZE),
+ mCaches(Caches::getInstance()), mDisplayListData(new DisplayListData),
mTranslateX(0.0f), mTranslateY(0.0f), mHasTranslate(false),
mHasDrawOps(false), mFunctorCount(0) {
}
@@ -1358,8 +42,7 @@ DisplayListRenderer::~DisplayListRenderer() {
}
void DisplayListRenderer::reset() {
- mWriter.reset();
-
+ mDisplayListData = new DisplayListData();
mCaches.resourceCache.lock();
for (size_t i = 0; i < mBitmapResources.size(); i++) {
@@ -1399,6 +82,9 @@ void DisplayListRenderer::reset() {
mPaints.clear();
mPaintMap.clear();
+ mRegions.clear();
+ mRegionMap.clear();
+
mPaths.clear();
mPathMap.clear();
@@ -1451,7 +137,7 @@ status_t DisplayListRenderer::prepareDirty(float left, float top,
void DisplayListRenderer::finish() {
insertRestoreToCount();
- insertTranlate();
+ insertTranslate();
}
void DisplayListRenderer::interrupt() {
@@ -1462,15 +148,13 @@ void DisplayListRenderer::resume() {
status_t DisplayListRenderer::callDrawGLFunction(Functor *functor, Rect& dirty) {
// Ignore dirty during recording, it matters only when we replay
- addOp(DisplayList::DrawGLFunction);
- addInt((int) functor);
+ addDrawOp(new (alloc()) DrawFunctorOp(functor));
mFunctorCount++;
return DrawGlInfo::kStatusDone; // No invalidate needed at record-time
}
int DisplayListRenderer::save(int flags) {
- addOp(DisplayList::Save);
- addInt(flags);
+ addStateOp(new (alloc()) SaveOp(flags));
return OpenGLRenderer::save(flags);
}
@@ -1481,31 +165,19 @@ void DisplayListRenderer::restore() {
}
mRestoreSaveCount--;
- insertTranlate();
+ insertTranslate();
OpenGLRenderer::restore();
}
void DisplayListRenderer::restoreToCount(int saveCount) {
mRestoreSaveCount = saveCount;
- insertTranlate();
+ insertTranslate();
OpenGLRenderer::restoreToCount(saveCount);
}
int DisplayListRenderer::saveLayer(float left, float top, float right, float bottom,
- SkPaint* p, int flags) {
- addOp(DisplayList::SaveLayer);
- addBounds(left, top, right, bottom);
- addPaint(p);
- addInt(flags);
- return OpenGLRenderer::save(flags);
-}
-
-int DisplayListRenderer::saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, int flags) {
- addOp(DisplayList::SaveLayerAlpha);
- addBounds(left, top, right, bottom);
- addInt(alpha);
- addInt(flags);
+ int alpha, SkXfermode::Mode mode, int flags) {
+ addStateOp(new (alloc()) SaveLayerOp(left, top, right, bottom, alpha, mode, flags));
return OpenGLRenderer::save(flags);
}
@@ -1518,126 +190,117 @@ void DisplayListRenderer::translate(float dx, float dy) {
}
void DisplayListRenderer::rotate(float degrees) {
- addOp(DisplayList::Rotate);
- addFloat(degrees);
+ addStateOp(new (alloc()) RotateOp(degrees));
OpenGLRenderer::rotate(degrees);
}
void DisplayListRenderer::scale(float sx, float sy) {
- addOp(DisplayList::Scale);
- addPoint(sx, sy);
+ addStateOp(new (alloc()) ScaleOp(sx, sy));
OpenGLRenderer::scale(sx, sy);
}
void DisplayListRenderer::skew(float sx, float sy) {
- addOp(DisplayList::Skew);
- addPoint(sx, sy);
+ addStateOp(new (alloc()) SkewOp(sx, sy));
OpenGLRenderer::skew(sx, sy);
}
void DisplayListRenderer::setMatrix(SkMatrix* matrix) {
- addOp(DisplayList::SetMatrix);
- addMatrix(matrix);
+ matrix = refMatrix(matrix);
+ addStateOp(new (alloc()) SetMatrixOp(matrix));
OpenGLRenderer::setMatrix(matrix);
}
void DisplayListRenderer::concatMatrix(SkMatrix* matrix) {
- addOp(DisplayList::ConcatMatrix);
- addMatrix(matrix);
+ matrix = refMatrix(matrix);
+ addStateOp(new (alloc()) ConcatMatrixOp(matrix));
OpenGLRenderer::concatMatrix(matrix);
}
bool DisplayListRenderer::clipRect(float left, float top, float right, float bottom,
SkRegion::Op op) {
- addOp(DisplayList::ClipRect);
- addBounds(left, top, right, bottom);
- addInt(op);
+ addStateOp(new (alloc()) ClipRectOp(left, top, right, bottom, op));
return OpenGLRenderer::clipRect(left, top, right, bottom, op);
}
+bool DisplayListRenderer::clipPath(SkPath* path, SkRegion::Op op) {
+ path = refPath(path);
+ addStateOp(new (alloc()) ClipPathOp(path, op));
+ return OpenGLRenderer::clipPath(path, op);
+}
+
+bool DisplayListRenderer::clipRegion(SkRegion* region, SkRegion::Op op) {
+ region = refRegion(region);
+ addStateOp(new (alloc()) ClipRegionOp(region, op));
+ return OpenGLRenderer::clipRegion(region, op);
+}
+
status_t DisplayListRenderer::drawDisplayList(DisplayList* displayList,
- Rect& dirty, int32_t flags, uint32_t level) {
+ Rect& dirty, int32_t flags) {
// dirty is an out parameter and should not be recorded,
// it matters only when replaying the display list
- addOp(DisplayList::DrawDisplayList);
- addDisplayList(displayList);
- addInt(flags);
+ // TODO: To be safe, the display list should be ref-counted in the
+ // resources cache, but we rely on the caller (UI toolkit) to
+ // do the right thing for now
+
+ addDrawOp(new (alloc()) DrawDisplayListOp(displayList, flags));
return DrawGlInfo::kStatusDone;
}
-status_t DisplayListRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* paint) {
- addOp(DisplayList::DrawLayer);
- addLayer(layer);
- addPoint(x, y);
- addPaint(paint);
+status_t DisplayListRenderer::drawLayer(Layer* layer, float x, float y) {
+ layer = refLayer(layer);
+ addDrawOp(new (alloc()) DrawLayerOp(layer, x, y));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint) {
- const bool reject = quickRejectNoScissor(left, top,
- left + bitmap->width(), top + bitmap->height());
- uint32_t* location = addOp(DisplayList::DrawBitmap, reject);
- addBitmap(bitmap);
- addPoint(left, top);
- addPaint(paint);
- addSkip(location);
+ bitmap = refBitmap(bitmap);
+ paint = refPaint(paint);
+
+ addDrawOp(new (alloc()) DrawBitmapOp(bitmap, left, top, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint) {
- Rect r(0.0f, 0.0f, bitmap->width(), bitmap->height());
- const mat4 transform(*matrix);
- transform.mapRect(r);
-
- const bool reject = quickRejectNoScissor(r.left, r.top, r.right, r.bottom);
- uint32_t* location = addOp(DisplayList::DrawBitmapMatrix, reject);
- addBitmap(bitmap);
- addMatrix(matrix);
- addPaint(paint);
- addSkip(location);
+ bitmap = refBitmap(bitmap);
+ matrix = refMatrix(matrix);
+ paint = refPaint(paint);
+
+ addDrawOp(new (alloc()) DrawBitmapMatrixOp(bitmap, matrix, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
float srcRight, float srcBottom, float dstLeft, float dstTop,
float dstRight, float dstBottom, SkPaint* paint) {
- const bool reject = quickRejectNoScissor(dstLeft, dstTop, dstRight, dstBottom);
- uint32_t* location = addOp(DisplayList::DrawBitmapRect, reject);
- addBitmap(bitmap);
- addBounds(srcLeft, srcTop, srcRight, srcBottom);
- addBounds(dstLeft, dstTop, dstRight, dstBottom);
- addPaint(paint);
- addSkip(location);
+ bitmap = refBitmap(bitmap);
+ paint = refPaint(paint);
+
+ addDrawOp(new (alloc()) DrawBitmapRectOp(bitmap,
+ srcLeft, srcTop, srcRight, srcBottom,
+ dstLeft, dstTop, dstRight, dstBottom, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawBitmapData(SkBitmap* bitmap, float left, float top,
SkPaint* paint) {
- const bool reject = quickRejectNoScissor(left, top,
- left + bitmap->width(), top + bitmap->height());
- uint32_t* location = addOp(DisplayList::DrawBitmapData, reject);
- addBitmapData(bitmap);
- addPoint(left, top);
- addPaint(paint);
- addSkip(location);
+ bitmap = refBitmapData(bitmap);
+ paint = refPaint(paint);
+
+ addDrawOp(new (alloc()) DrawBitmapDataOp(bitmap, left, top, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHeight,
float* vertices, int* colors, SkPaint* paint) {
- addOp(DisplayList::DrawBitmapMesh);
- addBitmap(bitmap);
- addInt(meshWidth);
- addInt(meshHeight);
- addFloats(vertices, (meshWidth + 1) * (meshHeight + 1) * 2);
- if (colors) {
- addInt(1);
- addInts(colors, (meshWidth + 1) * (meshHeight + 1));
- } else {
- addInt(0);
- }
- addPaint(paint);
+ int count = (meshWidth + 1) * (meshHeight + 1) * 2;
+ bitmap = refBitmap(bitmap);
+ vertices = refBuffer<float>(vertices, count);
+ paint = refPaint(paint);
+ colors = refBuffer<int>(colors, count);
+
+ addDrawOp(new (alloc()) DrawBitmapMeshOp(bitmap, meshWidth, meshHeight,
+ vertices, colors, paint));
return DrawGlInfo::kStatusDone;
}
@@ -1648,132 +311,104 @@ status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs,
SkXfermode::Mode mode;
OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode);
- const bool reject = quickRejectNoScissor(left, top, right, bottom);
- uint32_t* location = addOp(DisplayList::DrawPatch, reject);
- addBitmap(bitmap);
- addInts(xDivs, width);
- addInts(yDivs, height);
- addUInts(colors, numColors);
- addBounds(left, top, right, bottom);
- addInt(alpha);
- addInt(mode);
- addSkip(location);
+ bitmap = refBitmap(bitmap);
+ xDivs = refBuffer<int>(xDivs, width);
+ yDivs = refBuffer<int>(yDivs, height);
+ colors = refBuffer<uint32_t>(colors, numColors);
+
+ addDrawOp(new (alloc()) DrawPatchOp(bitmap, xDivs, yDivs, colors, width, height, numColors,
+ left, top, right, bottom, alpha, mode));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawColor(int color, SkXfermode::Mode mode) {
- addOp(DisplayList::DrawColor);
- addInt(color);
- addInt(mode);
+ addDrawOp(new (alloc()) DrawColorOp(color, mode));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawRect(float left, float top, float right, float bottom,
SkPaint* paint) {
- const bool reject = paint->getStyle() == SkPaint::kFill_Style &&
- quickRejectNoScissor(left, top, right, bottom);
- uint32_t* location = addOp(DisplayList::DrawRect, reject);
- addBounds(left, top, right, bottom);
- addPaint(paint);
- addSkip(location);
+ paint = refPaint(paint);
+ addDrawOp(new (alloc()) DrawRectOp(left, top, right, bottom, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, SkPaint* paint) {
- const bool reject = paint->getStyle() == SkPaint::kFill_Style &&
- quickRejectNoScissor(left, top, right, bottom);
- uint32_t* location = addOp(DisplayList::DrawRoundRect, reject);
- addBounds(left, top, right, bottom);
- addPoint(rx, ry);
- addPaint(paint);
- addSkip(location);
+ paint = refPaint(paint);
+ addDrawOp(new (alloc()) DrawRoundRectOp(left, top, right, bottom, rx, ry, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawCircle(float x, float y, float radius, SkPaint* paint) {
- addOp(DisplayList::DrawCircle);
- addPoint(x, y);
- addFloat(radius);
- addPaint(paint);
+ paint = refPaint(paint);
+ addDrawOp(new (alloc()) DrawCircleOp(x, y, radius, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawOval(float left, float top, float right, float bottom,
SkPaint* paint) {
- addOp(DisplayList::DrawOval);
- addBounds(left, top, right, bottom);
- addPaint(paint);
+ paint = refPaint(paint);
+ addDrawOp(new (alloc()) DrawOvalOp(left, top, right, bottom, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, SkPaint* paint) {
- addOp(DisplayList::DrawArc);
- addBounds(left, top, right, bottom);
- addPoint(startAngle, sweepAngle);
- addInt(useCenter ? 1 : 0);
- addPaint(paint);
+ paint = refPaint(paint);
+ addDrawOp(new (alloc()) DrawArcOp(left, top, right, bottom,
+ startAngle, sweepAngle, useCenter, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawPath(SkPath* path, SkPaint* paint) {
- float left, top, offset;
- uint32_t width, height;
- computePathBounds(path, paint, left, top, offset, width, height);
-
- left -= offset;
- top -= offset;
-
- const bool reject = quickRejectNoScissor(left, top, left + width, top + height);
- uint32_t* location = addOp(DisplayList::DrawPath, reject);
- addPath(path);
- addPaint(paint);
- addSkip(location);
+ path = refPath(path);
+ paint = refPaint(paint);
+
+ addDrawOp(new (alloc()) DrawPathOp(path, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawLines(float* points, int count, SkPaint* paint) {
- addOp(DisplayList::DrawLines);
- addFloats(points, count);
- addPaint(paint);
+ points = refBuffer<float>(points, count);
+ paint = refPaint(paint);
+
+ addDrawOp(new (alloc()) DrawLinesOp(points, count, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawPoints(float* points, int count, SkPaint* paint) {
- addOp(DisplayList::DrawPoints);
- addFloats(points, count);
- addPaint(paint);
+ points = refBuffer<float>(points, count);
+ paint = refPaint(paint);
+
+ addDrawOp(new (alloc()) DrawPointsOp(points, count, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawTextOnPath(const char* text, int bytesCount, int count,
SkPath* path, float hOffset, float vOffset, SkPaint* paint) {
if (!text || count <= 0) return DrawGlInfo::kStatusDone;
- addOp(DisplayList::DrawTextOnPath);
- addText(text, bytesCount);
- addInt(count);
- addPath(path);
- addFloat(hOffset);
- addFloat(vOffset);
- paint->setAntiAlias(true);
- SkPaint* addedPaint = addPaint(paint);
- FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(addedPaint);
- fontRenderer.precache(addedPaint, text, count);
+
+ text = refText(text, bytesCount);
+ path = refPath(path);
+ paint = refPaint(paint);
+
+ DrawOp* op = new (alloc()) DrawTextOnPathOp(text, bytesCount, count, path,
+ hOffset, vOffset, paint);
+ addDrawOp(op);
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawPosText(const char* text, int bytesCount, int count,
const float* positions, SkPaint* paint) {
if (!text || count <= 0) return DrawGlInfo::kStatusDone;
- addOp(DisplayList::DrawPosText);
- addText(text, bytesCount);
- addInt(count);
- addFloats(positions, count * 2);
- paint->setAntiAlias(true);
- SkPaint* addedPaint = addPaint(paint);
- FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(addedPaint);
- fontRenderer.precache(addedPaint, text, count);
+
+ text = refText(text, bytesCount);
+ positions = refBuffer<float>(positions, count * 2);
+ paint = refPaint(paint);
+
+ DrawOp* op = new (alloc()) DrawPosTextOp(text, bytesCount, count, positions, paint);
+ addDrawOp(op);
return DrawGlInfo::kStatusDone;
}
@@ -1781,76 +416,92 @@ status_t DisplayListRenderer::drawText(const char* text, int bytesCount, int cou
float x, float y, const float* positions, SkPaint* paint, float length) {
if (!text || count <= 0) return DrawGlInfo::kStatusDone;
- // TODO: We should probably make a copy of the paint instead of modifying
- // it; modifying the paint will change its generationID the first
- // time, which might impact caches. More investigation needed to
- // see if it matters.
- // If we make a copy, then drawTextDecorations() should *not* make
- // its own copy as it does right now.
- // Beware: this needs Glyph encoding (already done on the Paint constructor)
- paint->setAntiAlias(true);
if (length < 0.0f) length = paint->measureText(text, bytesCount);
- bool reject = false;
- if (CC_LIKELY(paint->getTextAlign() == SkPaint::kLeft_Align)) {
- SkPaint::FontMetrics metrics;
- paint->getFontMetrics(&metrics, 0.0f);
- reject = quickRejectNoScissor(x, y + metrics.fTop, x + length, y + metrics.fBottom);
- }
+ text = refText(text, bytesCount);
+ positions = refBuffer<float>(positions, count * 2);
+ paint = refPaint(paint);
- uint32_t* location = addOp(DisplayList::DrawText, reject);
- addText(text, bytesCount);
- addInt(count);
- addFloat(x);
- addFloat(y);
- addFloats(positions, count * 2);
- SkPaint* addedPaint = addPaint(paint);
- if (!reject) {
- FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(addedPaint);
- fontRenderer.precache(addedPaint, text, count);
- }
- addFloat(length);
- addSkip(location);
+ DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count, x, y, positions, paint, length);
+ addDrawOp(op);
+ return DrawGlInfo::kStatusDone;
+}
+
+status_t DisplayListRenderer::drawRects(const float* rects, int count, SkPaint* paint) {
+ if (count <= 0) return DrawGlInfo::kStatusDone;
+
+ rects = refBuffer<float>(rects, count);
+ paint = refPaint(paint);
+ addDrawOp(new (alloc()) DrawRectsOp(rects, count, paint));
return DrawGlInfo::kStatusDone;
}
void DisplayListRenderer::resetShader() {
- addOp(DisplayList::ResetShader);
+ addStateOp(new (alloc()) ResetShaderOp());
}
void DisplayListRenderer::setupShader(SkiaShader* shader) {
- addOp(DisplayList::SetupShader);
- addShader(shader);
+ shader = refShader(shader);
+ addStateOp(new (alloc()) SetupShaderOp(shader));
}
void DisplayListRenderer::resetColorFilter() {
- addOp(DisplayList::ResetColorFilter);
+ addStateOp(new (alloc()) ResetColorFilterOp());
}
void DisplayListRenderer::setupColorFilter(SkiaColorFilter* filter) {
- addOp(DisplayList::SetupColorFilter);
- addColorFilter(filter);
+ filter = refColorFilter(filter);
+ addStateOp(new (alloc()) SetupColorFilterOp(filter));
}
void DisplayListRenderer::resetShadow() {
- addOp(DisplayList::ResetShadow);
+ addStateOp(new (alloc()) ResetShadowOp());
}
void DisplayListRenderer::setupShadow(float radius, float dx, float dy, int color) {
- addOp(DisplayList::SetupShadow);
- addFloat(radius);
- addPoint(dx, dy);
- addInt(color);
+ addStateOp(new (alloc()) SetupShadowOp(radius, dx, dy, color));
}
void DisplayListRenderer::resetPaintFilter() {
- addOp(DisplayList::ResetPaintFilter);
+ addStateOp(new (alloc()) ResetPaintFilterOp());
}
void DisplayListRenderer::setupPaintFilter(int clearBits, int setBits) {
- addOp(DisplayList::SetupPaintFilter);
- addInt(clearBits);
- addInt(setBits);
+ addStateOp(new (alloc()) SetupPaintFilterOp(clearBits, setBits));
+}
+
+void DisplayListRenderer::insertRestoreToCount() {
+ if (mRestoreSaveCount >= 0) {
+ DisplayListOp* op = new (alloc()) RestoreToCountOp(mRestoreSaveCount);
+ mDisplayListData->displayListOps.add(op);
+ mRestoreSaveCount = -1;
+ }
+}
+
+void DisplayListRenderer::insertTranslate() {
+ if (mHasTranslate) {
+ if (mTranslateX != 0.0f || mTranslateY != 0.0f) {
+ DisplayListOp* op = new (alloc()) TranslateOp(mTranslateX, mTranslateY);
+ mDisplayListData->displayListOps.add(op);
+ mTranslateX = mTranslateY = 0.0f;
+ }
+ mHasTranslate = false;
+ }
+}
+
+void DisplayListRenderer::addStateOp(StateOp* op) {
+ addOpInternal(op);
+}
+
+void DisplayListRenderer::addDrawOp(DrawOp* op) {
+ Rect localBounds;
+ if (op->getLocalBounds(localBounds)) {
+ bool rejected = quickRejectNoScissor(localBounds.left, localBounds.top,
+ localBounds.right, localBounds.bottom);
+ op->setQuickRejected(rejected);
+ }
+ mHasDrawOps = true;
+ addOpInternal(op);
}
}; // namespace uirenderer
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index e42def5d36bd..19f7eb6c9f64 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -17,18 +17,12 @@
#ifndef ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
#define ANDROID_HWUI_DISPLAY_LIST_RENDERER_H
-#include <SkChunkAlloc.h>
-#include <SkFlattenable.h>
#include <SkMatrix.h>
-#include <SkCamera.h>
#include <SkPaint.h>
#include <SkPath.h>
-#include <SkRefCnt.h>
-#include <SkTDArray.h>
-#include <SkTSearch.h>
-
#include <cutils/compiler.h>
+#include "DisplayList.h"
#include "DisplayListLogBuffer.h"
#include "OpenGLRenderer.h"
@@ -49,493 +43,15 @@ namespace uirenderer {
#define DISPLAY_LIST_LOGD(...)
#endif
-#define TRANSLATION 0x0001
-#define ROTATION 0x0002
-#define ROTATION_3D 0x0004
-#define SCALE 0x0008
-#define PIVOT 0x0010
-
///////////////////////////////////////////////////////////////////////////////
// Display list
///////////////////////////////////////////////////////////////////////////////
+class DeferredDisplayList;
class DisplayListRenderer;
-
-/**
- * Replays recorded drawing commands.
- */
-class DisplayList {
-public:
- DisplayList(const DisplayListRenderer& recorder);
- ANDROID_API ~DisplayList();
-
- // IMPORTANT: Update the intialization of OP_NAMES in the .cpp file
- // when modifying this file
- enum Op {
- // Non-drawing operations
- Save = 0,
- Restore,
- RestoreToCount,
- SaveLayer,
- SaveLayerAlpha,
- Translate,
- Rotate,
- Scale,
- Skew,
- SetMatrix,
- ConcatMatrix,
- ClipRect,
- // Drawing operations
- DrawDisplayList,
- DrawLayer,
- DrawBitmap,
- DrawBitmapMatrix,
- DrawBitmapRect,
- DrawBitmapData,
- DrawBitmapMesh,
- DrawPatch,
- DrawColor,
- DrawRect,
- DrawRoundRect,
- DrawCircle,
- DrawOval,
- DrawArc,
- DrawPath,
- DrawLines,
- DrawPoints,
- DrawTextOnPath,
- DrawPosText,
- DrawText,
- ResetShader,
- SetupShader,
- ResetColorFilter,
- SetupColorFilter,
- ResetShadow,
- SetupShadow,
- ResetPaintFilter,
- SetupPaintFilter,
- DrawGLFunction,
- };
-
- // See flags defined in DisplayList.java
- enum ReplayFlag {
- kReplayFlag_ClipChildren = 0x1
- };
-
- static const char* OP_NAMES[];
-
- void setViewProperties(OpenGLRenderer& renderer, uint32_t level);
- void outputViewProperties(OpenGLRenderer& renderer, char* indent);
-
- ANDROID_API size_t getSize();
- ANDROID_API static void destroyDisplayListDeferred(DisplayList* displayList);
- ANDROID_API static void outputLogBuffer(int fd);
-
- void initFromDisplayListRenderer(const DisplayListRenderer& recorder, bool reusing = false);
-
- status_t replay(OpenGLRenderer& renderer, Rect& dirty, int32_t flags, uint32_t level = 0);
-
- void output(OpenGLRenderer& renderer, uint32_t level = 0);
-
- ANDROID_API void reset();
-
- void setRenderable(bool renderable) {
- mIsRenderable = renderable;
- }
-
- bool isRenderable() const {
- return mIsRenderable;
- }
-
- void setName(const char* name) {
- if (name) {
- mName.setTo(name);
- }
- }
-
- void setClipChildren(bool clipChildren) {
- mClipChildren = clipChildren;
- }
-
- void setStaticMatrix(SkMatrix* matrix) {
- delete mStaticMatrix;
- mStaticMatrix = new SkMatrix(*matrix);
- }
-
- void setAnimationMatrix(SkMatrix* matrix) {
- delete mAnimationMatrix;
- if (matrix) {
- mAnimationMatrix = new SkMatrix(*matrix);
- } else {
- mAnimationMatrix = NULL;
- }
- }
-
- void setAlpha(float alpha) {
- alpha = fminf(1.0f, fmaxf(0.0f, alpha));
- if (alpha != mAlpha) {
- mAlpha = alpha;
- mMultipliedAlpha = (int) (255 * alpha);
- }
- }
-
- void setHasOverlappingRendering(bool hasOverlappingRendering) {
- mHasOverlappingRendering = hasOverlappingRendering;
- }
-
- void setTranslationX(float translationX) {
- if (translationX != mTranslationX) {
- mTranslationX = translationX;
- mMatrixDirty = true;
- if (mTranslationX == 0.0f && mTranslationY == 0.0f) {
- mMatrixFlags &= ~TRANSLATION;
- } else {
- mMatrixFlags |= TRANSLATION;
- }
- }
- }
-
- void setTranslationY(float translationY) {
- if (translationY != mTranslationY) {
- mTranslationY = translationY;
- mMatrixDirty = true;
- if (mTranslationX == 0.0f && mTranslationY == 0.0f) {
- mMatrixFlags &= ~TRANSLATION;
- } else {
- mMatrixFlags |= TRANSLATION;
- }
- }
- }
-
- void setRotation(float rotation) {
- if (rotation != mRotation) {
- mRotation = rotation;
- mMatrixDirty = true;
- if (mRotation == 0.0f) {
- mMatrixFlags &= ~ROTATION;
- } else {
- mMatrixFlags |= ROTATION;
- }
- }
- }
-
- void setRotationX(float rotationX) {
- if (rotationX != mRotationX) {
- mRotationX = rotationX;
- mMatrixDirty = true;
- if (mRotationX == 0.0f && mRotationY == 0.0f) {
- mMatrixFlags &= ~ROTATION_3D;
- } else {
- mMatrixFlags |= ROTATION_3D;
- }
- }
- }
-
- void setRotationY(float rotationY) {
- if (rotationY != mRotationY) {
- mRotationY = rotationY;
- mMatrixDirty = true;
- if (mRotationX == 0.0f && mRotationY == 0.0f) {
- mMatrixFlags &= ~ROTATION_3D;
- } else {
- mMatrixFlags |= ROTATION_3D;
- }
- }
- }
-
- void setScaleX(float scaleX) {
- if (scaleX != mScaleX) {
- mScaleX = scaleX;
- mMatrixDirty = true;
- if (mScaleX == 1.0f && mScaleY == 1.0f) {
- mMatrixFlags &= ~SCALE;
- } else {
- mMatrixFlags |= SCALE;
- }
- }
- }
-
- void setScaleY(float scaleY) {
- if (scaleY != mScaleY) {
- mScaleY = scaleY;
- mMatrixDirty = true;
- if (mScaleX == 1.0f && mScaleY == 1.0f) {
- mMatrixFlags &= ~SCALE;
- } else {
- mMatrixFlags |= SCALE;
- }
- }
- }
-
- void setPivotX(float pivotX) {
- mPivotX = pivotX;
- mMatrixDirty = true;
- if (mPivotX == 0.0f && mPivotY == 0.0f) {
- mMatrixFlags &= ~PIVOT;
- } else {
- mMatrixFlags |= PIVOT;
- }
- mPivotExplicitlySet = true;
- }
-
- void setPivotY(float pivotY) {
- mPivotY = pivotY;
- mMatrixDirty = true;
- if (mPivotX == 0.0f && mPivotY == 0.0f) {
- mMatrixFlags &= ~PIVOT;
- } else {
- mMatrixFlags |= PIVOT;
- }
- mPivotExplicitlySet = true;
- }
-
- void setCameraDistance(float distance) {
- if (distance != mCameraDistance) {
- mCameraDistance = distance;
- mMatrixDirty = true;
- if (!mTransformCamera) {
- mTransformCamera = new Sk3DView();
- mTransformMatrix3D = new SkMatrix();
- }
- mTransformCamera->setCameraLocation(0, 0, distance);
- }
- }
-
- void setLeft(int left) {
- if (left != mLeft) {
- mLeft = left;
- mWidth = mRight - mLeft;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
-
- void setTop(int top) {
- if (top != mTop) {
- mTop = top;
- mHeight = mBottom - mTop;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
-
- void setRight(int right) {
- if (right != mRight) {
- mRight = right;
- mWidth = mRight - mLeft;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
-
- void setBottom(int bottom) {
- if (bottom != mBottom) {
- mBottom = bottom;
- mHeight = mBottom - mTop;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
-
- void setLeftTop(int left, int top) {
- if (left != mLeft || top != mTop) {
- mLeft = left;
- mTop = top;
- mWidth = mRight - mLeft;
- mHeight = mBottom - mTop;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
-
- void setLeftTopRightBottom(int left, int top, int right, int bottom) {
- if (left != mLeft || top != mTop || right != mRight || bottom != mBottom) {
- mLeft = left;
- mTop = top;
- mRight = right;
- mBottom = bottom;
- mWidth = mRight - mLeft;
- mHeight = mBottom - mTop;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
-
- void offsetLeftRight(int offset) {
- if (offset != 0) {
- mLeft += offset;
- mRight += offset;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
-
- void offsetTopBottom(int offset) {
- if (offset != 0) {
- mTop += offset;
- mBottom += offset;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
-
- void setCaching(bool caching) {
- mCaching = caching;
- }
-
- int getWidth() {
- return mWidth;
- }
-
- int getHeight() {
- return mHeight;
- }
-
-private:
- void init();
-
- void clearResources();
-
- void updateMatrix();
-
- class TextContainer {
- public:
- size_t length() const {
- return mByteLength;
- }
-
- const char* text() const {
- return (const char*) mText;
- }
-
- size_t mByteLength;
- const char* mText;
- };
-
- SkBitmap* getBitmap() {
- return (SkBitmap*) getInt();
- }
-
- SkBitmap* getBitmapData() {
- return (SkBitmap*) getInt();
- }
-
- SkiaShader* getShader() {
- return (SkiaShader*) getInt();
- }
-
- SkiaColorFilter* getColorFilter() {
- return (SkiaColorFilter*) getInt();
- }
-
- inline int32_t getIndex() {
- return mReader.readInt();
- }
-
- inline int32_t getInt() {
- return mReader.readInt();
- }
-
- inline uint32_t getUInt() {
- return mReader.readU32();
- }
-
- SkMatrix* getMatrix() {
- return (SkMatrix*) getInt();
- }
-
- SkPath* getPath() {
- return (SkPath*) getInt();
- }
-
- SkPaint* getPaint(OpenGLRenderer& renderer) {
- return renderer.filterPaint((SkPaint*) getInt());
- }
-
- DisplayList* getDisplayList() {
- return (DisplayList*) getInt();
- }
-
- inline float getFloat() {
- return mReader.readScalar();
- }
-
- int32_t* getInts(uint32_t& count) {
- count = getInt();
- return (int32_t*) mReader.skip(count * sizeof(int32_t));
- }
-
- uint32_t* getUInts(int8_t& count) {
- count = getInt();
- return (uint32_t*) mReader.skip(count * sizeof(uint32_t));
- }
-
- float* getFloats(int32_t& count) {
- count = getInt();
- return (float*) mReader.skip(count * sizeof(float));
- }
-
- void getText(TextContainer* text) {
- size_t length = text->mByteLength = getInt();
- text->mText = (const char*) mReader.skip(length);
- }
-
- Vector<SkBitmap*> mBitmapResources;
- Vector<SkBitmap*> mOwnedBitmapResources;
- Vector<SkiaColorFilter*> mFilterResources;
-
- Vector<SkPaint*> mPaints;
- Vector<SkPath*> mPaths;
- SortedVector<SkPath*> mSourcePaths;
- Vector<SkMatrix*> mMatrices;
- Vector<SkiaShader*> mShaders;
- Vector<Layer*> mLayers;
-
- mutable SkFlattenableReadBuffer mReader;
-
- size_t mSize;
-
- bool mIsRenderable;
- uint32_t mFunctorCount;
-
- String8 mName;
-
- // View properties
- bool mClipChildren;
- float mAlpha;
- int mMultipliedAlpha;
- bool mHasOverlappingRendering;
- float mTranslationX, mTranslationY;
- float mRotation, mRotationX, mRotationY;
- float mScaleX, mScaleY;
- float mPivotX, mPivotY;
- float mCameraDistance;
- int mLeft, mTop, mRight, mBottom;
- int mWidth, mHeight;
- int mPrevWidth, mPrevHeight;
- bool mPivotExplicitlySet;
- bool mMatrixDirty;
- bool mMatrixIsIdentity;
- uint32_t mMatrixFlags;
- SkMatrix* mTransformMatrix;
- Sk3DView* mTransformCamera;
- SkMatrix* mTransformMatrix3D;
- SkMatrix* mStaticMatrix;
- SkMatrix* mAnimationMatrix;
- bool mCaching;
-};
-
-///////////////////////////////////////////////////////////////////////////////
-// Renderer
-///////////////////////////////////////////////////////////////////////////////
+class DisplayListOp;
+class DrawOp;
+class StateOp;
/**
* Records drawing commands in a display list for latter playback.
@@ -563,9 +79,7 @@ public:
virtual void restoreToCount(int saveCount);
virtual int saveLayer(float left, float top, float right, float bottom,
- SkPaint* p, int flags);
- virtual int saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, int flags);
+ int alpha, SkXfermode::Mode mode, int flags);
virtual void translate(float dx, float dy);
virtual void rotate(float degrees);
@@ -576,10 +90,11 @@ public:
virtual void concatMatrix(SkMatrix* matrix);
virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
+ virtual bool clipPath(SkPath* path, SkRegion::Op op);
+ virtual bool clipRegion(SkRegion* region, SkRegion::Op op);
- virtual status_t drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t flags,
- uint32_t level = 0);
- virtual status_t drawLayer(Layer* layer, float x, float y, SkPaint* paint);
+ virtual status_t drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t flags);
+ virtual status_t drawLayer(Layer* layer, float x, float y);
virtual status_t drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
virtual status_t drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint);
virtual status_t drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
@@ -608,6 +123,7 @@ public:
const float* positions, SkPaint* paint);
virtual status_t drawText(const char* text, int bytesCount, int count,
float x, float y, const float* positions, SkPaint* paint, float length);
+ virtual status_t drawRects(const float* rects, int count, SkPaint* paint);
virtual void resetShader();
virtual void setupShader(SkiaShader* shader);
@@ -623,8 +139,8 @@ public:
ANDROID_API void reset();
- const SkWriter32& writeStream() const {
- return mWriter;
+ sp<DisplayListData> getDisplayListData() const {
+ return mDisplayListData;
}
const Vector<SkBitmap*>& getBitmapResources() const {
@@ -655,6 +171,10 @@ public:
return mSourcePaths;
}
+ const Vector<SkRegion*>& getRegions() const {
+ return mRegions;
+ }
+
const Vector<Layer*>& getLayers() const {
return mLayers;
}
@@ -668,102 +188,32 @@ public:
}
private:
- void insertRestoreToCount() {
- if (mRestoreSaveCount >= 0) {
- mWriter.writeInt(DisplayList::RestoreToCount);
- addInt(mRestoreSaveCount);
- mRestoreSaveCount = -1;
- }
- }
+ void insertRestoreToCount();
+ void insertTranslate();
- void insertTranlate() {
- if (mHasTranslate) {
- if (mTranslateX != 0.0f || mTranslateY != 0.0f) {
- mWriter.writeInt(DisplayList::Translate);
- addPoint(mTranslateX, mTranslateY);
- mTranslateX = mTranslateY = 0.0f;
- }
- mHasTranslate = false;
- }
- }
-
- inline void addOp(const DisplayList::Op drawOp) {
- insertRestoreToCount();
- insertTranlate();
- mWriter.writeInt(drawOp);
- mHasDrawOps = mHasDrawOps || drawOp >= DisplayList::DrawDisplayList;
- }
-
- uint32_t* addOp(const DisplayList::Op drawOp, const bool reject) {
+ LinearAllocator& alloc() { return mDisplayListData->allocator; }
+ void addStateOp(StateOp* op);
+ void addDrawOp(DrawOp* op);
+ void addOpInternal(DisplayListOp* op) {
insertRestoreToCount();
- insertTranlate();
- mHasDrawOps = mHasDrawOps || drawOp >= DisplayList::DrawDisplayList;
- if (reject) {
- mWriter.writeInt(OP_MAY_BE_SKIPPED_MASK | drawOp);
- mWriter.writeInt(0xdeaddead);
- mBufferSize = mWriter.size();
- return mWriter.peek32(mBufferSize - sizeof(int32_t));
- }
- mWriter.writeInt(drawOp);
- return NULL;
- }
-
- inline void addSkip(uint32_t* location) {
- if (location) {
- *location = (int32_t) (mWriter.size() - mBufferSize);
- }
- }
-
- inline void addInt(int32_t value) {
- mWriter.writeInt(value);
- }
-
- inline void addSize(uint32_t w, uint32_t h) {
- mWriter.writeInt(w);
- mWriter.writeInt(h);
- }
-
- void addInts(const int32_t* values, uint32_t count) {
- mWriter.writeInt(count);
- mWriter.write(values, count * sizeof(int32_t));
- }
-
- void addUInts(const uint32_t* values, int8_t count) {
- mWriter.writeInt(count);
- mWriter.write(values, count * sizeof(uint32_t));
- }
-
- inline void addFloat(float value) {
- mWriter.writeScalar(value);
- }
-
- void addFloats(const float* values, int32_t count) {
- mWriter.writeInt(count);
- mWriter.write(values, count * sizeof(float));
+ insertTranslate();
+ mDisplayListData->displayListOps.add(op);
}
- inline void addPoint(float x, float y) {
- mWriter.writeScalar(x);
- mWriter.writeScalar(y);
+ template<class T>
+ inline T* refBuffer(const T* srcBuffer, int32_t count) {
+ if (srcBuffer == NULL) return NULL;
+ T* dstBuffer = (T*) mDisplayListData->allocator.alloc(count * sizeof(T));
+ memcpy(dstBuffer, srcBuffer, count * sizeof(T));
+ return dstBuffer;
}
- inline void addBounds(float left, float top, float right, float bottom) {
- mWriter.writeScalar(left);
- mWriter.writeScalar(top);
- mWriter.writeScalar(right);
- mWriter.writeScalar(bottom);
+ inline char* refText(const char* text, size_t byteLength) {
+ return (char*) refBuffer<uint8_t>((uint8_t*)text, byteLength);
}
- inline void addText(const void* text, size_t byteLength) {
- mWriter.writeInt(byteLength);
- mWriter.writePad(text, byteLength);
- }
-
- inline void addPath(SkPath* path) {
- if (!path) {
- addInt((int) NULL);
- return;
- }
+ inline SkPath* refPath(SkPath* path) {
+ if (!path) return NULL;
SkPath* pathCopy = mPathMap.valueFor(path);
if (pathCopy == NULL || pathCopy->getGenerationID() != path->getGenerationID()) {
@@ -777,13 +227,11 @@ private:
mCaches.resourceCache.incrementRefcount(path);
mSourcePaths.add(path);
}
-
- addInt((int) pathCopy);
+ return pathCopy;
}
- inline SkPaint* addPaint(SkPaint* paint) {
+ inline SkPaint* refPaint(SkPaint* paint) {
if (!paint) {
- addInt((int) NULL);
return paint;
}
@@ -795,53 +243,58 @@ private:
mPaints.add(paintCopy);
}
- addInt((int) paintCopy);
-
return paintCopy;
}
- inline void addDisplayList(DisplayList* displayList) {
- // TODO: To be safe, the display list should be ref-counted in the
- // resources cache, but we rely on the caller (UI toolkit) to
- // do the right thing for now
- addInt((int) displayList);
+ inline SkRegion* refRegion(SkRegion* region) {
+ if (!region) {
+ return region;
+ }
+
+ SkRegion* regionCopy = mRegionMap.valueFor(region);
+ // TODO: Add generation ID to SkRegion
+ if (regionCopy == NULL) {
+ regionCopy = new SkRegion(*region);
+ // replaceValueFor() performs an add if the entry doesn't exist
+ mRegionMap.replaceValueFor(region, regionCopy);
+ mRegions.add(regionCopy);
+ }
+
+ return regionCopy;
}
- inline void addMatrix(SkMatrix* matrix) {
+ inline SkMatrix* refMatrix(SkMatrix* matrix) {
// Copying the matrix is cheap and prevents against the user changing the original
// matrix before the operation that uses it
SkMatrix* copy = new SkMatrix(*matrix);
- addInt((int) copy);
mMatrices.add(copy);
+ return copy;
}
- inline void addLayer(Layer* layer) {
- addInt((int) layer);
+ inline Layer* refLayer(Layer* layer) {
mLayers.add(layer);
mCaches.resourceCache.incrementRefcount(layer);
+ return layer;
}
- inline void addBitmap(SkBitmap* bitmap) {
+ inline SkBitmap* refBitmap(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.
- addInt((int) bitmap);
mBitmapResources.add(bitmap);
mCaches.resourceCache.incrementRefcount(bitmap);
+ return bitmap;
}
- void addBitmapData(SkBitmap* bitmap) {
- addInt((int) bitmap);
+ inline SkBitmap* refBitmapData(SkBitmap* bitmap) {
mOwnedBitmapResources.add(bitmap);
mCaches.resourceCache.incrementRefcount(bitmap);
+ return bitmap;
}
- inline void addShader(SkiaShader* shader) {
- if (!shader) {
- addInt((int) NULL);
- return;
- }
+ inline SkiaShader* refShader(SkiaShader* shader) {
+ if (!shader) return NULL;
SkiaShader* shaderCopy = mShaderMap.valueFor(shader);
// TODO: We also need to handle generation ID changes in compose shaders
@@ -852,14 +305,13 @@ private:
mShaders.add(shaderCopy);
mCaches.resourceCache.incrementRefcount(shaderCopy);
}
-
- addInt((int) shaderCopy);
+ return shaderCopy;
}
- inline void addColorFilter(SkiaColorFilter* colorFilter) {
- addInt((int) colorFilter);
+ inline SkiaColorFilter* refColorFilter(SkiaColorFilter* colorFilter) {
mFilterResources.add(colorFilter);
mCaches.resourceCache.incrementRefcount(colorFilter);
+ return colorFilter;
}
Vector<SkBitmap*> mBitmapResources;
@@ -874,6 +326,9 @@ private:
SortedVector<SkPath*> mSourcePaths;
+ Vector<SkRegion*> mRegions;
+ DefaultKeyedVector<SkRegion*, SkRegion*> mRegionMap;
+
Vector<SkiaShader*> mShaders;
DefaultKeyedVector<SkiaShader*, SkiaShader*> mShaderMap;
@@ -881,12 +336,10 @@ private:
Vector<Layer*> mLayers;
- uint32_t mBufferSize;
-
int mRestoreSaveCount;
Caches& mCaches;
- SkWriter32 mWriter;
+ sp<DisplayListData> mDisplayListData;
float mTranslateX;
float mTranslateY;
diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp
index e80b325e6e32..51f1e39c1501 100644
--- a/libs/hwui/Dither.cpp
+++ b/libs/hwui/Dither.cpp
@@ -21,38 +21,50 @@ namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
-// Defines
-///////////////////////////////////////////////////////////////////////////////
-
-// Must be a power of two
-#define DITHER_KERNEL_SIZE 4
-
-///////////////////////////////////////////////////////////////////////////////
// Lifecycle
///////////////////////////////////////////////////////////////////////////////
void Dither::bindDitherTexture() {
if (!mInitialized) {
- const uint8_t pattern[] = {
- 0, 8, 2, 10,
- 12, 4, 14, 6,
- 3, 11, 1, 9,
- 15, 7, 13, 5
- };
+ bool useFloatTexture = Extensions::getInstance().getMajorGlVersion() >= 3;
glGenTextures(1, &mDitherTexture);
glBindTexture(GL_TEXTURE_2D, mDitherTexture);
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-
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_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0,
- GL_ALPHA, GL_UNSIGNED_BYTE, &pattern);
+ if (useFloatTexture) {
+ // We use a R16F texture, let's remap the alpha channel to the
+ // red channel to avoid changing the shader sampling code on GL ES 3.0+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED);
+
+ float dither = 1.0f / (255.0f * DITHER_KERNEL_SIZE * DITHER_KERNEL_SIZE);
+ const GLfloat pattern[] = {
+ 0 * dither, 8 * dither, 2 * dither, 10 * dither,
+ 12 * dither, 4 * dither, 14 * dither, 6 * dither,
+ 3 * dither, 11 * dither, 1 * dither, 9 * dither,
+ 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 {
+ const uint8_t pattern[] = {
+ 0, 8, 2, 10,
+ 12, 4, 14, 6,
+ 3, 11, 1, 9,
+ 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);
+ }
mInitialized = true;
} else {
@@ -76,10 +88,7 @@ void Dither::setupProgram(Program* program, GLuint* textureUnit) {
bindDitherTexture();
- float ditherSize = 1.0f / DITHER_KERNEL_SIZE;
glUniform1i(program->getUniform("ditherSampler"), textureSlot);
- glUniform1f(program->getUniform("ditherSize"), ditherSize);
- glUniform1f(program->getUniform("ditherSizeSquared"), ditherSize * ditherSize);
}
}; // namespace uirenderer
diff --git a/libs/hwui/Dither.h b/libs/hwui/Dither.h
index 34cf9bf6a4b7..4d1f92168e4d 100644
--- a/libs/hwui/Dither.h
+++ b/libs/hwui/Dither.h
@@ -17,13 +17,23 @@
#ifndef ANDROID_HWUI_DITHER_H
#define ANDROID_HWUI_DITHER_H
-#include <GLES2/gl2.h>
+#include <GLES3/gl3.h>
#include "Program.h"
namespace android {
namespace uirenderer {
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Must be a power of two
+#define DITHER_KERNEL_SIZE 4
+// These must not use the .0f notation as they are used from GLSL
+#define DITHER_KERNEL_SIZE_INV (1.0 / 4.0)
+#define DITHER_KERNEL_SIZE_INV_SQUARE (1.0 / 16.0)
+
/**
* Handles dithering for programs.
*/
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
new file mode 100644
index 000000000000..51aec8d36715
--- /dev/null
+++ b/libs/hwui/Extensions.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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 "Debug.h"
+#include "Extensions.h"
+
+namespace android {
+
+using namespace uirenderer;
+ANDROID_SINGLETON_STATIC_INSTANCE(Extensions);
+
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#if DEBUG_EXTENSIONS
+ #define EXT_LOGD(...) ALOGD(__VA_ARGS__)
+#else
+ #define EXT_LOGD(...)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors
+///////////////////////////////////////////////////////////////////////////////
+
+Extensions::Extensions(): Singleton<Extensions>() {
+ const char* buffer = (const char*) glGetString(GL_EXTENSIONS);
+ const char* current = buffer;
+ const char* head = current;
+ EXT_LOGD("Available GL extensions:");
+ do {
+ head = strchr(current, ' ');
+ String8 s(current, head ? head - current : strlen(current));
+ if (s.length()) {
+ mExtensionList.add(s);
+ EXT_LOGD(" %s", s.string());
+ }
+ current = head + 1;
+ } while (head);
+
+ mHasNPot = hasExtension("GL_OES_texture_npot");
+ mHasFramebufferFetch = hasExtension("GL_NV_shader_framebuffer_fetch");
+ mHasDiscardFramebuffer = hasExtension("GL_EXT_discard_framebuffer");
+ mHasDebugMarker = hasExtension("GL_EXT_debug_marker");
+ mHasDebugLabel = hasExtension("GL_EXT_debug_label");
+ mHasTiledRendering = hasExtension("GL_QCOM_tiled_rendering");
+ mHas1BitStencil = hasExtension("GL_OES_stencil1");
+ mHas4BitStencil = hasExtension("GL_OES_stencil4");
+
+ mExtensions = strdup(buffer);
+
+ const char* version = (const char*) glGetString(GL_VERSION);
+ mVersion = strdup(version);
+
+ // Section 6.1.5 of the OpenGL ES specification indicates the GL version
+ // string strictly follows this format:
+ //
+ // OpenGL<space>ES<space><version number><space><vendor-specific information>
+ //
+ // In addition section 6.1.5 describes the version number thusly:
+ //
+ // "The version number is either of the form major number.minor number or
+ // major number.minor number.release number, where the numbers all have one
+ // or more digits. The release number and vendor specific information are
+ // optional."
+
+ if (sscanf(version, "OpenGL ES %d.%d", &mVersionMajor, &mVersionMinor) !=2) {
+ // If we cannot parse the version number, assume OpenGL ES 2.0
+ mVersionMajor = 2;
+ mVersionMinor = 0;
+ }
+}
+
+Extensions::~Extensions() {
+ free(mExtensions);
+ free(mVersion);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Methods
+///////////////////////////////////////////////////////////////////////////////
+
+bool Extensions::hasExtension(const char* extension) const {
+ const String8 s(extension);
+ return mExtensionList.indexOf(s) >= 0;
+}
+
+void Extensions::dump() const {
+ ALOGD("%s", mVersion);
+ ALOGD("Supported extensions:\n%s", mExtensions);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index bdaa3cc06aa8..54a3987afde4 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -17,62 +17,24 @@
#ifndef ANDROID_HWUI_EXTENSIONS_H
#define ANDROID_HWUI_EXTENSIONS_H
+#include <utils/Singleton.h>
#include <utils/SortedVector.h>
#include <utils/String8.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
-#include "Debug.h"
-
namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
-// Defines
-///////////////////////////////////////////////////////////////////////////////
-
-// Debug
-#if DEBUG_EXTENSIONS
- #define EXT_LOGD(...) ALOGD(__VA_ARGS__)
-#else
- #define EXT_LOGD(...)
-#endif
-
-///////////////////////////////////////////////////////////////////////////////
// Classes
///////////////////////////////////////////////////////////////////////////////
-class Extensions {
+class Extensions: public Singleton<Extensions> {
public:
- Extensions() {
- const char* buffer = (const char*) glGetString(GL_EXTENSIONS);
- const char* current = buffer;
- const char* head = current;
- EXT_LOGD("Available GL extensions:");
- do {
- head = strchr(current, ' ');
- String8 s(current, head ? head - current : strlen(current));
- if (s.length()) {
- mExtensionList.add(s);
- EXT_LOGD(" %s", s.string());
- }
- current = head + 1;
- } while (head);
-
- mHasNPot = hasExtension("GL_OES_texture_npot");
- mHasFramebufferFetch = hasExtension("GL_NV_shader_framebuffer_fetch");
- mHasDiscardFramebuffer = hasExtension("GL_EXT_discard_framebuffer");
- mHasDebugMarker = hasExtension("GL_EXT_debug_marker");
- mHasDebugLabel = hasExtension("GL_EXT_debug_label");
- mHasTiledRendering = hasExtension("GL_QCOM_tiled_rendering");
-
- mExtensions = strdup(buffer);
- }
-
- ~Extensions() {
- free(mExtensions);
- }
+ Extensions();
+ ~Extensions();
inline bool hasNPot() const { return mHasNPot; }
inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
@@ -80,20 +42,23 @@ public:
inline bool hasDebugMarker() const { return mHasDebugMarker; }
inline bool hasDebugLabel() const { return mHasDebugLabel; }
inline bool hasTiledRendering() const { return mHasTiledRendering; }
+ inline bool has1BitStencil() const { return mHas1BitStencil; }
+ inline bool has4BitStencil() const { return mHas4BitStencil; }
+
+ inline int getMajorGlVersion() const { return mVersionMajor; }
+ inline int getMinorGlVersion() const { return mVersionMinor; }
- bool hasExtension(const char* extension) const {
- const String8 s(extension);
- return mExtensionList.indexOf(s) >= 0;
- }
+ bool hasExtension(const char* extension) const;
- void dump() {
- ALOGD("Supported extensions:\n%s", mExtensions);
- }
+ void dump() const;
private:
+ friend class Singleton<Extensions>;
+
SortedVector<String8> mExtensionList;
char* mExtensions;
+ char* mVersion;
bool mHasNPot;
bool mHasFramebufferFetch;
@@ -101,6 +66,11 @@ private:
bool mHasDebugMarker;
bool mHasDebugLabel;
bool mHasTiledRendering;
+ bool mHas1BitStencil;
+ bool mHas4BitStencil;
+
+ int mVersionMajor;
+ int mVersionMinor;
}; // class Extensions
}; // namespace uirenderer
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 47784a41dcae..44dc731c6be7 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -16,27 +16,40 @@
#define LOG_TAG "OpenGLRenderer"
+#include <SkGlyph.h>
#include <SkUtils.h>
#include <cutils/properties.h>
+#include <utils/Functor.h>
#include <utils/Log.h>
+#include <RenderScript.h>
+
+#include "utils/Blur.h"
+#include "utils/Timing.h"
+
#include "Caches.h"
#include "Debug.h"
+#include "Extensions.h"
#include "FontRenderer.h"
#include "Rect.h"
namespace android {
namespace uirenderer {
+// blur inputs smaller than this constant will bypass renderscript
+#define RS_MIN_INPUT_CUTOFF 10000
+
///////////////////////////////////////////////////////////////////////////////
// FontRenderer
///////////////////////////////////////////////////////////////////////////////
static bool sLogFontRendererCreate = true;
-FontRenderer::FontRenderer() {
+FontRenderer::FontRenderer() :
+ mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity) {
+
if (sLogFontRendererCreate) {
INIT_LOGD("Creating FontRenderer");
}
@@ -44,10 +57,7 @@ FontRenderer::FontRenderer() {
mGammaTable = NULL;
mInitialized = false;
mMaxNumberOfQuads = 1024;
- mCurrentQuadIndex = 0;
- mLastQuadIndex = 0;
- mTextMesh = NULL;
mCurrentCacheTexture = NULL;
mLinearFiltering = false;
@@ -103,23 +113,21 @@ FontRenderer::~FontRenderer() {
// Unbinding the buffer shouldn't be necessary but it crashes with some drivers
Caches::getInstance().unbindIndicesBuffer();
glDeleteBuffers(1, &mIndexBufferID);
-
- delete[] mTextMesh;
}
- Vector<Font*> fontsToDereference = mActiveFonts;
- for (uint32_t i = 0; i < fontsToDereference.size(); i++) {
- delete fontsToDereference[i];
+ LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
+ while (it.next()) {
+ delete it.value();
}
+ mActiveFonts.clear();
}
void FontRenderer::flushAllAndInvalidate() {
- if (mCurrentQuadIndex != 0) {
- issueDrawCommand();
- }
+ issueDrawCommand();
- for (uint32_t i = 0; i < mActiveFonts.size(); i++) {
- mActiveFonts[i]->invalidateTextureCache();
+ LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
+ while (it.next()) {
+ it.value()->invalidateTextureCache();
}
for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
@@ -146,8 +154,9 @@ void FontRenderer::flushLargeCaches() {
CacheTexture* cacheTexture = mCacheTextures[i];
if (cacheTexture->getTexture()) {
cacheTexture->init();
- for (uint32_t j = 0; j < mActiveFonts.size(); j++) {
- mActiveFonts[j]->invalidateTextureCache(cacheTexture);
+ LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
+ while (it.next()) {
+ it.value()->invalidateTextureCache(cacheTexture);
}
cacheTexture->releaseTexture();
}
@@ -168,7 +177,17 @@ CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph,
void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
checkInit();
+
+ // If the glyph bitmap is empty let's assum the glyph is valid
+ // so we can avoid doing extra work later on
+ if (glyph.fWidth == 0 || glyph.fHeight == 0) {
+ cachedGlyph->mIsValid = true;
+ cachedGlyph->mCacheTexture = NULL;
+ return;
+ }
+
cachedGlyph->mIsValid = false;
+
// If the glyph is too tall, don't cache it
if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
@@ -212,49 +231,86 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp
// Large-glyph texture memory is allocated only as needed
cacheTexture->allocateTexture();
}
+ if (!cacheTexture->mesh()) {
+ cacheTexture->allocateMesh();
+ }
- uint8_t* cacheBuffer = cacheTexture->getTexture();
- uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
- unsigned int stride = glyph.rowBytes();
+ // Tells us whether the glyphs is B&W (1 bit per pixel)
+ // or anti-aliased (8 bits per pixel)
+ SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
+ uint8_t* cacheBuffer = cacheTexture->getTexture();
uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
- for (cacheX = startX - TEXTURE_BORDER_SIZE; cacheX < endX + TEXTURE_BORDER_SIZE; cacheX++) {
- cacheBuffer[(startY - TEXTURE_BORDER_SIZE) * cacheWidth + cacheX] = 0;
- cacheBuffer[(endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + cacheX] = 0;
- }
-
- for (cacheY = startY - TEXTURE_BORDER_SIZE + 1;
- cacheY < endY + TEXTURE_BORDER_SIZE - 1; cacheY++) {
- cacheBuffer[cacheY * cacheWidth + startX - TEXTURE_BORDER_SIZE] = 0;
- cacheBuffer[cacheY * cacheWidth + endX + TEXTURE_BORDER_SIZE - 1] = 0;
- }
-
- if (mGammaTable) {
- for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
- for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
- uint8_t tempCol = bitmapBuffer[bY * stride + bX];
- cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol];
+ // Copy the glyph image, taking the mask format into account
+ uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
+ int stride = glyph.rowBytes();
+
+ uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
+ memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
+
+ switch (format) {
+ case SkMask::kA8_Format: {
+ if (mGammaTable) {
+ for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
+ row = cacheY * cacheWidth;
+ cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
+ for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
+ uint8_t tempCol = bitmapBuffer[bY + bX];
+ cacheBuffer[row + cacheX] = mGammaTable[tempCol];
+ }
+ cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
+ }
+ } else {
+ for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
+ row = cacheY * cacheWidth;
+ memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth);
+ cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
+ cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
+ }
}
+ break;
}
- } else {
- for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
- for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
- uint8_t tempCol = bitmapBuffer[bY * stride + bX];
- cacheBuffer[cacheY * cacheWidth + cacheX] = tempCol;
+ case SkMask::kBW_Format: {
+ static const uint8_t COLORS[2] = { 0, 255 };
+
+ for (cacheY = startY; cacheY < endY; cacheY++) {
+ cacheX = startX;
+ int rowBytes = stride;
+ uint8_t* buffer = bitmapBuffer;
+
+ row = cacheY * cacheWidth;
+ cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
+ while (--rowBytes >= 0) {
+ uint8_t b = *buffer++;
+ for (int8_t mask = 7; mask >= 0 && cacheX < endX; mask--) {
+ cacheBuffer[cacheY * cacheWidth + cacheX++] = COLORS[(b >> mask) & 0x1];
+ }
+ }
+ cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
+
+ bitmapBuffer += stride;
}
+ break;
}
+ default:
+ ALOGW("Unkown glyph format: 0x%x", format);
+ break;
}
+ row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
+ memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
+
cachedGlyph->mIsValid = true;
}
CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
- CacheTexture* cacheTexture = new CacheTexture(width, height);
+ CacheTexture* cacheTexture = new CacheTexture(width, height, mMaxNumberOfQuads);
if (allocate) {
Caches::getInstance().activeTexture(0);
cacheTexture->allocateTexture();
+ cacheTexture->allocateMesh();
}
return cacheTexture;
@@ -299,12 +355,6 @@ void FontRenderer::initVertexArrayBuffers() {
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW);
free(indexBufferData);
-
- uint32_t coordSize = 2;
- uint32_t uvSize = 2;
- uint32_t vertsPerQuad = 4;
- uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize;
- mTextMesh = new float[vertexBufferSize];
}
// We don't want to allocate anything unless we actually draw text
@@ -319,15 +369,6 @@ void FontRenderer::checkInit() {
mInitialized = true;
}
-void FontRenderer::updateDrawParams() {
- if (mCurrentQuadIndex != mLastQuadIndex) {
- mDrawOffsets.add((uint16_t*)(mLastQuadIndex * sizeof(uint16_t) * 6));
- mDrawCounts.add(mCurrentQuadIndex - mLastQuadIndex);
- mDrawCacheTextures.add(mCurrentCacheTexture);
- mLastQuadIndex = mCurrentQuadIndex;
- }
-}
-
void FontRenderer::checkTextureUpdate() {
if (!mUploadTexture) {
return;
@@ -335,108 +376,118 @@ void FontRenderer::checkTextureUpdate() {
Caches& caches = Caches::getInstance();
GLuint lastTextureId = 0;
+
+ // OpenGL ES 3.0+ lets us specify the row length for unpack operations such
+ // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture.
+ // With OpenGL ES 2.0 we have to upload entire stripes instead.
+ const bool hasUnpackRowLength = Extensions::getInstance().getMajorGlVersion() >= 3;
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
// Iterate over all the cache textures and see which ones need to be updated
for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
CacheTexture* cacheTexture = mCacheTextures[i];
if (cacheTexture->isDirty() && cacheTexture->getTexture()) {
- // Can't copy inner rect; glTexSubimage expects pointer to deal with entire buffer
- // of data. So expand the dirty rect to the encompassing horizontal stripe.
const Rect* dirtyRect = cacheTexture->getDirtyRect();
- uint32_t x = 0;
+ uint32_t x = hasUnpackRowLength ? dirtyRect->left : 0;
uint32_t y = dirtyRect->top;
uint32_t width = cacheTexture->getWidth();
uint32_t height = dirtyRect->getHeight();
- void* textureData = cacheTexture->getTexture() + y * width;
+ void* textureData = cacheTexture->getTexture() + y * width + x;
if (cacheTexture->getTextureId() != lastTextureId) {
lastTextureId = cacheTexture->getTextureId();
caches.activeTexture(0);
glBindTexture(GL_TEXTURE_2D, lastTextureId);
+
+ // The unpack row length only needs to be specified when a new
+ // texture is bound
+ if (hasUnpackRowLength) {
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
+ }
+ }
+
+ // If we can upload a sub-rectangle, use the dirty rect width
+ // instead of the width of the entire texture
+ if (hasUnpackRowLength) {
+ width = dirtyRect->getWidth();
}
+
#if DEBUG_FONT_RENDERER
ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d",
i, x, y, width, height);
#endif
+
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height,
GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
+
cacheTexture->setDirty(false);
}
}
+ // Reset to default unpack row length to avoid affecting texture
+ // uploads in other parts of the renderer
+ if (hasUnpackRowLength) {
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ }
+
mUploadTexture = false;
}
void FontRenderer::issueDrawCommand() {
- updateDrawParams();
- checkTextureUpdate();
+ bool first = true;
+ bool force = false;
+ GLuint lastId = 0;
Caches& caches = Caches::getInstance();
- caches.bindIndicesBuffer(mIndexBufferID);
- if (!mDrawn) {
- float* buffer = mTextMesh;
- int offset = 2;
- bool force = caches.unbindMeshBuffer();
- caches.bindPositionVertexPointer(force, buffer);
- caches.bindTexCoordsVertexPointer(force, buffer + offset);
- }
+ for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
+ CacheTexture* texture = mCacheTextures[i];
+ if (texture->canDraw()) {
+ if (first) {
+ if (mFunctor) (*mFunctor)(0, NULL);
+
+ checkTextureUpdate();
+ caches.bindIndicesBuffer(mIndexBufferID);
+
+ if (!mDrawn) {
+ // If returns true, a VBO was bound and we must
+ // rebind our vertex attrib pointers even if
+ // they have the same values as the current pointers
+ force = caches.unbindMeshBuffer();
+ }
- for (uint32_t i = 0; i < mDrawOffsets.size(); i++) {
- uint16_t* offset = mDrawOffsets[i];
- uint32_t count = mDrawCounts[i];
- CacheTexture* texture = mDrawCacheTextures[i];
+ caches.activeTexture(0);
+ first = false;
+ }
- caches.activeTexture(0);
- glBindTexture(GL_TEXTURE_2D, texture->getTextureId());
+ glBindTexture(GL_TEXTURE_2D, texture->getTextureId());
+ texture->setLinearFiltering(mLinearFiltering, false);
- texture->setLinearFiltering(mLinearFiltering, false);
+ TextureVertex* mesh = texture->mesh();
+ caches.bindPositionVertexPointer(force, &mesh[0].position[0]);
+ caches.bindTexCoordsVertexPointer(force, &mesh[0].texture[0]);
+ force = false;
- glDrawElements(GL_TRIANGLES, count * 6, GL_UNSIGNED_SHORT, offset);
+ glDrawElements(GL_TRIANGLES, texture->meshElementCount(),
+ GL_UNSIGNED_SHORT, texture->indices());
+
+ texture->resetMesh();
+ }
}
mDrawn = true;
-
- mCurrentQuadIndex = 0;
- mLastQuadIndex = 0;
- mDrawOffsets.clear();
- mDrawCounts.clear();
- mDrawCacheTextures.clear();
}
void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
float x4, float y4, float u4, float v4, CacheTexture* texture) {
if (texture != mCurrentCacheTexture) {
- updateDrawParams();
// Now use the new texture id
mCurrentCacheTexture = texture;
}
- const uint32_t vertsPerQuad = 4;
- const uint32_t floatsPerVert = 4;
- float* currentPos = mTextMesh + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
-
- (*currentPos++) = x1;
- (*currentPos++) = y1;
- (*currentPos++) = u1;
- (*currentPos++) = v1;
-
- (*currentPos++) = x2;
- (*currentPos++) = y2;
- (*currentPos++) = u2;
- (*currentPos++) = v2;
-
- (*currentPos++) = x3;
- (*currentPos++) = y3;
- (*currentPos++) = u3;
- (*currentPos++) = v3;
-
- (*currentPos++) = x4;
- (*currentPos++) = y4;
- (*currentPos++) = u4;
- (*currentPos++) = v4;
-
- mCurrentQuadIndex++;
+ mCurrentCacheTexture->addQuad(x1, y1, u1, v1, x2, y2, u2, v2,
+ x3, y3, u3, v3, x4, y4, u4, v4);
}
void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
@@ -457,7 +508,7 @@ void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
mBounds->bottom = fmax(mBounds->bottom, y1);
}
- if (mCurrentQuadIndex == mMaxNumberOfQuads) {
+ if (mCurrentCacheTexture->endOfMesh()) {
issueDrawCommand();
}
}
@@ -475,27 +526,13 @@ void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
}
- if (mCurrentQuadIndex == mMaxNumberOfQuads) {
+ if (mCurrentCacheTexture->endOfMesh()) {
issueDrawCommand();
}
}
-void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) {
- int flags = 0;
- if (paint->isFakeBoldText()) {
- flags |= Font::kFakeBold;
- }
-
- const float skewX = paint->getTextSkewX();
- uint32_t italicStyle = *(uint32_t*) &skewX;
- const float scaleXFloat = paint->getTextScaleX();
- uint32_t scaleX = *(uint32_t*) &scaleXFloat;
- SkPaint::Style style = paint->getStyle();
- const float strokeWidthFloat = paint->getStrokeWidth();
- uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat;
- mCurrentFont = Font::create(this, fontId, fontSize, flags, italicStyle,
- scaleX, style, strokeWidth);
-
+void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) {
+ mCurrentFont = Font::create(this, paint, matrix);
}
FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
@@ -521,18 +558,28 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const ch
uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
- uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
- for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) {
- dataBuffer[i] = 0;
+ // Align buffers for renderscript usage
+ if (paddedWidth & (RS_CPU_ALLOCATION_ALIGNMENT - 1)) {
+ paddedWidth += RS_CPU_ALLOCATION_ALIGNMENT - paddedWidth % RS_CPU_ALLOCATION_ALIGNMENT;
}
+ int size = paddedWidth * paddedHeight;
+ uint8_t* dataBuffer = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, size);
+ memset(dataBuffer, 0, size);
+
int penX = radius - bounds.left;
int penY = radius - bounds.bottom;
- mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
- Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions);
- blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
+ if ((bounds.right > bounds.left) && (bounds.top > bounds.bottom)) {
+ // 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,
+ Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions);
+
+ blurImage(&dataBuffer, paddedWidth, paddedHeight, radius);
+ }
DropShadow image;
image.width = paddedWidth;
@@ -544,11 +591,12 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const ch
return image;
}
-void FontRenderer::initRender(const Rect* clip, Rect* bounds) {
+void FontRenderer::initRender(const Rect* clip, Rect* bounds, Functor* functor) {
checkInit();
mDrawn = false;
mBounds = bounds;
+ mFunctor = functor;
mClip = clip;
}
@@ -556,53 +604,23 @@ void FontRenderer::finishRender() {
mBounds = NULL;
mClip = NULL;
- if (mCurrentQuadIndex != 0) {
- issueDrawCommand();
- }
+ issueDrawCommand();
}
-void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs) {
- int flags = 0;
- if (paint->isFakeBoldText()) {
- flags |= Font::kFakeBold;
- }
- const float skewX = paint->getTextSkewX();
- uint32_t italicStyle = *(uint32_t*) &skewX;
- const float scaleXFloat = paint->getTextScaleX();
- uint32_t scaleX = *(uint32_t*) &scaleXFloat;
- SkPaint::Style style = paint->getStyle();
- const float strokeWidthFloat = paint->getStrokeWidth();
- uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat;
- float fontSize = paint->getTextSize();
- Font* font = Font::create(this, SkTypeface::UniqueID(paint->getTypeface()),
- fontSize, flags, italicStyle, scaleX, style, strokeWidth);
-
+void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) {
+ Font* font = Font::create(this, paint, matrix);
font->precache(paint, text, numGlyphs);
}
-bool FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, Rect* bounds) {
- if (!mCurrentFont) {
- ALOGE("No font set");
- return false;
- }
-
- initRender(clip, bounds);
- mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y);
- finishRender();
-
- return mDrawn;
-}
-
bool FontRenderer::renderPosText(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) {
+ const float* positions, Rect* bounds, Functor* functor) {
if (!mCurrentFont) {
ALOGE("No font set");
return false;
}
- initRender(clip, bounds);
+ initRender(clip, bounds, functor);
mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
finishRender();
@@ -617,7 +635,7 @@ bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char
return false;
}
- initRender(clip, bounds);
+ initRender(clip, bounds, NULL);
mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
finishRender();
@@ -625,155 +643,51 @@ bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char
}
void FontRenderer::removeFont(const Font* font) {
- for (uint32_t ct = 0; ct < mActiveFonts.size(); ct++) {
- if (mActiveFonts[ct] == font) {
- mActiveFonts.removeAt(ct);
- break;
- }
- }
+ mActiveFonts.remove(font->getDescription());
if (mCurrentFont == font) {
mCurrentFont = NULL;
}
}
-void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
- // Compute gaussian weights for the blur
- // e is the euler's number
- float e = 2.718281828459045f;
- float pi = 3.1415926535897932f;
- // 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.
- // Based on some experimental radius values and sigma's
- // we approximately fit sigma = f(radius) as
- // sigma = radius * 0.3 + 0.6
- // The larger the radius gets, the more our gaussian blur
- // will resemble a box blur since with large sigma
- // the gaussian curve begins to lose its shape
- float sigma = 0.3f * (float) radius + 0.6f;
-
- // Now compute the coefficints
- // We will store some redundant values to save some math during
- // the blur calculations
- // precompute some values
- float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
- float coeff2 = - 1.0f / (2.0f * sigma * sigma);
-
- float normalizeFactor = 0.0f;
- for (int32_t r = -radius; r <= radius; r ++) {
- float floatR = (float) r;
- weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
- normalizeFactor += weights[r + radius];
- }
-
- //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;
- }
-}
+void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius) {
+ if (width * height * radius < RS_MIN_INPUT_CUTOFF) {
+ float *gaussian = new float[2 * radius + 1];
+ Blur::generateGaussianWeights(gaussian, radius);
-void FontRenderer::horizontalBlur(float* weights, int32_t radius,
- const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
- float blurredPixel = 0.0f;
- float currentPixel = 0.0f;
-
- for (int32_t y = 0; y < height; y ++) {
-
- const uint8_t* input = source + y * width;
- uint8_t* output = dest + y * width;
-
- for (int32_t x = 0; x < width; x ++) {
- blurredPixel = 0.0f;
- const float* gPtr = weights;
- // Optimization for non-border pixels
- if (x > radius && x < (width - radius)) {
- const uint8_t *i = input + (x - radius);
- for (int r = -radius; r <= radius; r ++) {
- currentPixel = (float) (*i);
- blurredPixel += currentPixel * gPtr[0];
- gPtr++;
- i++;
- }
- } else {
- for (int32_t r = -radius; r <= radius; r ++) {
- // Stepping left and right away from the pixel
- int validW = x + r;
- if (validW < 0) {
- validW = 0;
- }
- if (validW > width - 1) {
- validW = width - 1;
- }
+ uint8_t* scratch = new uint8_t[width * height];
+ Blur::horizontal(gaussian, radius, *image, scratch, width, height);
+ Blur::vertical(gaussian, radius, scratch, *image, width, height);
- currentPixel = (float) input[validW];
- blurredPixel += currentPixel * gPtr[0];
- gPtr++;
- }
- }
- *output = (uint8_t)blurredPixel;
- output ++;
- }
+ delete[] gaussian;
+ delete[] scratch;
+ return;
}
-}
-void FontRenderer::verticalBlur(float* weights, int32_t radius,
- const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
- float blurredPixel = 0.0f;
- float currentPixel = 0.0f;
-
- for (int32_t y = 0; y < height; y ++) {
- uint8_t* output = dest + y * width;
-
- for (int32_t x = 0; x < width; x ++) {
- blurredPixel = 0.0f;
- const float* gPtr = weights;
- const uint8_t* input = source + x;
- // Optimization for non-border pixels
- if (y > radius && y < (height - radius)) {
- const uint8_t *i = input + ((y - radius) * width);
- for (int32_t r = -radius; r <= radius; r ++) {
- currentPixel = (float)(*i);
- blurredPixel += currentPixel * gPtr[0];
- gPtr++;
- i += width;
- }
- } else {
- for (int32_t r = -radius; r <= radius; r ++) {
- int validH = y + r;
- // Clamp to zero and width
- if (validH < 0) {
- validH = 0;
- }
- if (validH > height - 1) {
- validH = height - 1;
- }
+ uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height);
- const uint8_t *i = input + validH * width;
- currentPixel = (float) (*i);
- blurredPixel += currentPixel * gPtr[0];
- gPtr++;
- }
- }
- *output = (uint8_t) blurredPixel;
- output++;
+ if (mRs.get() == 0) {
+ mRs = new RSC::RS();
+ if (!mRs->init(true, true)) {
+ ALOGE("blur RS failed to init");
}
- }
-}
-
-void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) {
- float *gaussian = new float[2 * radius + 1];
- computeGaussianWeights(gaussian, radius);
+ mRsElement = RSC::Element::A_8(mRs);
+ mRsScript = new RSC::ScriptIntrinsicBlur(mRs, mRsElement);
+ }
- uint8_t* scratch = new uint8_t[width * height];
+ sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0);
+ sp<RSC::Allocation> ain = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE,
+ RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, *image);
+ sp<RSC::Allocation> aout = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE,
+ RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, outImage);
- horizontalBlur(gaussian, radius, image, scratch, width, height);
- verticalBlur(gaussian, radius, scratch, image, width, height);
+ mRsScript->setRadius(radius);
+ mRsScript->blur(ain, aout);
- delete[] gaussian;
- delete[] scratch;
+ // replace the original image's pointer, avoiding a copy back to the original buffer
+ free(*image);
+ *image = outImage;
}
}; // namespace uirenderer
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 09a3c2591c6e..1da3b6cb3433 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_HWUI_FONT_RENDERER_H
#define ANDROID_HWUI_FONT_RENDERER_H
+#include <utils/LruCache.h>
#include <utils/Vector.h>
#include <SkPaint.h>
@@ -27,8 +28,18 @@
#include "font/CacheTexture.h"
#include "font/CachedGlyphInfo.h"
#include "font/Font.h"
+#include "utils/SortedList.h"
+#include "Matrix.h"
#include "Properties.h"
+namespace RSC {
+ class Element;
+ class RS;
+ class ScriptIntrinsicBlur;
+}
+
+class Functor;
+
namespace android {
namespace uirenderer {
@@ -47,16 +58,14 @@ public:
mGammaTable = gammaTable;
}
- void setFont(SkPaint* paint, uint32_t fontId, float fontSize);
+ void setFont(SkPaint* paint, const mat4& matrix);
- void precache(SkPaint* paint, const char* text, int numGlyphs);
+ void precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix);
// bounds is an out parameter
- bool renderText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
- uint32_t len, int numGlyphs, int x, int y, Rect* bounds);
- // bounds is an out parameter
bool renderPosText(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);
+ uint32_t len, int numGlyphs, int x, int y, const float* positions, Rect* bounds,
+ Functor* functor);
// bounds is an out parameter
bool renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds);
@@ -82,13 +91,8 @@ public:
DropShadow renderDropShadow(SkPaint* paint, const char *text, uint32_t startIndex,
uint32_t len, int numGlyphs, uint32_t radius, const float* positions);
- GLuint getTexture(bool linearFiltering = false) {
- checkInit();
-
- mCurrentCacheTexture->setLinearFiltering(linearFiltering);
+ void setTextureFiltering(bool linearFiltering) {
mLinearFiltering = linearFiltering;
-
- return mCurrentCacheTexture->getTextureId();
}
uint32_t getCacheSize() const {
@@ -119,7 +123,7 @@ private:
void initVertexArrayBuffers();
void checkInit();
- void initRender(const Rect* clip, Rect* bounds);
+ void initRender(const Rect* clip, Rect* bounds, Functor* functor);
void finishRender();
void issueDrawCommand();
@@ -138,7 +142,6 @@ private:
void removeFont(const Font* font);
- void updateDrawParams();
void checkTextureUpdate();
void setTextureDirty() {
@@ -153,20 +156,16 @@ private:
Vector<CacheTexture*> mCacheTextures;
Font* mCurrentFont;
- Vector<Font*> mActiveFonts;
+ LruCache<Font::FontDescription, Font*> mActiveFonts;
CacheTexture* mCurrentCacheTexture;
bool mUploadTexture;
- // Pointer to vertex data to speed up frame to frame work
- float* mTextMesh;
- uint32_t mCurrentQuadIndex;
- uint32_t mLastQuadIndex;
uint32_t mMaxNumberOfQuads;
-
uint32_t mIndexBufferID;
+ Functor* mFunctor;
const Rect* mClip;
Rect* mBounds;
bool mDrawn;
@@ -175,17 +174,19 @@ private:
bool mLinearFiltering;
- Vector<uint16_t*> mDrawOffsets;
- Vector<uint32_t> mDrawCounts;
- Vector<CacheTexture*> mDrawCacheTextures;
+ // RS constructs
+ sp<RSC::RS> mRs;
+ sp<const RSC::Element> mRsElement;
+ sp<RSC::ScriptIntrinsicBlur> mRsScript;
- /** We should consider multi-threading this code or using Renderscript **/
static void computeGaussianWeights(float* weights, int32_t radius);
static void horizontalBlur(float* weights, int32_t radius, const uint8_t *source, uint8_t *dest,
int32_t width, int32_t height);
static void verticalBlur(float* weights, int32_t radius, const uint8_t *source, uint8_t *dest,
int32_t width, int32_t height);
- static void blurImage(uint8_t* image, int32_t width, int32_t height, int32_t radius);
+
+ // the input image handle may have its pointer replaced (to avoid copies)
+ void blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius);
};
}; // namespace uirenderer
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index 2e4e3492df03..eef366c409f9 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -16,7 +16,7 @@
#define LOG_TAG "OpenGLRenderer"
-#include <utils/threads.h>
+#include <utils/JenkinsHash.h>
#include "Caches.h"
#include "Debug.h"
@@ -27,13 +27,6 @@ namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
-// Defines
-///////////////////////////////////////////////////////////////////////////////
-
-#define GRADIENT_TEXTURE_HEIGHT 2
-#define GRADIENT_BYTES_PER_PIXEL 4
-
-///////////////////////////////////////////////////////////////////////////////
// Functions
///////////////////////////////////////////////////////////////////////////////
@@ -43,11 +36,34 @@ static inline T min(T a, T b) {
}
///////////////////////////////////////////////////////////////////////////////
+// Cache entry
+///////////////////////////////////////////////////////////////////////////////
+
+hash_t GradientCacheEntry::hash() const {
+ uint32_t hash = JenkinsHashMix(0, count);
+ for (uint32_t i = 0; i < count; i++) {
+ hash = JenkinsHashMix(hash, android::hash_type(colors[i]));
+ hash = JenkinsHashMix(hash, android::hash_type(positions[i]));
+ }
+ return JenkinsHashWhiten(hash);
+}
+
+int GradientCacheEntry::compare(const GradientCacheEntry& lhs, const GradientCacheEntry& rhs) {
+ int deltaInt = int(lhs.count) - int(rhs.count);
+ if (deltaInt != 0) return deltaInt;
+
+ deltaInt = memcmp(lhs.colors, rhs.colors, lhs.count * sizeof(uint32_t));
+ if (deltaInt != 0) return deltaInt;
+
+ return memcmp(lhs.positions, rhs.positions, lhs.count * sizeof(float));
+}
+
+///////////////////////////////////////////////////////////////////////////////
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
GradientCache::GradientCache():
- mCache(GenerationCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity),
+ mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity),
mSize(0), mMaxSize(MB(DEFAULT_GRADIENT_CACHE_SIZE)) {
char property[PROPERTY_VALUE_MAX];
if (property_get(PROPERTY_GRADIENT_CACHE_SIZE, property, NULL) > 0) {
@@ -60,10 +76,14 @@ GradientCache::GradientCache():
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
mCache.setOnEntryRemovedListener(this);
+
+ const Extensions& extensions = Extensions::getInstance();
+ mUseFloatTexture = extensions.getMajorGlVersion() >= 3;
+ mHasNpot = extensions.hasNPot();
}
GradientCache::GradientCache(uint32_t maxByteSize):
- mCache(GenerationCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity),
+ mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity),
mSize(0), mMaxSize(maxByteSize) {
mCache.setOnEntryRemovedListener(this);
}
@@ -97,11 +117,9 @@ void GradientCache::setMaxSize(uint32_t maxSize) {
void GradientCache::operator()(GradientCacheEntry& shader, Texture*& texture) {
if (texture) {
- const uint32_t size = texture->width * texture->height * GRADIENT_BYTES_PER_PIXEL;
+ const uint32_t size = texture->width * texture->height * bytesPerPixel();
mSize -= size;
- }
- if (texture) {
glDeleteTextures(1, &texture->id);
delete texture;
}
@@ -112,7 +130,6 @@ void GradientCache::operator()(GradientCacheEntry& shader, Texture*& texture) {
///////////////////////////////////////////////////////////////////////////////
Texture* GradientCache::get(uint32_t* colors, float* positions, int count) {
-
GradientCacheEntry gradient(colors, positions, count);
Texture* texture = mCache.get(gradient);
@@ -131,7 +148,7 @@ void GradientCache::getGradientInfo(const uint32_t* colors, const int count,
GradientInfo& info) {
uint32_t width = 256 * (count - 1);
- if (!Caches::getInstance().extensions.hasNPot()) {
+ if (!mHasNpot) {
width = 1 << (31 - __builtin_clz(width));
}
@@ -155,13 +172,13 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient,
Texture* texture = new Texture;
texture->width = info.width;
- texture->height = GRADIENT_TEXTURE_HEIGHT;
+ 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 * GRADIENT_BYTES_PER_PIXEL;
- while (mSize + size > mMaxSize) {
+ const uint32_t size = texture->width * texture->height * bytesPerPixel();
+ while (getSize() + size > mMaxSize) {
mCache.removeOldest();
}
@@ -173,69 +190,110 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient,
return texture;
}
+size_t GradientCache::bytesPerPixel() const {
+ // We use 4 channels (RGBA)
+ return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t));
+}
+
+void GradientCache::splitToBytes(uint32_t inColor, GradientColor& outColor) const {
+ outColor.r = (inColor >> 16) & 0xff;
+ outColor.g = (inColor >> 8) & 0xff;
+ outColor.b = (inColor >> 0) & 0xff;
+ outColor.a = (inColor >> 24) & 0xff;
+}
+
+void GradientCache::splitToFloats(uint32_t inColor, GradientColor& outColor) const {
+ outColor.r = ((inColor >> 16) & 0xff) / 255.0f;
+ outColor.g = ((inColor >> 8) & 0xff) / 255.0f;
+ outColor.b = ((inColor >> 0) & 0xff) / 255.0f;
+ outColor.a = ((inColor >> 24) & 0xff) / 255.0f;
+}
+
+void GradientCache::mixBytes(GradientColor& start, GradientColor& end, float amount,
+ uint8_t*& dst) const {
+ float oppAmount = 1.0f - amount;
+ const float alpha = start.a * oppAmount + end.a * amount;
+ const float a = alpha / 255.0f;
+
+ *dst++ = uint8_t(a * (start.r * oppAmount + end.r * amount));
+ *dst++ = uint8_t(a * (start.g * oppAmount + end.g * amount));
+ *dst++ = uint8_t(a * (start.b * oppAmount + end.b * amount));
+ *dst++ = uint8_t(alpha);
+}
+
+void GradientCache::mixFloats(GradientColor& start, GradientColor& end, float amount,
+ uint8_t*& dst) const {
+ float oppAmount = 1.0f - amount;
+ const float a = start.a * oppAmount + end.a * amount;
+
+ float* d = (float*) dst;
+ *d++ = a * (start.r * oppAmount + end.r * amount);
+ *d++ = a * (start.g * oppAmount + end.g * amount);
+ *d++ = a * (start.b * oppAmount + end.b * amount);
+ *d++ = a;
+
+ dst += 4 * sizeof(float);
+}
+
void GradientCache::generateTexture(uint32_t* colors, float* positions,
int count, Texture* texture) {
-
const uint32_t width = texture->width;
- const GLsizei rowBytes = width * GRADIENT_BYTES_PER_PIXEL;
- uint32_t pixels[width * texture->height];
+ const GLsizei rowBytes = width * bytesPerPixel();
+ uint8_t pixels[rowBytes * texture->height];
- int currentPos = 1;
+ static ChannelSplitter gSplitters[] = {
+ &android::uirenderer::GradientCache::splitToBytes,
+ &android::uirenderer::GradientCache::splitToFloats,
+ };
+ ChannelSplitter split = gSplitters[mUseFloatTexture];
- float startA = (colors[0] >> 24) & 0xff;
- float startR = (colors[0] >> 16) & 0xff;
- float startG = (colors[0] >> 8) & 0xff;
- float startB = (colors[0] >> 0) & 0xff;
+ static ChannelMixer gMixers[] = {
+ &android::uirenderer::GradientCache::mixBytes,
+ &android::uirenderer::GradientCache::mixFloats,
+ };
+ ChannelMixer mix = gMixers[mUseFloatTexture];
- float endA = (colors[1] >> 24) & 0xff;
- float endR = (colors[1] >> 16) & 0xff;
- float endG = (colors[1] >> 8) & 0xff;
- float endB = (colors[1] >> 0) & 0xff;
+ GradientColor start;
+ (this->*split)(colors[0], start);
- float start = positions[0];
- float distance = positions[1] - start;
+ GradientColor end;
+ (this->*split)(colors[1], end);
+
+ int currentPos = 1;
+ float startPos = positions[0];
+ float distance = positions[1] - startPos;
- uint8_t* p = (uint8_t*) pixels;
+ uint8_t* dst = pixels;
for (uint32_t x = 0; x < width; x++) {
float pos = x / float(width - 1);
if (pos > positions[currentPos]) {
- startA = endA;
- startR = endR;
- startG = endG;
- startB = endB;
- start = positions[currentPos];
+ start = end;
+ startPos = positions[currentPos];
currentPos++;
- endA = (colors[currentPos] >> 24) & 0xff;
- endR = (colors[currentPos] >> 16) & 0xff;
- endG = (colors[currentPos] >> 8) & 0xff;
- endB = (colors[currentPos] >> 0) & 0xff;
- distance = positions[currentPos] - start;
+ (this->*split)(colors[currentPos], end);
+ distance = positions[currentPos] - startPos;
}
- float amount = (pos - start) / distance;
- float oppAmount = 1.0f - amount;
-
- const float alpha = startA * oppAmount + endA * amount;
- const float a = alpha / 255.0f;
- *p++ = uint8_t(a * (startR * oppAmount + endR * amount));
- *p++ = uint8_t(a * (startG * oppAmount + endG * amount));
- *p++ = uint8_t(a * (startB * oppAmount + endB * amount));
- *p++ = uint8_t(alpha);
+ float amount = (pos - startPos) / distance;
+ (this->*mix)(start, end, amount, dst);
}
- for (int i = 1; i < GRADIENT_TEXTURE_HEIGHT; i++) {
- memcpy(pixels + width * i, pixels, rowBytes);
- }
+ memcpy(pixels + rowBytes, pixels, rowBytes);
glGenTextures(1, &texture->id);
-
glBindTexture(GL_TEXTURE_2D, texture->id);
- glPixelStorei(GL_UNPACK_ALIGNMENT, GRADIENT_BYTES_PER_PIXEL);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0,
- GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+ 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);
+ } else {
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+ }
texture->setFilter(GL_LINEAR);
texture->setWrap(GL_CLAMP_TO_EDGE);
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
index 3b7c1fa92499..43934d958000 100644
--- a/libs/hwui/GradientCache.h
+++ b/libs/hwui/GradientCache.h
@@ -17,16 +17,15 @@
#ifndef ANDROID_HWUI_GRADIENT_CACHE_H
#define ANDROID_HWUI_GRADIENT_CACHE_H
-#include <GLES2/gl2.h>
+#include <GLES3/gl3.h>
#include <SkShader.h>
+#include <utils/LruCache.h>
#include <utils/Mutex.h>
#include <utils/Vector.h>
#include "Texture.h"
-#include "utils/Compare.h"
-#include "utils/GenerationCache.h"
namespace android {
namespace uirenderer {
@@ -38,7 +37,7 @@ struct GradientCacheEntry {
positions = NULL;
}
- GradientCacheEntry(uint32_t* colors, float* positions, int count) {
+ GradientCacheEntry(uint32_t* colors, float* positions, uint32_t count) {
copy(colors, positions, count);
}
@@ -62,27 +61,24 @@ struct GradientCacheEntry {
return *this;
}
- bool operator<(const GradientCacheEntry& r) const {
- const GradientCacheEntry& rhs = (const GradientCacheEntry&) r;
- LTE_INT(count) {
- int result = memcmp(colors, rhs.colors, count * sizeof(uint32_t));
- if (result< 0) return true;
- else if (result == 0) {
- result = memcmp(positions, rhs.positions, count * sizeof(float));
- if (result < 0) return true;
- }
- }
- return false;
+ hash_t hash() const;
+
+ static int compare(const GradientCacheEntry& lhs, const GradientCacheEntry& rhs);
+
+ bool operator==(const GradientCacheEntry& other) const {
+ return compare(*this, other) == 0;
+ }
+
+ bool operator!=(const GradientCacheEntry& other) const {
+ return compare(*this, other) != 0;
}
uint32_t* colors;
float* positions;
- int count;
- SkShader::TileMode tileMode;
+ uint32_t count;
private:
-
- void copy(uint32_t* colors, float* positions, int count) {
+ void copy(uint32_t* colors, float* positions, uint32_t count) {
this->count = count;
this->colors = new uint32_t[count];
this->positions = new float[count];
@@ -93,6 +89,20 @@ private:
}; // GradientCacheEntry
+// Caching support
+
+inline int strictly_order_type(const GradientCacheEntry& lhs, const GradientCacheEntry& rhs) {
+ return GradientCacheEntry::compare(lhs, rhs) < 0;
+}
+
+inline int compare_type(const GradientCacheEntry& lhs, const GradientCacheEntry& rhs) {
+ return GradientCacheEntry::compare(lhs, rhs);
+}
+
+inline hash_t hash_type(const GradientCacheEntry& entry) {
+ return entry.hash();
+}
+
/**
* A simple LRU gradient cache. The cache has a maximum size expressed in bytes.
* Any texture added to the cache causing the cache to grow beyond the maximum
@@ -150,12 +160,35 @@ private:
void getGradientInfo(const uint32_t* colors, const int count, GradientInfo& info);
- GenerationCache<GradientCacheEntry, Texture*> mCache;
+ size_t bytesPerPixel() const;
+
+ struct GradientColor {
+ float r;
+ float g;
+ float b;
+ float a;
+ };
+
+ typedef void (GradientCache::*ChannelSplitter)(uint32_t inColor,
+ GradientColor& outColor) const;
+
+ void splitToBytes(uint32_t inColor, GradientColor& outColor) const;
+ void splitToFloats(uint32_t inColor, GradientColor& outColor) const;
+
+ typedef void (GradientCache::*ChannelMixer)(GradientColor& start, GradientColor& end,
+ float amount, uint8_t*& dst) const;
+
+ void mixBytes(GradientColor& start, GradientColor& end, float amount, uint8_t*& dst) const;
+ void mixFloats(GradientColor& start, GradientColor& end, float amount, uint8_t*& dst) const;
+
+ LruCache<GradientCacheEntry, Texture*> mCache;
uint32_t mSize;
uint32_t mMaxSize;
GLint mMaxTextureSize;
+ bool mUseFloatTexture;
+ bool mHasNpot;
Vector<SkShader*> mGarbage;
mutable Mutex mLock;
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index 1cdc06306be4..a71829464434 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -18,6 +18,8 @@
#include <utils/Log.h>
+#include "DisplayList.h"
+#include "DeferredDisplayList.h"
#include "Layer.h"
#include "LayerRenderer.h"
#include "OpenGLRenderer.h"
@@ -41,20 +43,89 @@ Layer::Layer(const uint32_t layerWidth, const uint32_t layerHeight) {
renderer = NULL;
displayList = NULL;
fbo = 0;
+ stencil = NULL;
+ debugDrawUpdate = false;
+ deferredList = NULL;
Caches::getInstance().resourceCache.incrementRefcount(this);
}
Layer::~Layer() {
- if (mesh) delete mesh;
- if (meshIndices) delete meshIndices;
if (colorFilter) Caches::getInstance().resourceCache.decrementRefcount(colorFilter);
removeFbo();
deleteTexture();
+
+ delete[] mesh;
+ delete[] meshIndices;
+ delete deferredList;
+}
+
+uint32_t Layer::computeIdealWidth(uint32_t layerWidth) {
+ return uint32_t(ceilf(layerWidth / float(LAYER_SIZE)) * LAYER_SIZE);
+}
+
+uint32_t Layer::computeIdealHeight(uint32_t layerHeight) {
+ return uint32_t(ceilf(layerHeight / float(LAYER_SIZE)) * LAYER_SIZE);
+}
+
+bool Layer::resize(const uint32_t width, const uint32_t height) {
+ uint32_t desiredWidth = computeIdealWidth(width);
+ uint32_t desiredHeight = computeIdealWidth(height);
+
+ if (desiredWidth <= getWidth() && desiredHeight <= getHeight()) {
+ return true;
+ }
+
+ const uint32_t maxTextureSize = Caches::getInstance().maxTextureSize;
+ if (desiredWidth > maxTextureSize || desiredHeight > maxTextureSize) {
+ ALOGW("Layer exceeds max. dimensions supported by the GPU (%dx%d, max=%dx%d)",
+ desiredWidth, desiredHeight, maxTextureSize, maxTextureSize);
+ return false;
+ }
+
+ uint32_t oldWidth = getWidth();
+ uint32_t oldHeight = getHeight();
+
+ setSize(desiredWidth, desiredHeight);
+
+ if (fbo) {
+ Caches::getInstance().activeTexture(0);
+ bindTexture();
+ allocateTexture();
+
+ if (glGetError() != GL_NO_ERROR) {
+ setSize(oldWidth, oldHeight);
+ return false;
+ }
+ }
+
+ if (stencil) {
+ stencil->bind();
+ stencil->resize(desiredWidth, desiredHeight);
+
+ if (glGetError() != GL_NO_ERROR) {
+ setSize(oldWidth, oldHeight);
+ return false;
+ }
+ }
+
+ return true;
}
-void Layer::removeFbo() {
+void Layer::removeFbo(bool flush) {
+ if (stencil) {
+ GLuint previousFbo;
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
+ if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
+ if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+
+ Caches::getInstance().renderBufferCache.put(stencil);
+ stencil = NULL;
+ }
+
if (fbo) {
- LayerRenderer::flushLayer(this);
+ if (flush) LayerRenderer::flushLayer(this);
+ // If put fails the cache will delete the FBO
Caches::getInstance().fboCache.put(fbo);
fbo = 0;
}
@@ -74,7 +145,61 @@ void Layer::setColorFilter(SkiaColorFilter* filter) {
}
}
+void Layer::defer() {
+ if (!deferredList) {
+ deferredList = new DeferredDisplayList;
+ }
+ DeferStateStruct deferredState(*deferredList, *renderer,
+ DisplayList::kReplayFlag_ClipChildren);
+
+ const float width = layer.getWidth();
+ const float height = layer.getHeight();
+
+ if (dirtyRect.isEmpty() || (dirtyRect.left <= 0 && dirtyRect.top <= 0 &&
+ dirtyRect.right >= width && dirtyRect.bottom >= height)) {
+ dirtyRect.set(0, 0, width, height);
+ }
+
+ renderer->initViewport(width, height);
+ renderer->setupFrameState(dirtyRect.left, dirtyRect.top,
+ dirtyRect.right, dirtyRect.bottom, !isBlend());
+
+ displayList->defer(deferredState, 0);
+
+ deferredUpdateScheduled = false;
+}
+
+void Layer::flush() {
+ if (deferredList) {
+ renderer->setViewport(layer.getWidth(), layer.getHeight());
+ renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom,
+ !isBlend());
+
+ deferredList->flush(*renderer, dirtyRect);
+
+ renderer->finish();
+ renderer = NULL;
+ dirtyRect.setEmpty();
+ displayList = NULL;
+ }
+}
+
+void Layer::render() {
+ renderer->setViewport(layer.getWidth(), layer.getHeight());
+ renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom,
+ !isBlend());
+
+ renderer->drawDisplayList(displayList, dirtyRect, DisplayList::kReplayFlag_ClipChildren);
+
+ renderer->finish();
+ renderer = NULL;
+
+ dirtyRect.setEmpty();
+
+ deferredUpdateScheduled = false;
+ displayList = NULL;
+}
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index e1f6a70e7ad1..715dfa46660d 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -23,9 +23,11 @@
#include <ui/Region.h>
+#include <SkPaint.h>
#include <SkXfermode.h>
#include "Rect.h"
+#include "RenderBuffer.h"
#include "SkiaColorFilter.h"
#include "Texture.h"
#include "Vertex.h"
@@ -40,6 +42,8 @@ namespace uirenderer {
// Forward declarations
class OpenGLRenderer;
class DisplayList;
+class DeferredDisplayList;
+class DeferStateStruct;
/**
* A layer has dimensions and is backed by an OpenGL texture or FBO.
@@ -48,7 +52,15 @@ struct Layer {
Layer(const uint32_t layerWidth, const uint32_t layerHeight);
~Layer();
- void removeFbo();
+ static uint32_t computeIdealWidth(uint32_t layerWidth);
+ static uint32_t computeIdealHeight(uint32_t layerHeight);
+
+ /**
+ * Calling this method will remove (either by recycling or
+ * destroying) the associated FBO, if present, and any render
+ * buffer (stencil for instance.)
+ */
+ void removeFbo(bool flush = true);
/**
* Sets this layer's region to a rectangle. Computes the appropriate
@@ -78,14 +90,25 @@ struct Layer {
deferredUpdateScheduled = true;
}
- inline uint32_t getWidth() {
+ inline uint32_t getWidth() const {
return texture.width;
}
- inline uint32_t getHeight() {
+ inline uint32_t getHeight() const {
return texture.height;
}
+ /**
+ * Resize the layer and its texture if needed.
+ *
+ * @param width The new width of the layer
+ * @param height The new height of the layer
+ *
+ * @return True if the layer was resized or nothing happened, false if
+ * a failure occurred during the resizing operation
+ */
+ bool resize(const uint32_t width, const uint32_t height);
+
void setSize(uint32_t width, uint32_t height) {
texture.width = width;
texture.height = height;
@@ -97,7 +120,7 @@ struct Layer {
texture.blend = blend;
}
- inline bool isBlend() {
+ inline bool isBlend() const {
return texture.blend;
}
@@ -110,11 +133,11 @@ struct Layer {
this->mode = mode;
}
- inline int getAlpha() {
+ inline int getAlpha() const {
return alpha;
}
- inline SkXfermode::Mode getMode() {
+ inline SkXfermode::Mode getMode() const {
return mode;
}
@@ -122,7 +145,7 @@ struct Layer {
this->empty = empty;
}
- inline bool isEmpty() {
+ inline bool isEmpty() const {
return empty;
}
@@ -130,15 +153,29 @@ struct Layer {
this->fbo = fbo;
}
- inline GLuint getFbo() {
+ inline GLuint getFbo() const {
return fbo;
}
- inline GLuint getTexture() {
+ inline void setStencilRenderBuffer(RenderBuffer* renderBuffer) {
+ if (RenderBuffer::isStencilBuffer(renderBuffer->getFormat())) {
+ this->stencil = renderBuffer;
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, stencil->getName());
+ } else {
+ ALOGE("The specified render buffer is not a stencil buffer");
+ }
+ }
+
+ inline RenderBuffer* getStencilRenderBuffer() const {
+ return stencil;
+ }
+
+ inline GLuint getTexture() const {
return texture.id;
}
- inline GLenum getRenderTarget() {
+ inline GLenum getRenderTarget() const {
return renderTarget;
}
@@ -154,7 +191,7 @@ struct Layer {
texture.setFilter(filter, bindTexture, force, renderTarget);
}
- inline bool isCacheable() {
+ inline bool isCacheable() const {
return cacheable;
}
@@ -162,7 +199,7 @@ struct Layer {
this->cacheable = cacheable;
}
- inline bool isDirty() {
+ inline bool isDirty() const {
return dirty;
}
@@ -170,7 +207,7 @@ struct Layer {
this->dirty = dirty;
}
- inline bool isTextureLayer() {
+ inline bool isTextureLayer() const {
return textureLayer;
}
@@ -178,18 +215,24 @@ struct Layer {
this->textureLayer = textureLayer;
}
- inline SkiaColorFilter* getColorFilter() {
+ inline SkiaColorFilter* getColorFilter() const {
return colorFilter;
}
ANDROID_API void setColorFilter(SkiaColorFilter* filter);
- inline void bindTexture() {
+ inline void bindTexture() const {
if (texture.id) {
glBindTexture(renderTarget, texture.id);
}
}
+ inline void bindStencilRenderBuffer() const {
+ if (stencil) {
+ stencil->bind();
+ }
+ }
+
inline void generateTexture() {
if (!texture.id) {
glGenTextures(1, &texture.id);
@@ -212,15 +255,15 @@ struct Layer {
texture.id = 0;
}
- inline void deleteFbo() {
- if (fbo) glDeleteFramebuffers(1, &fbo);
- }
-
- inline void allocateTexture(GLenum format, GLenum storage) {
+ inline void allocateTexture() {
#if DEBUG_LAYERS
ALOGD(" Allocate layer: %dx%d", getWidth(), getHeight());
#endif
- glTexImage2D(renderTarget, 0, format, getWidth(), getHeight(), 0, format, storage, NULL);
+ if (texture.id) {
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ glTexImage2D(renderTarget, 0, GL_RGBA, getWidth(), getHeight(), 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ }
}
inline mat4& getTexTransform() {
@@ -231,6 +274,10 @@ struct Layer {
return transform;
}
+ void defer();
+ void flush();
+ void render();
+
/**
* Bounds of the layer.
*/
@@ -239,6 +286,10 @@ struct Layer {
* Texture coordinates of the layer.
*/
Rect texCoords;
+ /**
+ * Clipping rectangle.
+ */
+ Rect clipRect;
/**
* Dirty region indicating what parts of the layer
@@ -265,6 +316,7 @@ struct Layer {
OpenGLRenderer* renderer;
DisplayList* displayList;
Rect dirtyRect;
+ bool debugDrawUpdate;
private:
/**
@@ -274,6 +326,11 @@ private:
GLuint fbo;
/**
+ * The render buffer used as the stencil buffer.
+ */
+ RenderBuffer* stencil;
+
+ /**
* Indicates whether this layer has been used already.
*/
bool empty;
@@ -329,6 +386,12 @@ private:
*/
mat4 transform;
+ /**
+ * Used to defer display lists when the layer is updated with a
+ * display list.
+ */
+ DeferredDisplayList* deferredList;
+
}; // struct Layer
}; // namespace uirenderer
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index ce74ceec1b27..a0709afa010f 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -21,7 +21,6 @@
#include <utils/Log.h>
#include "Caches.h"
-#include "Debug.h"
#include "LayerCache.h"
#include "Properties.h"
@@ -67,6 +66,14 @@ void LayerCache::setMaxSize(uint32_t maxSize) {
// Caching
///////////////////////////////////////////////////////////////////////////////
+int LayerCache::LayerEntry::compare(const LayerCache::LayerEntry& lhs,
+ const LayerCache::LayerEntry& rhs) {
+ int deltaInt = int(lhs.mWidth) - int(rhs.mWidth);
+ if (deltaInt != 0) return deltaInt;
+
+ return int(lhs.mHeight) - int(rhs.mHeight);
+}
+
void LayerCache::deleteLayer(Layer* layer) {
if (layer) {
LAYER_LOGD("Destroying layer %dx%d, fbo %d", layer->getWidth(), layer->getHeight(),
@@ -128,31 +135,6 @@ void LayerCache::dump() {
}
}
-bool LayerCache::resize(Layer* layer, const uint32_t width, const uint32_t height) {
- // TODO: We should be smarter and see if we have a texture of the appropriate
- // size already in the cache, and reuse it instead of creating a new one
-
- LayerEntry entry(width, height);
- if (entry.mWidth <= layer->getWidth() && entry.mHeight <= layer->getHeight()) {
- return true;
- }
-
- uint32_t oldWidth = layer->getWidth();
- uint32_t oldHeight = layer->getHeight();
-
- Caches::getInstance().activeTexture(0);
- layer->bindTexture();
- layer->setSize(entry.mWidth, entry.mHeight);
- layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE);
-
- if (glGetError() != GL_NO_ERROR) {
- layer->setSize(oldWidth, oldHeight);
- return false;
- }
-
- return true;
-}
-
bool LayerCache::put(Layer* layer) {
if (!layer->isCacheable()) return false;
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index fd698e24b777..221bfe000de1 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -19,7 +19,6 @@
#include "Debug.h"
#include "Layer.h"
-#include "Properties.h"
#include "utils/SortedList.h"
namespace android {
@@ -54,7 +53,7 @@ public:
* size of the cache goes down.
*
* @param width The desired width of the layer
- * @param width The desired height of the layer
+ * @param height The desired height of the layer
*/
Layer* get(const uint32_t width, const uint32_t height);
@@ -72,17 +71,6 @@ public:
* Clears the cache. This causes all layers to be deleted.
*/
void clear();
- /**
- * Resize the specified layer if needed.
- *
- * @param layer The layer to resize
- * @param width The new width of the layer
- * @param height The new height of the layer
- *
- * @return True if the layer was resized or nothing happened, false if
- * a failure occurred during the resizing operation
- */
- bool resize(Layer* layer, const uint32_t width, const uint32_t height);
/**
* Sets the maximum size of the cache in bytes.
@@ -103,31 +91,36 @@ public:
void dump();
private:
- void deleteLayer(Layer* layer);
-
struct LayerEntry {
LayerEntry():
mLayer(NULL), mWidth(0), mHeight(0) {
}
LayerEntry(const uint32_t layerWidth, const uint32_t layerHeight): mLayer(NULL) {
- mWidth = uint32_t(ceilf(layerWidth / float(LAYER_SIZE)) * LAYER_SIZE);
- mHeight = uint32_t(ceilf(layerHeight / float(LAYER_SIZE)) * LAYER_SIZE);
+ mWidth = Layer::computeIdealWidth(layerWidth);
+ mHeight = Layer::computeIdealHeight(layerHeight);
}
LayerEntry(Layer* layer):
mLayer(layer), mWidth(layer->getWidth()), mHeight(layer->getHeight()) {
}
- bool operator<(const LayerEntry& rhs) const {
- if (mWidth == rhs.mWidth) {
- return mHeight < rhs.mHeight;
- }
- return mWidth < rhs.mWidth;
+ static int compare(const LayerEntry& lhs, const LayerEntry& rhs);
+
+ bool operator==(const LayerEntry& other) const {
+ return compare(*this, other) == 0;
}
- bool operator==(const LayerEntry& rhs) const {
- return mWidth == rhs.mWidth && mHeight == rhs.mHeight;
+ bool operator!=(const LayerEntry& other) const {
+ 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);
}
Layer* mLayer;
@@ -135,6 +128,8 @@ private:
uint32_t mHeight;
}; // struct LayerEntry
+ void deleteLayer(Layer* layer);
+
SortedList<LayerEntry> mCache;
uint32_t mSize;
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 3484d411c130..3e55fffd93a9 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -62,6 +62,7 @@ status_t LayerRenderer::prepareDirty(float left, float top, float right, float b
android::Rect r(dirty.left, dirty.top, dirty.right, dirty.bottom);
mLayer->region.subtractSelf(r);
}
+ mLayer->clipRect.set(dirty);
return OpenGLRenderer::prepareDirty(dirty.left, dirty.top, dirty.right, dirty.bottom, opaque);
}
@@ -91,41 +92,45 @@ void LayerRenderer::finish() {
// who will invoke OpenGLRenderer::resume()
}
-GLint LayerRenderer::getTargetFbo() {
+GLint LayerRenderer::getTargetFbo() const {
return mLayer->getFbo();
}
-bool LayerRenderer::suppressErrorChecks() {
+bool LayerRenderer::suppressErrorChecks() const {
return true;
}
///////////////////////////////////////////////////////////////////////////////
-// Dirty region tracking
+// Layer support
///////////////////////////////////////////////////////////////////////////////
-bool LayerRenderer::hasLayer() {
+bool LayerRenderer::hasLayer() const {
return true;
}
-Region* LayerRenderer::getRegion() {
+void LayerRenderer::ensureStencilBuffer() {
+ attachStencilBufferToLayer(mLayer);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Dirty region tracking
+///////////////////////////////////////////////////////////////////////////////
+
+Region* LayerRenderer::getRegion() const {
if (getSnapshot()->flags & Snapshot::kFlagFboTarget) {
return OpenGLRenderer::getRegion();
}
return &mLayer->region;
}
-// TODO: This implementation is flawed and can generate T-junctions
-// in the mesh, which will in turn produce cracks when the
-// mesh is rotated/skewed. The easiest way to fix this would
-// be, for each row, to add new vertices shared with the previous
-// row when the two rows share an edge.
-// In practice, T-junctions do not appear often so this has yet
-// to be fixed.
+// TODO: This implementation uses a very simple approach to fixing T-junctions which keeps the
+// results as rectangles, and is thus not necessarily efficient in the geometry
+// produced. Eventually, it may be better to develop triangle-based mechanism.
void LayerRenderer::generateMesh() {
if (mLayer->region.isRect() || mLayer->region.isEmpty()) {
if (mLayer->mesh) {
- delete mLayer->mesh;
- delete mLayer->meshIndices;
+ delete[] mLayer->mesh;
+ delete[] mLayer->meshIndices;
mLayer->mesh = NULL;
mLayer->meshIndices = NULL;
@@ -136,14 +141,20 @@ void LayerRenderer::generateMesh() {
return;
}
+ // 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 (see
+ // OpenGLRenderer::composeLayerRegion())
+ Region safeRegion = Region::createTJunctionFreeRegion(mLayer->region);
+
size_t count;
- const android::Rect* rects = mLayer->region.getArray(&count);
+ const android::Rect* rects = safeRegion.getArray(&count);
GLsizei elementCount = count * 6;
if (mLayer->mesh && mLayer->meshElementCount < elementCount) {
- delete mLayer->mesh;
- delete mLayer->meshIndices;
+ delete[] mLayer->mesh;
+ delete[] mLayer->meshIndices;
mLayer->mesh = NULL;
mLayer->meshIndices = NULL;
@@ -211,6 +222,21 @@ Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque
return NULL;
}
+ // 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
+ 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)",
+ width, height, maxTextureSize, maxTextureSize);
+
+ // Creating a new layer always increment its refcount by 1, this allows
+ // us to destroy the layer object if one was created for us
+ Caches::getInstance().resourceCache.decrementRefcount(layer);
+
+ return NULL;
+ }
+
layer->setFbo(fbo);
layer->layer.set(0.0f, 0.0f, width, height);
layer->texCoords.set(0.0f, height / float(layer->getHeight()),
@@ -230,16 +256,13 @@ Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque
// Initialize the texture if needed
if (layer->isEmpty()) {
layer->setEmpty(false);
- layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE);
+ layer->allocateTexture();
+ // This should only happen if we run out of memory
if (glGetError() != GL_NO_ERROR) {
- ALOGD("Could not allocate texture for layer (fbo=%d %dx%d)",
- fbo, width, height);
-
+ ALOGE("Could not allocate texture for layer (fbo=%d %dx%d)", fbo, width, height);
glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
-
- Caches::getInstance().resourceCache.decrementRefcount(layer);
-
+ caches.resourceCache.decrementRefcount(layer);
return NULL;
}
}
@@ -256,12 +279,11 @@ bool LayerRenderer::resizeLayer(Layer* layer, uint32_t width, uint32_t height) {
if (layer) {
LAYER_RENDERER_LOGD("Resizing layer fbo = %d to %dx%d", layer->getFbo(), width, height);
- if (Caches::getInstance().layerCache.resize(layer, width, height)) {
+ if (layer->resize(width, height)) {
layer->layer.set(0.0f, 0.0f, width, height);
layer->texCoords.set(0.0f, height / float(layer->getHeight()),
width / float(layer->getWidth()), 0.0f);
} else {
- Caches::getInstance().resourceCache.decrementRefcount(layer);
return false;
}
}
@@ -342,7 +364,7 @@ void LayerRenderer::flushLayer(Layer* layer) {
if (layer && fbo) {
// If possible, discard any enqueud operations on deferred
// rendering architectures
- if (Caches::getInstance().extensions.hasDiscardFramebuffer()) {
+ if (Extensions::getInstance().hasDiscardFramebuffer()) {
GLuint previousFbo;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, fbo);
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
index c44abce11768..5f867317ecf6 100644
--- a/libs/hwui/LayerRenderer.h
+++ b/libs/hwui/LayerRenderer.h
@@ -64,10 +64,11 @@ public:
static void flushLayer(Layer* layer);
protected:
- virtual bool hasLayer();
- virtual Region* getRegion();
- virtual GLint getTargetFbo();
- virtual bool suppressErrorChecks();
+ virtual void ensureStencilBuffer();
+ virtual bool hasLayer() const;
+ virtual Region* getRegion() const;
+ virtual GLint getTargetFbo() const;
+ virtual bool suppressErrorChecks() const;
private:
void generateMesh();
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index a924362bf312..6a5ea510482b 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -24,12 +24,26 @@
#include <SkMatrix.h>
-#include "utils/Compare.h"
#include "Matrix.h"
namespace android {
namespace uirenderer {
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+static const float EPSILON = 0.0000001f;
+
+///////////////////////////////////////////////////////////////////////////////
+// Matrix
+///////////////////////////////////////////////////////////////////////////////
+
+const Matrix4& Matrix4::identity() {
+ static Matrix4 sIdentity;
+ return sIdentity;
+}
+
void Matrix4::loadIdentity() {
data[kScaleX] = 1.0f;
data[kSkewY] = 0.0f;
@@ -51,44 +65,91 @@ void Matrix4::loadIdentity() {
data[kTranslateZ] = 0.0f;
data[kPerspective2] = 1.0f;
- mIsIdentity = true;
- mSimpleMatrix = true;
+ mType = kTypeIdentity | kTypeRectToRect;
+}
+
+static bool isZero(float f) {
+ return fabs(f) <= EPSILON;
+}
+
+uint32_t Matrix4::getType() const {
+ if (mType & kTypeUnknown) {
+ mType = kTypeIdentity;
+
+ if (data[kPerspective0] != 0.0f || data[kPerspective1] != 0.0f ||
+ data[kPerspective2] != 1.0f) {
+ mType |= kTypePerspective;
+ }
+
+ if (data[kTranslateX] != 0.0f || data[kTranslateY] != 0.0f) {
+ mType |= kTypeTranslate;
+ }
+
+ float m00 = data[kScaleX];
+ float m01 = data[kSkewX];
+ float m10 = data[kSkewY];
+ float m11 = data[kScaleY];
+
+ if (m01 != 0.0f || m10 != 0.0f) {
+ mType |= kTypeAffine;
+ }
+
+ if (m00 != 1.0f || m11 != 1.0f) {
+ mType |= kTypeScale;
+ }
+
+ // The following section determines whether the matrix will preserve
+ // rectangles. For instance, a rectangle transformed by a pure
+ // translation matrix will result in a rectangle. A rectangle
+ // transformed by a 45 degrees rotation matrix is not a rectangle.
+ // If the matrix has a perspective component then we already know
+ // it doesn't preserve rectangles.
+ if (!(mType & kTypePerspective)) {
+ if ((isZero(m00) && isZero(m11) && !isZero(m01) && !isZero(m10)) ||
+ (isZero(m01) && isZero(m10) && !isZero(m00) && !isZero(m11))) {
+ mType |= kTypeRectToRect;
+ }
+ }
+ }
+ return mType;
+}
+
+uint32_t Matrix4::getGeometryType() const {
+ return getType() & sGeometryMask;
+}
+
+bool Matrix4::rectToRect() const {
+ return getType() & kTypeRectToRect;
}
bool Matrix4::changesBounds() const {
- return !(data[0] == 1.0f && data[1] == 0.0f && data[2] == 0.0f && data[4] == 0.0f &&
- data[5] == 1.0f && data[6] == 0.0f && data[8] == 0.0f && data[9] == 0.0f &&
- data[10] == 1.0f);
+ return getType() & (kTypeScale | kTypeAffine | kTypePerspective);
}
bool Matrix4::isPureTranslate() const {
- return mSimpleMatrix && data[kScaleX] == 1.0f && data[kScaleY] == 1.0f;
+ return getGeometryType() == kTypeTranslate;
}
bool Matrix4::isSimple() const {
- return mSimpleMatrix;
+ return getGeometryType() <= (kTypeScale | kTypeTranslate);
}
bool Matrix4::isIdentity() const {
- return mIsIdentity;
+ return getGeometryType() == kTypeIdentity;
}
bool Matrix4::isPerspective() const {
- return data[kPerspective0] != 0.0f || data[kPerspective1] != 0.0f ||
- data[kPerspective2] != 1.0f;
+ return getType() & kTypePerspective;
}
void Matrix4::load(const float* v) {
memcpy(data, v, sizeof(data));
- // TODO: Do something smarter here
- mSimpleMatrix = false;
- mIsIdentity = false;
+ mType = kTypeUnknown;
}
void Matrix4::load(const Matrix4& v) {
memcpy(data, v.data, sizeof(data));
- mSimpleMatrix = v.mSimpleMatrix;
- mIsIdentity = v.mIsIdentity;
+ mType = v.getType();
}
void Matrix4::load(const SkMatrix& v) {
@@ -108,8 +169,14 @@ void Matrix4::load(const SkMatrix& v) {
data[kScaleZ] = 1.0f;
- mSimpleMatrix = (v.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask));
- mIsIdentity = v.isIdentity();
+ // NOTE: The flags are compatible between SkMatrix and this class.
+ // However, SkMatrix::getType() does not return the flag
+ // kRectStaysRect. The return value is masked with 0xF
+ // so we need the extra rectStaysRect() check
+ mType = v.getType();
+ if (v.rectStaysRect()) {
+ mType |= kTypeRectToRect;
+ }
}
void Matrix4::copyTo(SkMatrix& v) const {
@@ -158,19 +225,18 @@ void Matrix4::loadInverse(const Matrix4& v) {
data[kPerspective2] = (v.data[kScaleX] * v.data[kScaleY] -
v.data[kSkewX] * v.data[kSkewY]) * scale;
- mSimpleMatrix = v.mSimpleMatrix;
- mIsIdentity = v.mIsIdentity;
+ mType = kTypeUnknown;
}
void Matrix4::copyTo(float* v) const {
memcpy(v, data, sizeof(data));
}
-float Matrix4::getTranslateX() {
+float Matrix4::getTranslateX() const {
return data[kTranslateX];
}
-float Matrix4::getTranslateY() {
+float Matrix4::getTranslateY() const {
return data[kTranslateY];
}
@@ -178,7 +244,7 @@ void Matrix4::multiply(float v) {
for (int i = 0; i < 16; i++) {
data[i] *= v;
}
- mIsIdentity = false;
+ mType = kTypeUnknown;
}
void Matrix4::loadTranslate(float x, float y, float z) {
@@ -188,7 +254,7 @@ void Matrix4::loadTranslate(float x, float y, float z) {
data[kTranslateY] = y;
data[kTranslateZ] = z;
- mIsIdentity = false;
+ mType = kTypeTranslate | kTypeRectToRect;
}
void Matrix4::loadScale(float sx, float sy, float sz) {
@@ -198,7 +264,7 @@ void Matrix4::loadScale(float sx, float sy, float sz) {
data[kScaleY] = sy;
data[kScaleZ] = sz;
- mIsIdentity = false;
+ mType = kTypeScale | kTypeRectToRect;
}
void Matrix4::loadSkew(float sx, float sy) {
@@ -216,8 +282,23 @@ void Matrix4::loadSkew(float sx, float sy) {
data[kPerspective1] = 0.0f;
data[kPerspective2] = 1.0f;
- mSimpleMatrix = false;
- mIsIdentity = false;
+ mType = kTypeUnknown;
+}
+
+void Matrix4::loadRotate(float angle) {
+ angle *= float(M_PI / 180.0f);
+ float c = cosf(angle);
+ float s = sinf(angle);
+
+ loadIdentity();
+
+ data[kScaleX] = c;
+ data[kSkewX] = -s;
+
+ data[kSkewY] = s;
+ data[kScaleY] = c;
+
+ mType = kTypeUnknown;
}
void Matrix4::loadRotate(float angle, float x, float y, float z) {
@@ -257,8 +338,7 @@ void Matrix4::loadRotate(float angle, float x, float y, float z) {
data[6] = yz * nc + xs;
data[kScaleZ] = z * z * nc + c;
- mSimpleMatrix = false;
- mIsIdentity = false;
+ mType = kTypeUnknown;
}
void Matrix4::loadMultiply(const Matrix4& u, const Matrix4& v) {
@@ -282,8 +362,7 @@ void Matrix4::loadMultiply(const Matrix4& u, const Matrix4& v) {
set(i, 3, w);
}
- mSimpleMatrix = u.mSimpleMatrix && v.mSimpleMatrix;
- mIsIdentity = false;
+ mType = kTypeUnknown;
}
void Matrix4::loadOrtho(float left, float right, float bottom, float top, float near, float far) {
@@ -296,13 +375,13 @@ void Matrix4::loadOrtho(float left, float right, float bottom, float top, float
data[kTranslateY] = -(top + bottom) / (top - bottom);
data[kTranslateZ] = -(far + near) / (far - near);
- mIsIdentity = false;
+ mType = kTypeTranslate | kTypeScale | kTypeRectToRect;
}
#define MUL_ADD_STORE(a, b, c) a = (a) * (b) + (c)
void Matrix4::mapPoint(float& x, float& y) const {
- if (mSimpleMatrix) {
+ if (isSimple()) {
MUL_ADD_STORE(x, data[kScaleX], data[kTranslateX]);
MUL_ADD_STORE(y, data[kScaleY], data[kTranslateY]);
return;
@@ -318,7 +397,7 @@ void Matrix4::mapPoint(float& x, float& y) const {
}
void Matrix4::mapRect(Rect& r) const {
- if (mSimpleMatrix) {
+ if (isSimple()) {
MUL_ADD_STORE(r.left, data[kScaleX], data[kTranslateX]);
MUL_ADD_STORE(r.right, data[kScaleX], data[kTranslateX]);
MUL_ADD_STORE(r.top, data[kScaleY], data[kTranslateY]);
@@ -375,8 +454,16 @@ void Matrix4::mapRect(Rect& r) const {
}
}
+void Matrix4::decomposeScale(float& sx, float& sy) const {
+ float len;
+ len = data[mat4::kScaleX] * data[mat4::kScaleX] + data[mat4::kSkewX] * data[mat4::kSkewX];
+ sx = copysignf(sqrtf(len), data[mat4::kScaleX]);
+ len = data[mat4::kScaleY] * data[mat4::kScaleY] + data[mat4::kSkewY] * data[mat4::kSkewY];
+ sy = copysignf(sqrtf(len), data[mat4::kScaleY]);
+}
+
void Matrix4::dump() const {
- ALOGD("Matrix4[simple=%d", mSimpleMatrix);
+ ALOGD("Matrix4[simple=%d, type=0x%x", isSimple(), getType());
ALOGD(" %f %f %f %f", data[kScaleX], data[kSkewX], data[8], data[kTranslateX]);
ALOGD(" %f %f %f %f", data[kSkewY], data[kScaleY], data[9], data[kTranslateY]);
ALOGD(" %f %f %f %f", data[2], data[6], data[kScaleZ], data[kTranslateZ]);
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index f86823d0a5b5..75e280cf502b 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -48,6 +48,21 @@ public:
kPerspective2 = 15
};
+ // NOTE: The flags from kTypeIdentity to kTypePerspective
+ // must be kept in sync with the type flags found
+ // in SkMatrix
+ enum Type {
+ kTypeIdentity = 0,
+ kTypeTranslate = 0x1,
+ kTypeScale = 0x2,
+ kTypeAffine = 0x4,
+ kTypePerspective = 0x8,
+ kTypeRectToRect = 0x10,
+ kTypeUnknown = 0x20,
+ };
+
+ static const int sGeometryMask = 0xf;
+
Matrix4() {
loadIdentity();
}
@@ -64,6 +79,28 @@ public:
load(v);
}
+ float operator[](int index) const {
+ return data[index];
+ }
+
+ float& operator[](int index) {
+ mType = kTypeUnknown;
+ return data[index];
+ }
+
+ Matrix4& operator=(const SkMatrix& v) {
+ load(v);
+ return *this;
+ }
+
+ friend bool operator==(const Matrix4& a, const Matrix4& b) {
+ return !memcmp(&a.data[0], &b.data[0], 16 * sizeof(float));
+ }
+
+ friend bool operator!=(const Matrix4& a, const Matrix4& b) {
+ return !(a == b);
+ }
+
void loadIdentity();
void load(const float* v);
@@ -75,11 +112,14 @@ public:
void loadTranslate(float x, float y, float z);
void loadScale(float sx, float sy, float sz);
void loadSkew(float sx, float sy);
+ void loadRotate(float angle);
void loadRotate(float angle, float x, float y, float z);
void loadMultiply(const Matrix4& u, const Matrix4& v);
void loadOrtho(float left, float right, float bottom, float top, float near, float far);
+ uint32_t getType() const;
+
void multiply(const Matrix4& v) {
Matrix4 u;
u.loadMultiply(*this, v);
@@ -112,10 +152,14 @@ public:
multiply(u);
}
- bool isPureTranslate() const;
+ /**
+ * If the matrix is identity or translate and/or scale.
+ */
bool isSimple() const;
+ bool isPureTranslate() const;
bool isIdentity() const;
bool isPerspective() const;
+ bool rectToRect() const;
bool changesBounds() const;
@@ -125,14 +169,17 @@ public:
void mapRect(Rect& r) const;
void mapPoint(float& x, float& y) const;
- float getTranslateX();
- float getTranslateY();
+ float getTranslateX() const;
+ float getTranslateY() const;
+
+ void decomposeScale(float& sx, float& sy) const;
void dump() const;
+ static const Matrix4& identity();
+
private:
- bool mSimpleMatrix;
- bool mIsIdentity;
+ mutable uint32_t mType;
inline float get(int i, int j) const {
return data[i * 4 + j];
@@ -141,6 +188,9 @@ private:
inline void set(int i, int j, float v) {
data[i * 4 + j] = v;
}
+
+ uint32_t getGeometryType() const;
+
}; // class Matrix4
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index bc30738113d4..e18d92223ed4 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -32,8 +32,9 @@
#include <ui/Rect.h>
#include "OpenGLRenderer.h"
+#include "DeferredDisplayList.h"
#include "DisplayListRenderer.h"
-#include "PathRenderer.h"
+#include "PathTessellator.h"
#include "Properties.h"
#include "Vector.h"
@@ -80,7 +81,7 @@ static const Blender gBlends[] = {
{ SkXfermode::kDstATop_Mode, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA },
{ SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
{ SkXfermode::kPlus_Mode, GL_ONE, GL_ONE },
- { SkXfermode::kMultiply_Mode, GL_ZERO, GL_SRC_COLOR },
+ { SkXfermode::kModulate_Mode, GL_ZERO, GL_SRC_COLOR },
{ SkXfermode::kScreen_Mode, GL_ONE, GL_ONE_MINUS_SRC_COLOR }
};
@@ -101,7 +102,7 @@ static const Blender gBlendsSwap[] = {
{ SkXfermode::kDstATop_Mode, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
{ SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
{ SkXfermode::kPlus_Mode, GL_ONE, GL_ONE },
- { SkXfermode::kMultiply_Mode, GL_DST_COLOR, GL_ZERO },
+ { SkXfermode::kModulate_Mode, GL_DST_COLOR, GL_ZERO },
{ SkXfermode::kScreen_Mode, GL_ONE_MINUS_DST_COLOR, GL_ONE }
};
@@ -109,15 +110,18 @@ static const Blender gBlendsSwap[] = {
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
-OpenGLRenderer::OpenGLRenderer(): mCaches(Caches::getInstance()) {
- mShader = NULL;
- mColorFilter = NULL;
- mHasShadow = false;
- mHasDrawFilter = false;
+OpenGLRenderer::OpenGLRenderer():
+ mCaches(Caches::getInstance()), mExtensions(Extensions::getInstance()) {
+ mDrawModifiers.mShader = NULL;
+ mDrawModifiers.mColorFilter = NULL;
+ mDrawModifiers.mOverrideLayerAlpha = 1.0f;
+ mDrawModifiers.mHasShadow = false;
+ mDrawModifiers.mHasDrawFilter = false;
memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices));
mFirstSnapshot = new Snapshot;
+ mFrameStarted = false;
mScissorOptimizationDisabled = false;
}
@@ -142,6 +146,18 @@ void OpenGLRenderer::initProperties() {
// Setup
///////////////////////////////////////////////////////////////////////////////
+void OpenGLRenderer::setName(const char* name) {
+ if (name) {
+ mName.setTo(name);
+ } else {
+ mName.clear();
+ }
+}
+
+const char* OpenGLRenderer::getName() const {
+ return mName.string();
+}
+
bool OpenGLRenderer::isDeferred() {
return false;
}
@@ -165,46 +181,77 @@ void OpenGLRenderer::initViewport(int width, int height) {
mFirstSnapshot->viewport.set(0, 0, width, height);
}
-status_t OpenGLRenderer::prepare(bool opaque) {
- return prepareDirty(0.0f, 0.0f, mWidth, mHeight, opaque);
-}
-
-status_t OpenGLRenderer::prepareDirty(float left, float top, float right, float bottom,
- bool opaque) {
+void OpenGLRenderer::setupFrameState(float left, float top,
+ float right, float bottom, bool opaque) {
mCaches.clearGarbage();
+ mOpaque = opaque;
mSnapshot = new Snapshot(mFirstSnapshot,
SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
mSnapshot->fbo = getTargetFbo();
mSaveCount = 1;
mSnapshot->setClip(left, top, right, bottom);
- mDirtyClip = true;
+ mTilingClip.set(left, top, right, bottom);
+}
- updateLayers();
+status_t OpenGLRenderer::startFrame() {
+ if (mFrameStarted) return DrawGlInfo::kStatusDone;
+ mFrameStarted = true;
- // If we know that we are going to redraw the entire framebuffer,
- // perform a discard to let the driver know we don't need to preserve
- // the back buffer for this frame.
- if (mCaches.extensions.hasDiscardFramebuffer() &&
- left <= 0.0f && top <= 0.0f && right >= mWidth && bottom >= mHeight) {
- const GLenum attachments[] = { getTargetFbo() == 0 ? GL_COLOR_EXT : GL_COLOR_ATTACHMENT0 };
- glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, attachments);
- }
+ mDirtyClip = true;
+
+ discardFramebuffer(mTilingClip.left, mTilingClip.top, mTilingClip.right, mTilingClip.bottom);
- syncState();
+ glViewport(0, 0, mWidth, mHeight);
// 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();
- mTilingSnapshot = mSnapshot;
- startTiling(mTilingSnapshot, true);
+ startTiling(mSnapshot, true);
debugOverdraw(true, true);
- return clear(left, top, right, bottom, opaque);
+ return clear(mTilingClip.left, mTilingClip.top,
+ mTilingClip.right, mTilingClip.bottom, mOpaque);
+}
+
+status_t OpenGLRenderer::prepare(bool opaque) {
+ return prepareDirty(0.0f, 0.0f, mWidth, mHeight, opaque);
+}
+
+status_t OpenGLRenderer::prepareDirty(float left, float top,
+ float right, float bottom, bool opaque) {
+ setupFrameState(left, top, right, bottom, opaque);
+
+ // Layer renderers will start the frame immediately
+ // The framebuffer renderer will first defer the display list
+ // for each layer and wait until the first drawing command
+ // to start the frame
+ if (mSnapshot->fbo == 0) {
+ syncState();
+ updateLayers();
+ } else {
+ return startFrame();
+ }
+
+ return DrawGlInfo::kStatusDone;
+}
+
+void OpenGLRenderer::discardFramebuffer(float left, float top, float right, float bottom) {
+ // If we know that we are going to redraw the entire framebuffer,
+ // perform a discard to let the driver know we don't need to preserve
+ // the back buffer for this frame.
+ if (mExtensions.hasDiscardFramebuffer() &&
+ left <= 0.0f && top <= 0.0f && right >= mWidth && bottom >= mHeight) {
+ const bool isFbo = getTargetFbo() == 0;
+ const GLenum attachments[] = {
+ isFbo ? (const GLenum) GL_COLOR_EXT : (const GLenum) GL_COLOR_ATTACHMENT0,
+ isFbo ? (const GLenum) GL_STENCIL_EXT : (const GLenum) GL_STENCIL_ATTACHMENT };
+ glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, attachments);
+ }
}
status_t OpenGLRenderer::clear(float left, float top, float right, float bottom, bool opaque) {
@@ -220,8 +267,6 @@ status_t OpenGLRenderer::clear(float left, float top, float right, float bottom,
}
void OpenGLRenderer::syncState() {
- glViewport(0, 0, mWidth, mHeight);
-
if (mCaches.blend) {
glEnable(GL_BLEND);
} else {
@@ -231,13 +276,19 @@ void OpenGLRenderer::syncState() {
void OpenGLRenderer::startTiling(const sp<Snapshot>& s, bool opaque) {
if (!mSuppressTiling) {
- Rect* clip = mTilingSnapshot->clipRect;
- if (s->flags & Snapshot::kFlagIsFboLayer) {
- clip = s->clipRect;
+ Rect* clip = &mTilingClip;
+ if (s->flags & Snapshot::kFlagFboTarget) {
+ clip = &(s->layer->clipRect);
}
- mCaches.startTiling(clip->left, s->height - clip->bottom,
- clip->right - clip->left, clip->bottom - clip->top, opaque);
+ startTiling(*clip, s->height, opaque);
+ }
+}
+
+void OpenGLRenderer::startTiling(const Rect& clip, int windowHeight, bool opaque) {
+ if (!mSuppressTiling) {
+ mCaches.startTiling(clip.left, windowHeight - clip.bottom,
+ clip.right - clip.left, clip.bottom - clip.top, opaque);
}
}
@@ -249,6 +300,12 @@ void OpenGLRenderer::finish() {
renderOverdraw();
endTiling();
+ // When finish() is invoked on FBO 0 we've reached the end
+ // of the current frame
+ if (getTargetFbo() == 0) {
+ mCaches.pathCache.trim();
+ }
+
if (!suppressErrorChecks()) {
#if DEBUG_OPENGL
GLenum status = GL_NO_ERROR;
@@ -279,6 +336,8 @@ void OpenGLRenderer::finish() {
}
#endif
}
+
+ mFrameStarted = false;
}
void OpenGLRenderer::interrupt() {
@@ -291,7 +350,7 @@ void OpenGLRenderer::interrupt() {
mCaches.unbindMeshBuffer();
mCaches.unbindIndicesBuffer();
mCaches.resetVertexPointers();
- mCaches.disbaleTexCoordsVertexArray();
+ mCaches.disableTexCoordsVertexArray();
debugOverdraw(false, false);
}
@@ -373,6 +432,8 @@ status_t OpenGLRenderer::invokeFunctors(Rect& dirty) {
}
status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
+ if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;
+
interrupt();
detachFunctor(functor);
@@ -419,6 +480,10 @@ status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
// Debug
///////////////////////////////////////////////////////////////////////////////
+void OpenGLRenderer::eventMark(const char* name) const {
+ mCaches.eventMark(0, name);
+}
+
void OpenGLRenderer::startMark(const char* name) const {
mCaches.startMark(0, name);
}
@@ -443,10 +508,10 @@ void OpenGLRenderer::debugOverdraw(bool enable, bool clear) {
void OpenGLRenderer::renderOverdraw() {
if (mCaches.debugOverdraw && getTargetFbo() == 0) {
- const Rect* clip = mTilingSnapshot->clipRect;
+ const Rect* clip = &mTilingClip;
mCaches.enableScissor();
- mCaches.setScissor(clip->left, mTilingSnapshot->height - clip->bottom,
+ mCaches.setScissor(clip->left, mFirstSnapshot->height - clip->bottom,
clip->right - clip->left, clip->bottom - clip->top);
mCaches.stencil.enableDebugTest(2);
@@ -466,8 +531,8 @@ void OpenGLRenderer::renderOverdraw() {
///////////////////////////////////////////////////////////////////////////////
bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) {
- if (layer->deferredUpdateScheduled && layer->renderer && layer->displayList) {
- OpenGLRenderer* renderer = layer->renderer;
+ if (layer->deferredUpdateScheduled && layer->renderer &&
+ layer->displayList && layer->displayList->isRenderable()) {
Rect& dirty = layer->dirtyRect;
if (inFrame) {
@@ -475,20 +540,18 @@ bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) {
debugOverdraw(false, false);
}
- renderer->setViewport(layer->layer.getWidth(), layer->layer.getHeight());
- renderer->prepareDirty(dirty.left, dirty.top, dirty.right, dirty.bottom, !layer->isBlend());
- renderer->drawDisplayList(layer->displayList, dirty, DisplayList::kReplayFlag_ClipChildren);
- renderer->finish();
+ if (CC_UNLIKELY(inFrame || mCaches.drawDeferDisabled)) {
+ layer->render();
+ } else {
+ layer->defer();
+ }
if (inFrame) {
resumeAfterLayer();
startTiling(mSnapshot);
}
- dirty.setEmpty();
- layer->deferredUpdateScheduled = false;
- layer->renderer = NULL;
- layer->displayList = NULL;
+ layer->debugDrawUpdate = mCaches.debugLayersUpdates;
return true;
}
@@ -497,25 +560,70 @@ bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) {
}
void OpenGLRenderer::updateLayers() {
+ // If draw deferring is enabled this method will simply defer
+ // the display list of each individual layer. The layers remain
+ // in the layer updates list which will be cleared by flushLayers().
int count = mLayerUpdates.size();
if (count > 0) {
- startMark("Layer Updates");
+ if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
+ startMark("Layer Updates");
+ } else {
+ startMark("Defer Layer Updates");
+ }
- // Note: it is very important to update the layers in reverse order
- for (int i = count - 1; i >= 0; i--) {
+ // Note: it is very important to update the layers in order
+ for (int i = 0; i < count; i++) {
Layer* layer = mLayerUpdates.itemAt(i);
updateLayer(layer, false);
+ if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
+ mCaches.resourceCache.decrementRefcount(layer);
+ }
+ }
+
+ if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
+ mLayerUpdates.clear();
+ glBindFramebuffer(GL_FRAMEBUFFER, getTargetFbo());
+ }
+ endMark();
+ }
+}
+
+void OpenGLRenderer::flushLayers() {
+ int count = mLayerUpdates.size();
+ if (count > 0) {
+ startMark("Apply Layer Updates");
+ char layerName[12];
+
+ // Note: it is very important to update the layers in order
+ for (int i = 0; i < count; i++) {
+ sprintf(layerName, "Layer #%d", i);
+ startMark(layerName);
+
+ Layer* layer = mLayerUpdates.itemAt(i);
+ layer->flush();
mCaches.resourceCache.decrementRefcount(layer);
+
+ endMark();
}
- mLayerUpdates.clear();
+ mLayerUpdates.clear();
glBindFramebuffer(GL_FRAMEBUFFER, getTargetFbo());
+
endMark();
}
}
void OpenGLRenderer::pushLayerUpdate(Layer* layer) {
if (layer) {
+ // Make sure we don't introduce duplicates.
+ // SortedVector would do this automatically but we need to respect
+ // 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) {
+ return;
+ }
+ }
mLayerUpdates.push_back(layer);
mCaches.resourceCache.incrementRefcount(layer);
}
@@ -586,7 +694,10 @@ bool OpenGLRenderer::restoreSnapshot() {
}
if (restoreLayer) {
+ endMark(); // Savelayer
+ startMark("ComposeLayer");
composeLayer(current, previous);
+ endMark();
}
return restoreClip;
@@ -597,38 +708,86 @@ bool OpenGLRenderer::restoreSnapshot() {
///////////////////////////////////////////////////////////////////////////////
int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
- SkPaint* p, int flags) {
+ int alpha, SkXfermode::Mode mode, int flags) {
const GLuint previousFbo = mSnapshot->fbo;
const int count = saveSnapshot(flags);
if (!mSnapshot->isIgnored()) {
- int alpha = 255;
- SkXfermode::Mode mode;
-
- if (p) {
- alpha = p->getAlpha();
- mode = getXfermode(p->getXfermode());
- } else {
- mode = SkXfermode::kSrcOver_Mode;
- }
-
createLayer(left, top, right, bottom, alpha, mode, flags, previousFbo);
}
return count;
}
-int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, int flags) {
- if (alpha >= 255) {
- return saveLayer(left, top, right, bottom, NULL, flags);
+void OpenGLRenderer::calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool fboLayer) {
+ const Rect untransformedBounds(bounds);
+
+ currentTransform().mapRect(bounds);
+
+ // Layers only make sense if they are in the framebuffer's bounds
+ if (bounds.intersect(*mSnapshot->clipRect)) {
+ // We cannot work with sub-pixels in this case
+ bounds.snapToPixelBoundaries();
+
+ // When the layer is not an FBO, we may use glCopyTexImage so we
+ // need to make sure the layer does not extend outside the bounds
+ // of the framebuffer
+ if (!bounds.intersect(mSnapshot->previous->viewport)) {
+ bounds.setEmpty();
+ } else if (fboLayer) {
+ clip.set(bounds);
+ mat4 inverse;
+ inverse.loadInverse(currentTransform());
+ inverse.mapRect(clip);
+ clip.snapToPixelBoundaries();
+ if (clip.intersect(untransformedBounds)) {
+ clip.translate(-untransformedBounds.left, -untransformedBounds.top);
+ bounds.set(untransformedBounds);
+ } else {
+ clip.setEmpty();
+ }
+ }
+ } else {
+ bounds.setEmpty();
+ }
+}
+
+void OpenGLRenderer::updateSnapshotIgnoreForLayer(const Rect& bounds, const Rect& clip,
+ bool fboLayer, int alpha) {
+ if (bounds.isEmpty() || bounds.getWidth() > mCaches.maxTextureSize ||
+ bounds.getHeight() > mCaches.maxTextureSize ||
+ (fboLayer && clip.isEmpty())) {
+ mSnapshot->empty = fboLayer;
} else {
- SkPaint paint;
- paint.setAlpha(alpha);
- return saveLayer(left, top, right, bottom, &paint, flags);
+ mSnapshot->invisible = mSnapshot->invisible || (alpha <= ALPHA_THRESHOLD && fboLayer);
}
}
+int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float bottom,
+ int alpha, SkXfermode::Mode mode, int flags) {
+ const GLuint previousFbo = mSnapshot->fbo;
+ const int count = saveSnapshot(flags);
+
+ if (!mSnapshot->isIgnored() && (flags & SkCanvas::kClipToLayer_SaveFlag)) {
+ // 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)
+
+ Rect bounds(left, top, right, bottom);
+ Rect clip;
+ calculateLayerBoundsAndClip(bounds, clip, true);
+ updateSnapshotIgnoreForLayer(bounds, clip, true, alpha);
+
+ if (!mSnapshot->isIgnored()) {
+ mSnapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
+ mSnapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom);
+ }
+ }
+
+ return count;
+}
+
+
/**
* Layers are viewed by Skia are slightly different than layers in image editing
* programs (for instance.) When a layer is created, previously created layers
@@ -690,46 +849,11 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto
// Window coordinates of the layer
Rect clip;
Rect bounds(left, top, right, bottom);
- Rect untransformedBounds(bounds);
- mSnapshot->transform->mapRect(bounds);
-
- // Layers only make sense if they are in the framebuffer's bounds
- if (bounds.intersect(*mSnapshot->clipRect)) {
- // We cannot work with sub-pixels in this case
- bounds.snapToPixelBoundaries();
-
- // When the layer is not an FBO, we may use glCopyTexImage so we
- // need to make sure the layer does not extend outside the bounds
- // of the framebuffer
- if (!bounds.intersect(mSnapshot->previous->viewport)) {
- bounds.setEmpty();
- } else if (fboLayer) {
- clip.set(bounds);
- mat4 inverse;
- inverse.loadInverse(*mSnapshot->transform);
- inverse.mapRect(clip);
- clip.snapToPixelBoundaries();
- if (clip.intersect(untransformedBounds)) {
- clip.translate(-left, -top);
- bounds.set(untransformedBounds);
- } else {
- clip.setEmpty();
- }
- }
- } else {
- bounds.setEmpty();
- }
-
- if (bounds.isEmpty() || bounds.getWidth() > mCaches.maxTextureSize ||
- bounds.getHeight() > mCaches.maxTextureSize ||
- (fboLayer && clip.isEmpty())) {
- mSnapshot->empty = fboLayer;
- } else {
- mSnapshot->invisible = mSnapshot->invisible || (alpha <= ALPHA_THRESHOLD && fboLayer);
- }
+ calculateLayerBoundsAndClip(bounds, clip, fboLayer);
+ updateSnapshotIgnoreForLayer(bounds, clip, fboLayer, alpha);
// Bail out if we won't draw in this snapshot
- if (mSnapshot->invisible || mSnapshot->empty) {
+ if (mSnapshot->isIgnored()) {
return false;
}
@@ -743,7 +867,7 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto
layer->layer.set(bounds);
layer->texCoords.set(0.0f, bounds.getHeight() / float(layer->getHeight()),
bounds.getWidth() / float(layer->getWidth()), 0.0f);
- layer->setColorFilter(mColorFilter);
+ layer->setColorFilter(mDrawModifiers.mColorFilter);
layer->setBlend(true);
layer->setDirty(false);
@@ -751,6 +875,7 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto
mSnapshot->flags |= Snapshot::kFlagIsLayer;
mSnapshot->layer = layer;
+ startMark("SaveLayer");
if (fboLayer) {
return createFboLayer(layer, bounds, clip, previousFbo);
} else {
@@ -776,18 +901,17 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto
}
bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLuint previousFbo) {
+ layer->clipRect.set(clip);
layer->setFbo(mCaches.fboCache.get());
mSnapshot->region = &mSnapshot->layer->region;
- mSnapshot->flags |= Snapshot::kFlagFboTarget;
-
- mSnapshot->flags |= Snapshot::kFlagIsFboLayer;
+ mSnapshot->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer |
+ Snapshot::kFlagDirtyOrtho;
mSnapshot->fbo = layer->getFbo();
mSnapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
mSnapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom);
mSnapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
mSnapshot->height = bounds.getHeight();
- mSnapshot->flags |= Snapshot::kFlagDirtyOrtho;
mSnapshot->orthoMatrix.load(mOrthoMatrix);
endTiling();
@@ -798,14 +922,14 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLui
// Initialize the texture if needed
if (layer->isEmpty()) {
- layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE);
+ layer->allocateTexture();
layer->setEmpty(false);
}
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
layer->getTexture(), 0);
- startTiling(mSnapshot);
+ startTiling(mSnapshot, true);
// Clear the FBO, expand the clear region by 1 to get nice bilinear filtering
mCaches.enableScissor();
@@ -831,6 +955,8 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
return;
}
+ Layer* layer = current->layer;
+ const Rect& rect = layer->layer;
const bool fboLayer = current->flags & Snapshot::kFlagIsFboLayer;
if (fboLayer) {
@@ -838,6 +964,9 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
// Detach the texture from the FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+
+ layer->removeFbo(false);
+
// Unbind current FBO and restore previous one
glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
debugOverdraw(true, false);
@@ -845,9 +974,6 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
startTiling(previous);
}
- Layer* layer = current->layer;
- const Rect& rect = layer->layer;
-
if (!fboLayer && layer->getAlpha() < 255) {
drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
layer->getAlpha() << 24, SkXfermode::kDstIn_Mode, true);
@@ -875,17 +1001,6 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
composeLayerRect(layer, rect, true);
}
- if (fboLayer) {
- // Note: No need to use glDiscardFramebufferEXT() since we never
- // create/compose layers that are not on screen with this
- // code path
- // See LayerRenderer::destroyLayer(Layer*)
-
- // Put the FBO name back in the cache, if it doesn't fit, it will be destroyed
- mCaches.fboCache.put(current->fbo);
- layer->setFbo(0);
- }
-
dirtyClip();
// Failing to add the layer to the cache should happen only if the layer is too large
@@ -896,7 +1011,7 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
}
void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) {
- float alpha = layer->getAlpha() / 255.0f;
+ float alpha = layer->getAlpha() / 255.0f * mSnapshot->alpha;
setupDraw();
if (layer->getRenderTarget() == GL_TEXTURE_2D) {
@@ -916,11 +1031,11 @@ void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) {
} else {
setupDrawExternalTexture(layer->getTexture());
}
- if (mSnapshot->transform->isPureTranslate() &&
+ if (currentTransform().isPureTranslate() &&
layer->getWidth() == (uint32_t) rect.getWidth() &&
layer->getHeight() == (uint32_t) rect.getHeight()) {
- const float x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
- const float y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
+ const float x = (int) floorf(rect.left + currentTransform().getTranslateX() + 0.5f);
+ const float y = (int) floorf(rect.top + currentTransform().getTranslateY() + 0.5f);
layer->setFilter(GL_NEAREST);
setupDrawModelView(x, y, x + rect.getWidth(), y + rect.getHeight(), true);
@@ -944,15 +1059,15 @@ void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap)
float x = rect.left;
float y = rect.top;
- bool simpleTransform = mSnapshot->transform->isPureTranslate() &&
+ bool simpleTransform = currentTransform().isPureTranslate() &&
layer->getWidth() == (uint32_t) rect.getWidth() &&
layer->getHeight() == (uint32_t) rect.getHeight();
if (simpleTransform) {
// When we're swapping, the layer is already in screen coordinates
if (!swap) {
- x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
- y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
+ x = (int) floorf(rect.left + currentTransform().getTranslateX() + 0.5f);
+ y = (int) floorf(rect.top + currentTransform().getTranslateY() + 0.5f);
}
layer->setFilter(GL_NEAREST, true);
@@ -960,9 +1075,10 @@ void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap)
layer->setFilter(GL_LINEAR, true);
}
+ float alpha = getLayerAlpha(layer);
+ bool blend = layer->isBlend() || alpha < 1.0f;
drawTextureMesh(x, y, x + rect.getWidth(), y + rect.getHeight(),
- layer->getTexture(), layer->getAlpha() / 255.0f,
- layer->getMode(), layer->isBlend(),
+ layer->getTexture(), alpha, layer->getMode(), blend,
&mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
GL_TRIANGLE_STRIP, gMeshCount, swap, swap || simpleTransform);
@@ -988,17 +1104,28 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) {
// information about this implementation
if (CC_LIKELY(!layer->region.isEmpty())) {
size_t count;
- const android::Rect* rects = layer->region.getArray(&count);
+ const android::Rect* rects;
+ Region safeRegion;
+ if (CC_LIKELY(hasRectToRectTransform())) {
+ rects = layer->region.getArray(&count);
+ } else {
+ safeRegion = Region::createTJunctionFreeRegion(layer->region);
+ rects = safeRegion.getArray(&count);
+ }
- const float alpha = layer->getAlpha() / 255.0f;
+ const float alpha = getLayerAlpha(layer);
const float texX = 1.0f / float(layer->getWidth());
const float texY = 1.0f / float(layer->getHeight());
const float height = rect.getHeight();
+ setupDraw();
+
+ // We must get (and therefore bind) the region mesh buffer
+ // after we setup drawing in case we need to mess with the
+ // stencil buffer in setupDraw()
TextureVertex* mesh = mCaches.getRegionMesh();
GLsizei numQuads = 0;
- setupDraw();
setupDrawWithTexture();
setupDrawColor(alpha, alpha, alpha, alpha);
setupDrawColorFilter();
@@ -1008,9 +1135,9 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) {
setupDrawPureColorUniforms();
setupDrawColorFilterUniforms();
setupDrawTexture(layer->getTexture());
- if (mSnapshot->transform->isPureTranslate()) {
- const float x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
- const float y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
+ if (currentTransform().isPureTranslate()) {
+ const float x = (int) floorf(rect.left + currentTransform().getTranslateX() + 0.5f);
+ const float y = (int) floorf(rect.top + currentTransform().getTranslateY() + 0.5f);
layer->setFilter(GL_NEAREST);
setupDrawModelViewTranslate(x, y, x + rect.getWidth(), y + rect.getHeight(), true);
@@ -1083,6 +1210,25 @@ void OpenGLRenderer::drawRegionRects(const Region& region) {
#endif
}
+void OpenGLRenderer::drawRegionRects(const SkRegion& region, int color,
+ SkXfermode::Mode mode, bool dirty) {
+ 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();
+ }
+
+ drawColorRects(rects.array(), count, color, mode, true, dirty, false);
+}
+
void OpenGLRenderer::dirtyLayer(const float left, const float top,
const float right, const float bottom, const mat4 transform) {
if (hasLayer()) {
@@ -1139,6 +1285,10 @@ void OpenGLRenderer::clearLayerRegions() {
delete bounds;
}
+ // We must clear the list of dirty rects before we
+ // call setupDraw() to prevent stencil setup to do
+ // the same thing again
+ mLayers.clear();
setupDraw(false);
setupDrawColor(0.0f, 0.0f, 0.0f, 1.0f);
@@ -1155,9 +1305,54 @@ void OpenGLRenderer::clearLayerRegions() {
for (uint32_t i = 0; i < count; i++) {
delete mLayers.itemAt(i);
}
+ mLayers.clear();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// State Deferral
+///////////////////////////////////////////////////////////////////////////////
+
+bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) {
+ const Rect& currentClip = *(mSnapshot->clipRect);
+ const mat4& currentMatrix = *(mSnapshot->transform);
+
+ if (stateDeferFlags & kStateDeferFlag_Draw) {
+ // state has bounds initialized in local coordinates
+ if (!state.mBounds.isEmpty()) {
+ currentMatrix.mapRect(state.mBounds);
+ if (!state.mBounds.intersect(currentClip)) {
+ // quick rejected
+ return true;
+ }
+ } else {
+ state.mBounds.set(currentClip);
+ }
}
- mLayers.clear();
+ if (stateDeferFlags & kStateDeferFlag_Clip) {
+ state.mClip.set(currentClip);
+ } else {
+ state.mClip.setEmpty();
+ }
+
+ // Transform, drawModifiers, 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.mDrawModifiers = mDrawModifiers;
+ state.mAlpha = mSnapshot->alpha;
+ return false;
+}
+
+void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state) {
+ currentTransform().load(state.mMatrix);
+ mDrawModifiers = state.mDrawModifiers;
+ mSnapshot->alpha = state.mAlpha;
+
+ if (!state.mClip.isEmpty()) {
+ mSnapshot->setClip(state.mClip.left, state.mClip.top, state.mClip.right, state.mClip.bottom);
+ dirtyClip();
+ }
}
///////////////////////////////////////////////////////////////////////////////
@@ -1165,38 +1360,42 @@ void OpenGLRenderer::clearLayerRegions() {
///////////////////////////////////////////////////////////////////////////////
void OpenGLRenderer::translate(float dx, float dy) {
- mSnapshot->transform->translate(dx, dy, 0.0f);
+ currentTransform().translate(dx, dy, 0.0f);
}
void OpenGLRenderer::rotate(float degrees) {
- mSnapshot->transform->rotate(degrees, 0.0f, 0.0f, 1.0f);
+ currentTransform().rotate(degrees, 0.0f, 0.0f, 1.0f);
}
void OpenGLRenderer::scale(float sx, float sy) {
- mSnapshot->transform->scale(sx, sy, 1.0f);
+ currentTransform().scale(sx, sy, 1.0f);
}
void OpenGLRenderer::skew(float sx, float sy) {
- mSnapshot->transform->skew(sx, sy);
+ currentTransform().skew(sx, sy);
}
void OpenGLRenderer::setMatrix(SkMatrix* matrix) {
if (matrix) {
- mSnapshot->transform->load(*matrix);
+ currentTransform().load(*matrix);
} else {
- mSnapshot->transform->loadIdentity();
+ currentTransform().loadIdentity();
}
}
+bool OpenGLRenderer::hasRectToRectTransform() {
+ return CC_LIKELY(currentTransform().rectToRect());
+}
+
void OpenGLRenderer::getMatrix(SkMatrix* matrix) {
- mSnapshot->transform->copyTo(*matrix);
+ currentTransform().copyTo(*matrix);
}
void OpenGLRenderer::concatMatrix(SkMatrix* matrix) {
SkMatrix transform;
- mSnapshot->transform->copyTo(transform);
+ currentTransform().copyTo(transform);
transform.preConcat(*matrix);
- mSnapshot->transform->load(transform);
+ currentTransform().load(transform);
}
///////////////////////////////////////////////////////////////////////////////
@@ -1213,6 +1412,73 @@ void OpenGLRenderer::setScissorFromClip() {
}
}
+void OpenGLRenderer::ensureStencilBuffer() {
+ // Thanks to the mismatch between EGL and OpenGL ES FBO we
+ // cannot attach a stencil buffer to fbo0 dynamically. Let's
+ // just hope we have one when hasLayer() returns false.
+ if (hasLayer()) {
+ attachStencilBufferToLayer(mSnapshot->layer);
+ }
+}
+
+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::getSmallestStencilFormat(), layer->getWidth(), layer->getHeight());
+ layer->setStencilRenderBuffer(buffer);
+
+ startTiling(layer->clipRect, layer->layer.getHeight());
+ }
+}
+
+void OpenGLRenderer::setStencilFromClip() {
+ if (!mCaches.debugOverdraw) {
+ if (!mSnapshot->clipRegion->isEmpty()) {
+ // NOTE: The order here is important, we must set dirtyClip to false
+ // before any draw call to avoid calling back into this method
+ mDirtyClip = false;
+
+ ensureStencilBuffer();
+
+ mCaches.stencil.enableWrite();
+
+ // Clear the stencil but first make sure we restrict drawing
+ // to the region's bounds
+ bool resetScissor = mCaches.enableScissor();
+ if (resetScissor) {
+ // The scissor was not set so we now need to update it
+ setScissorFromClip();
+ }
+ mCaches.stencil.clear();
+ if (resetScissor) mCaches.disableScissor();
+
+ // NOTE: We could use the region contour path to generate a smaller mesh
+ // Since we are using the stencil we could use the red book path
+ // drawing technique. It might increase bandwidth usage though.
+
+ // The last parameter is important: we are not drawing in the color buffer
+ // so we don't want to dirty the current layer, if any
+ drawRegionRects(*mSnapshot->clipRegion, 0xff000000, SkXfermode::kSrc_Mode, false);
+
+ mCaches.stencil.enableTest();
+
+ // Draw the region used to generate the stencil if the appropriate debug
+ // mode is enabled
+ if (mCaches.debugStencilClip == Caches::kStencilShowRegion) {
+ drawRegionRects(*mSnapshot->clipRegion, 0x7f0000ff, SkXfermode::kSrcOver_Mode);
+ }
+ } else {
+ mCaches.stencil.disable();
+ }
+ }
+}
+
const Rect& OpenGLRenderer::getClipBounds() {
return mSnapshot->getLocalClip();
}
@@ -1223,7 +1489,7 @@ bool OpenGLRenderer::quickRejectNoScissor(float left, float top, float right, fl
}
Rect r(left, top, right, bottom);
- mSnapshot->transform->mapRect(r);
+ currentTransform().mapRect(r);
r.snapToPixelBoundaries();
Rect clipRect(*mSnapshot->clipRect);
@@ -1239,7 +1505,7 @@ bool OpenGLRenderer::quickRejectNoScissor(float left, float top, float right, fl
}
transformed.set(left, top, right, bottom);
- mSnapshot->transform->mapRect(transformed);
+ currentTransform().mapRect(transformed);
transformed.snapToPixelBoundaries();
clip.set(*mSnapshot->clipRect);
@@ -1248,7 +1514,8 @@ bool OpenGLRenderer::quickRejectNoScissor(float left, float top, float right, fl
return !clip.intersects(transformed);
}
-bool OpenGLRenderer::quickRejectPreStroke(float left, float top, float right, float bottom, SkPaint* paint) {
+bool OpenGLRenderer::quickRejectPreStroke(float left, float top, float right, float bottom,
+ SkPaint* paint) {
if (paint->getStyle() != SkPaint::kFill_Style) {
float outset = paint->getStrokeWidth() * 0.5f;
return quickReject(left - outset, top - outset, right + outset, bottom + outset);
@@ -1263,7 +1530,7 @@ bool OpenGLRenderer::quickReject(float left, float top, float right, float botto
}
Rect r(left, top, right, bottom);
- mSnapshot->transform->mapRect(r);
+ currentTransform().mapRect(r);
r.snapToPixelBoundaries();
Rect clipRect(*mSnapshot->clipRect);
@@ -1277,8 +1544,56 @@ bool OpenGLRenderer::quickReject(float left, float top, float right, float botto
return rejected;
}
+void OpenGLRenderer::debugClip() {
+#if DEBUG_CLIP_REGIONS
+ if (!isDeferred() && !mSnapshot->clipRegion->isEmpty()) {
+ drawRegionRects(*mSnapshot->clipRegion, 0x7f00ff00, SkXfermode::kSrcOver_Mode);
+ }
+#endif
+}
+
bool OpenGLRenderer::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
- bool clipped = mSnapshot->clip(left, top, right, bottom, op);
+ if (CC_LIKELY(currentTransform().rectToRect())) {
+ bool clipped = mSnapshot->clip(left, top, right, bottom, op);
+ if (clipped) {
+ dirtyClip();
+ }
+ return !mSnapshot->clipRect->isEmpty();
+ }
+
+ SkPath path;
+ path.addRect(left, top, right, bottom);
+
+ return clipPath(&path, op);
+}
+
+bool OpenGLRenderer::clipPath(SkPath* path, SkRegion::Op op) {
+ SkMatrix transform;
+ currentTransform().copyTo(transform);
+
+ SkPath transformed;
+ path->transform(transform, &transformed);
+
+ SkRegion clip;
+ if (!mSnapshot->clipRegion->isEmpty()) {
+ clip.setRegion(*mSnapshot->clipRegion);
+ } else {
+ Rect* bounds = mSnapshot->clipRect;
+ clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom);
+ }
+
+ SkRegion region;
+ region.setPath(transformed, clip);
+
+ bool clipped = mSnapshot->clipRegionTransformed(region, op);
+ if (clipped) {
+ dirtyClip();
+ }
+ return !mSnapshot->clipRect->isEmpty();
+}
+
+bool OpenGLRenderer::clipRegion(SkRegion* region, SkRegion::Op op) {
+ bool clipped = mSnapshot->clipRegionTransformed(*region, op);
if (clipped) {
dirtyClip();
}
@@ -1297,15 +1612,28 @@ void OpenGLRenderer::setupDraw(bool clear) {
// TODO: It would be best if we could do this before quickReject()
// changes the scissor test state
if (clear) clearLayerRegions();
+ // Make sure setScissor & setStencil happen at the beginning of
+ // this method
if (mDirtyClip) {
- setScissorFromClip();
+ if (mCaches.scissorEnabled) {
+ setScissorFromClip();
+ }
+ setStencilFromClip();
}
+
mDescription.reset();
+
mSetShaderColor = false;
mColorSet = false;
mColorA = mColorR = mColorG = mColorB = 0.0f;
mTextureUnit = 0;
mTrackDirtyRegions = true;
+
+ // Enable debug highlight when what we're about to draw is tested against
+ // the stencil buffer and if stencil highlight debugging is on
+ mDescription.hasDebugHighlight = !mCaches.debugOverdraw &&
+ mCaches.debugStencilClip == Caches::kStencilShowHighlight &&
+ mCaches.stencil.isTestEnabled();
}
void OpenGLRenderer::setupDrawWithTexture(bool isAlpha8) {
@@ -1313,51 +1641,43 @@ void OpenGLRenderer::setupDrawWithTexture(bool isAlpha8) {
mDescription.hasAlpha8Texture = isAlpha8;
}
+void OpenGLRenderer::setupDrawWithTextureAndColor(bool isAlpha8) {
+ mDescription.hasTexture = true;
+ mDescription.hasColors = true;
+ mDescription.hasAlpha8Texture = isAlpha8;
+}
+
void OpenGLRenderer::setupDrawWithExternalTexture() {
mDescription.hasExternalTexture = true;
}
void OpenGLRenderer::setupDrawNoTexture() {
- mCaches.disbaleTexCoordsVertexArray();
+ mCaches.disableTexCoordsVertexArray();
}
void OpenGLRenderer::setupDrawAA() {
mDescription.isAA = true;
}
-void OpenGLRenderer::setupDrawVertexShape() {
- mDescription.isVertexShape = true;
-}
-
void OpenGLRenderer::setupDrawPoint(float pointSize) {
mDescription.isPoint = true;
mDescription.pointSize = pointSize;
}
-void OpenGLRenderer::setupDrawColor(int color) {
- setupDrawColor(color, (color >> 24) & 0xFF);
-}
-
void OpenGLRenderer::setupDrawColor(int color, int alpha) {
mColorA = alpha / 255.0f;
- // Second divide of a by 255 is an optimization, allowing us to simply multiply
- // the rgb values by a instead of also dividing by 255
- const float a = mColorA / 255.0f;
- mColorR = a * ((color >> 16) & 0xFF);
- mColorG = a * ((color >> 8) & 0xFF);
- mColorB = a * ((color ) & 0xFF);
+ mColorR = mColorA * ((color >> 16) & 0xFF) / 255.0f;
+ mColorG = mColorA * ((color >> 8) & 0xFF) / 255.0f;
+ mColorB = mColorA * ((color ) & 0xFF) / 255.0f;
mColorSet = true;
mSetShaderColor = mDescription.setColor(mColorR, mColorG, mColorB, mColorA);
}
void OpenGLRenderer::setupDrawAlpha8Color(int color, int alpha) {
mColorA = alpha / 255.0f;
- // Double-divide of a by 255 is an optimization, allowing us to simply multiply
- // the rgb values by a instead of also dividing by 255
- const float a = mColorA / 255.0f;
- mColorR = a * ((color >> 16) & 0xFF);
- mColorG = a * ((color >> 8) & 0xFF);
- mColorB = a * ((color ) & 0xFF);
+ mColorR = mColorA * ((color >> 16) & 0xFF) / 255.0f;
+ mColorG = mColorA * ((color >> 8) & 0xFF) / 255.0f;
+ mColorB = mColorA * ((color ) & 0xFF) / 255.0f;
mColorSet = true;
mSetShaderColor = mDescription.setAlpha8Color(mColorR, mColorG, mColorB, mColorA);
}
@@ -1376,14 +1696,14 @@ void OpenGLRenderer::setupDrawColor(float r, float g, float b, float a) {
}
void OpenGLRenderer::setupDrawShader() {
- if (mShader) {
- mShader->describe(mDescription, mCaches.extensions);
+ if (mDrawModifiers.mShader) {
+ mDrawModifiers.mShader->describe(mDescription, mExtensions);
}
}
void OpenGLRenderer::setupDrawColorFilter() {
- if (mColorFilter) {
- mColorFilter->describe(mDescription, mCaches.extensions);
+ if (mDrawModifiers.mColorFilter) {
+ mDrawModifiers.mColorFilter->describe(mDescription, mExtensions);
}
}
@@ -1399,16 +1719,19 @@ void OpenGLRenderer::setupDrawBlending(SkXfermode::Mode mode, bool swapSrcDst) {
// When the blending mode is kClear_Mode, we need to use a modulate color
// argb=1,0,0,0
accountForClear(mode);
- chooseBlending((mColorSet && mColorA < 1.0f) || (mShader && mShader->blend()), mode,
- mDescription, swapSrcDst);
+ bool blend = (mColorSet && mColorA < 1.0f) ||
+ (mDrawModifiers.mShader && mDrawModifiers.mShader->blend());
+ chooseBlending(blend, mode, mDescription, swapSrcDst);
}
void OpenGLRenderer::setupDrawBlending(bool blend, SkXfermode::Mode mode, bool swapSrcDst) {
// When the blending mode is kClear_Mode, we need to use a modulate color
// argb=1,0,0,0
accountForClear(mode);
- chooseBlending(blend || (mColorSet && mColorA < 1.0f) || (mShader && mShader->blend()) ||
- (mColorFilter && mColorFilter->blend()), mode, mDescription, swapSrcDst);
+ blend |= (mColorSet && mColorA < 1.0f) ||
+ (mDrawModifiers.mShader && mDrawModifiers.mShader->blend()) ||
+ (mDrawModifiers.mColorFilter && mDrawModifiers.mColorFilter->blend());
+ chooseBlending(blend, mode, mDescription, swapSrcDst);
}
void OpenGLRenderer::setupDrawProgram() {
@@ -1423,16 +1746,16 @@ void OpenGLRenderer::setupDrawModelViewTranslate(float left, float top, float ri
bool ignoreTransform) {
mModelView.loadTranslate(left, top, 0.0f);
if (!ignoreTransform) {
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
- if (mTrackDirtyRegions) dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, currentTransform());
+ if (mTrackDirtyRegions) dirtyLayer(left, top, right, bottom, currentTransform());
} else {
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, mIdentity);
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, mat4::identity());
if (mTrackDirtyRegions) dirtyLayer(left, top, right, bottom);
}
}
void OpenGLRenderer::setupDrawModelViewIdentity(bool offset) {
- mCaches.currentProgram->set(mOrthoMatrix, mIdentity, *mSnapshot->transform, offset);
+ mCaches.currentProgram->set(mOrthoMatrix, mat4::identity(), currentTransform(), offset);
}
void OpenGLRenderer::setupDrawModelView(float left, float top, float right, float bottom,
@@ -1445,12 +1768,12 @@ void OpenGLRenderer::setupDrawModelView(float left, float top, float right, floa
}
bool dirty = right - left > 0.0f && bottom - top > 0.0f;
if (!ignoreTransform) {
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, currentTransform());
if (mTrackDirtyRegions && dirty) {
- dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
+ dirtyLayer(left, top, right, bottom, currentTransform());
}
} else {
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, mIdentity);
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, mat4::identity());
if (mTrackDirtyRegions && dirty) dirtyLayer(left, top, right, bottom);
}
}
@@ -1461,7 +1784,7 @@ void OpenGLRenderer::setupDrawPointUniforms() {
}
void OpenGLRenderer::setupDrawColorUniforms() {
- if ((mColorSet && !mShader) || (mShader && mSetShaderColor)) {
+ if ((mColorSet && !mDrawModifiers.mShader) || (mDrawModifiers.mShader && mSetShaderColor)) {
mCaches.currentProgram->setColor(mColorR, mColorG, mColorB, mColorA);
}
}
@@ -1473,23 +1796,25 @@ void OpenGLRenderer::setupDrawPureColorUniforms() {
}
void OpenGLRenderer::setupDrawShaderUniforms(bool ignoreTransform) {
- if (mShader) {
+ if (mDrawModifiers.mShader) {
if (ignoreTransform) {
- mModelView.loadInverse(*mSnapshot->transform);
+ mModelView.loadInverse(currentTransform());
}
- mShader->setupProgram(mCaches.currentProgram, mModelView, *mSnapshot, &mTextureUnit);
+ mDrawModifiers.mShader->setupProgram(mCaches.currentProgram,
+ mModelView, *mSnapshot, &mTextureUnit);
}
}
void OpenGLRenderer::setupDrawShaderIdentityUniforms() {
- if (mShader) {
- mShader->setupProgram(mCaches.currentProgram, mIdentity, *mSnapshot, &mTextureUnit);
+ if (mDrawModifiers.mShader) {
+ mDrawModifiers.mShader->setupProgram(mCaches.currentProgram,
+ mat4::identity(), *mSnapshot, &mTextureUnit);
}
}
void OpenGLRenderer::setupDrawColorFilterUniforms() {
- if (mColorFilter) {
- mColorFilter->setupProgram(mCaches.currentProgram);
+ if (mDrawModifiers.mColorFilter) {
+ mDrawModifiers.mColorFilter->setupProgram(mCaches.currentProgram);
}
}
@@ -1504,7 +1829,7 @@ void OpenGLRenderer::setupDrawSimpleMesh() {
}
void OpenGLRenderer::setupDrawTexture(GLuint texture) {
- bindTexture(texture);
+ if (texture) bindTexture(texture);
mTextureUnit++;
mCaches.enableTexCoordsVertexArray();
}
@@ -1540,6 +1865,23 @@ void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint v
mCaches.unbindIndicesBuffer();
}
+void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLvoid* colors) {
+ bool force = mCaches.unbindMeshBuffer();
+ GLsizei stride = sizeof(ColorTextureVertex);
+
+ mCaches.bindPositionVertexPointer(force, vertices, stride);
+ if (mCaches.currentProgram->texCoords >= 0) {
+ mCaches.bindTexCoordsVertexPointer(force, texCoords, stride);
+ }
+ int slot = mCaches.currentProgram->getAttrib("colors");
+ if (slot >= 0) {
+ glEnableVertexAttribArray(slot);
+ glVertexAttribPointer(slot, 4, GL_FLOAT, GL_FALSE, stride, colors);
+ }
+
+ mCaches.unbindIndicesBuffer();
+}
+
void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords) {
bool force = mCaches.unbindMeshBuffer();
mCaches.bindPositionVertexPointer(force, vertices);
@@ -1554,41 +1896,6 @@ void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) {
mCaches.unbindIndicesBuffer();
}
-/**
- * Sets up the shader to draw an AA line. We draw AA lines with quads, where there is an
- * outer boundary that fades out to 0. The variables set in the shader define the proportion of
- * the width and length of the primitive occupied by the AA region. The vtxWidth and vtxLength
- * attributes (one per vertex) are values from zero to one that tells the fragment
- * shader where the fragment is in relation to the line width/length overall; these values are
- * then used to compute the proper color, based on whether the fragment lies in the fading AA
- * region of the line.
- * Note that we only pass down the width values in this setup function. The length coordinates
- * are set up for each individual segment.
- */
-void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* widthCoords,
- GLvoid* lengthCoords, float boundaryWidthProportion, int& widthSlot, int& lengthSlot) {
- bool force = mCaches.unbindMeshBuffer();
- mCaches.bindPositionVertexPointer(force, vertices, gAAVertexStride);
- mCaches.resetTexCoordsVertexPointer();
- mCaches.unbindIndicesBuffer();
-
- widthSlot = mCaches.currentProgram->getAttrib("vtxWidth");
- glEnableVertexAttribArray(widthSlot);
- glVertexAttribPointer(widthSlot, 1, GL_FLOAT, GL_FALSE, gAAVertexStride, widthCoords);
-
- lengthSlot = mCaches.currentProgram->getAttrib("vtxLength");
- glEnableVertexAttribArray(lengthSlot);
- glVertexAttribPointer(lengthSlot, 1, GL_FLOAT, GL_FALSE, gAAVertexStride, lengthCoords);
-
- int boundaryWidthSlot = mCaches.currentProgram->getUniform("boundaryWidth");
- glUniform1f(boundaryWidthSlot, boundaryWidthProportion);
-}
-
-void OpenGLRenderer::finishDrawAALine(const int widthSlot, const int lengthSlot) {
- glDisableVertexAttribArray(widthSlot);
- glDisableVertexAttribArray(lengthSlot);
-}
-
void OpenGLRenderer::finishDrawTexture() {
}
@@ -1596,21 +1903,35 @@ void OpenGLRenderer::finishDrawTexture() {
// Drawing
///////////////////////////////////////////////////////////////////////////////
-status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList,
- Rect& dirty, int32_t flags, uint32_t level) {
-
+status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList, Rect& dirty,
+ int32_t replayFlags) {
+ status_t status;
// All the usual checks and setup operations (quickReject, setupDraw, etc.)
// will be performed by the display list itself
if (displayList && displayList->isRenderable()) {
- return displayList->replay(*this, dirty, flags, level);
+ if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
+ status = startFrame();
+ ReplayStateStruct replayStruct(*this, dirty, replayFlags);
+ displayList->replay(replayStruct, 0);
+ return status | replayStruct.mDrawGlStatus;
+ }
+
+ DeferredDisplayList deferredList;
+ DeferStateStruct deferStruct(deferredList, *this, replayFlags);
+ displayList->defer(deferStruct, 0);
+
+ flushLayers();
+ status = startFrame();
+
+ return status | deferredList.flush(*this, dirty);
}
return DrawGlInfo::kStatusDone;
}
-void OpenGLRenderer::outputDisplayList(DisplayList* displayList, uint32_t level) {
+void OpenGLRenderer::outputDisplayList(DisplayList* displayList) {
if (displayList) {
- displayList->output(*this, level);
+ displayList->output(1);
}
}
@@ -1619,43 +1940,27 @@ void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, Sk
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
+ int color = paint != NULL ? paint->getColor() : 0;
+
float x = left;
float y = top;
- GLenum filter = GL_LINEAR;
+ texture->setWrap(GL_CLAMP_TO_EDGE, true);
+
bool ignoreTransform = false;
- if (mSnapshot->transform->isPureTranslate()) {
- x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f);
- y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f);
+ if (currentTransform().isPureTranslate()) {
+ x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f);
+ y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f);
ignoreTransform = true;
- filter = GL_NEAREST;
- } else {
- filter = FILTER(paint);
- }
- setupDraw();
- setupDrawWithTexture(true);
- if (paint) {
- setupDrawAlpha8Color(paint->getColor(), alpha);
+ texture->setFilter(GL_NEAREST, true);
+ } else {
+ texture->setFilter(FILTER(paint), true);
}
- setupDrawColorFilter();
- setupDrawShader();
- setupDrawBlending(true, mode);
- setupDrawProgram();
- setupDrawModelView(x, y, x + texture->width, y + texture->height, ignoreTransform);
-
- setupDrawTexture(texture->id);
- texture->setWrap(GL_CLAMP_TO_EDGE);
- texture->setFilter(filter);
-
- setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawShaderUniforms();
- setupDrawMesh(NULL, (GLvoid*) gMeshTextureOffset);
- glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
-
- finishDrawTexture();
+ drawAlpha8TextureMesh(x, y, x + texture->width, y + texture->height, texture->id,
+ paint != NULL, color, alpha, mode, (GLvoid*) NULL,
+ (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform);
}
status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint) {
@@ -1698,7 +2003,11 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint*
// to the vertex shader. The save/restore is a bit overkill.
save(SkCanvas::kMatrix_SaveFlag);
concatMatrix(matrix);
- drawTextureRect(0.0f, 0.0f, bitmap->width(), bitmap->height(), texture, paint);
+ if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) {
+ drawAlphaBitmap(texture, 0.0f, 0.0f, paint);
+ } else {
+ drawTextureRect(0.0f, 0.0f, bitmap->width(), bitmap->height(), texture, paint);
+ }
restore();
return DrawGlInfo::kStatusDrew;
@@ -1716,7 +2025,11 @@ status_t OpenGLRenderer::drawBitmapData(SkBitmap* bitmap, float left, float top,
Texture* texture = mCaches.textureCache.getTransient(bitmap);
const AutoTexture autoCleanup(texture);
- drawTextureRect(left, top, right, bottom, texture, paint);
+ if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) {
+ drawAlphaBitmap(texture, left, top, paint);
+ } else {
+ drawTextureRect(left, top, right, bottom, texture, paint);
+ }
return DrawGlInfo::kStatusDrew;
}
@@ -1727,7 +2040,6 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes
return DrawGlInfo::kStatusDone;
}
- // TODO: We should compute the bounding box when recording the display list
float left = FLT_MAX;
float top = FLT_MAX;
float right = FLT_MIN;
@@ -1735,9 +2047,16 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes
const uint32_t count = meshWidth * meshHeight * 6;
- // TODO: Support the colors array
- TextureVertex mesh[count];
- TextureVertex* vertex = mesh;
+ ColorTextureVertex mesh[count];
+ ColorTextureVertex* vertex = mesh;
+
+ bool cleanupColors = false;
+ if (!colors) {
+ uint32_t colorsCount = (meshWidth + 1) * (meshHeight + 1);
+ colors = new int[colorsCount];
+ memset(colors, 0xff, colorsCount * sizeof(int));
+ cleanupColors = true;
+ }
for (int32_t y = 0; y < meshHeight; y++) {
for (int32_t x = 0; x < meshWidth; x++) {
@@ -1757,15 +2076,14 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes
int dx = i + (meshWidth + 1) * 2 + 2;
int dy = dx + 1;
- TextureVertex::set(vertex++, vertices[ax], vertices[ay], u1, v2);
- TextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1);
- TextureVertex::set(vertex++, vertices[cx], vertices[cy], u2, v1);
+ 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]);
- TextureVertex::set(vertex++, vertices[ax], vertices[ay], u1, v2);
- TextureVertex::set(vertex++, vertices[cx], vertices[cy], u2, v1);
- TextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2);
+ 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]);
- // TODO: This could be optimized to avoid unnecessary ops
left = fminf(left, fminf(vertices[ax], fminf(vertices[bx], vertices[cx])));
top = fminf(top, fminf(vertices[ay], fminf(vertices[by], vertices[cy])));
right = fmaxf(right, fmaxf(vertices[ax], fmaxf(vertices[bx], vertices[cx])));
@@ -1774,12 +2092,16 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes
}
if (quickReject(left, top, right, bottom)) {
+ if (cleanupColors) delete[] colors;
return DrawGlInfo::kStatusDone;
}
mCaches.activeTexture(0);
Texture* texture = mCaches.textureCache.get(bitmap);
- if (!texture) return DrawGlInfo::kStatusDone;
+ if (!texture) {
+ if (cleanupColors) delete[] colors;
+ return DrawGlInfo::kStatusDone;
+ }
const AutoTexture autoCleanup(texture);
texture->setWrap(GL_CLAMP_TO_EDGE, true);
@@ -1789,13 +2111,35 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
+ float a = alpha / 255.0f;
+
if (hasLayer()) {
- dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
+ dirtyLayer(left, top, right, bottom, currentTransform());
+ }
+
+ setupDraw();
+ setupDrawWithTextureAndColor();
+ setupDrawColor(a, a, a, a);
+ setupDrawColorFilter();
+ setupDrawBlending(true, mode, false);
+ setupDrawProgram();
+ setupDrawDirtyRegionsDisabled();
+ setupDrawModelView(0.0f, 0.0f, 1.0f, 1.0f, false);
+ setupDrawTexture(texture->id);
+ setupDrawPureColorUniforms();
+ setupDrawColorFilterUniforms();
+ setupDrawMesh(&mesh[0].position[0], &mesh[0].texture[0], &mesh[0].color[0]);
+
+ glDrawArrays(GL_TRIANGLES, 0, count);
+
+ finishDrawTexture();
+
+ int slot = mCaches.currentProgram->getAttrib("colors");
+ if (slot >= 0) {
+ glDisableVertexAttribArray(slot);
}
- drawTextureMesh(0.0f, 0.0f, 1.0f, 1.0f, texture->id, alpha / 255.0f,
- mode, texture->blend, &mesh[0].position[0], &mesh[0].texture[0],
- GL_TRIANGLES, count, false, false, 0, false, false);
+ if (cleanupColors) delete[] colors;
return DrawGlInfo::kStatusDrew;
}
@@ -1830,26 +2174,59 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap,
texture->setWrap(GL_CLAMP_TO_EDGE, true);
- if (CC_LIKELY(mSnapshot->transform->isPureTranslate())) {
- const float x = (int) floorf(dstLeft + mSnapshot->transform->getTranslateX() + 0.5f);
- const float y = (int) floorf(dstTop + mSnapshot->transform->getTranslateY() + 0.5f);
+ float scaleX = (dstRight - dstLeft) / (srcRight - srcLeft);
+ float scaleY = (dstBottom - dstTop) / (srcBottom - srcTop);
- GLenum filter = GL_NEAREST;
- // Enable linear filtering if the source rectangle is scaled
- if (srcRight - srcLeft != dstRight - dstLeft || srcBottom - srcTop != dstBottom - dstTop) {
- filter = FILTER(paint);
- }
+ bool scaled = scaleX != 1.0f || scaleY != 1.0f;
+ // Apply a scale transform on the canvas only when a shader is in use
+ // Skia handles the ratio between the dst and src rects as a scale factor
+ // when a shader is set
+ bool useScaleTransform = mDrawModifiers.mShader && scaled;
+ bool ignoreTransform = false;
- texture->setFilter(filter, true);
- drawTextureMesh(x, y, x + (dstRight - dstLeft), y + (dstBottom - dstTop),
- texture->id, alpha / 255.0f, mode, texture->blend,
- &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
- GL_TRIANGLE_STRIP, gMeshCount, false, true);
+ if (CC_LIKELY(currentTransform().isPureTranslate() && !useScaleTransform)) {
+ float x = (int) floorf(dstLeft + currentTransform().getTranslateX() + 0.5f);
+ float y = (int) floorf(dstTop + currentTransform().getTranslateY() + 0.5f);
+
+ dstRight = x + (dstRight - dstLeft);
+ dstBottom = y + (dstBottom - dstTop);
+
+ dstLeft = x;
+ dstTop = y;
+
+ texture->setFilter(scaled ? FILTER(paint) : GL_NEAREST, true);
+ ignoreTransform = true;
} else {
texture->setFilter(FILTER(paint), true);
- drawTextureMesh(dstLeft, dstTop, dstRight, dstBottom, texture->id, alpha / 255.0f,
- mode, texture->blend, &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
- GL_TRIANGLE_STRIP, gMeshCount);
+ }
+
+ if (CC_UNLIKELY(useScaleTransform)) {
+ save(SkCanvas::kMatrix_SaveFlag);
+ translate(dstLeft, dstTop);
+ scale(scaleX, scaleY);
+
+ dstLeft = 0.0f;
+ dstTop = 0.0f;
+
+ dstRight = srcRight - srcLeft;
+ dstBottom = srcBottom - srcTop;
+ }
+
+ if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) {
+ int color = paint ? paint->getColor() : 0;
+ drawAlpha8TextureMesh(dstLeft, dstTop, dstRight, dstBottom,
+ texture->id, paint != NULL, color, alpha, mode,
+ &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
+ GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform);
+ } else {
+ drawTextureMesh(dstLeft, dstTop, dstRight, dstBottom,
+ texture->id, alpha / 255.0f, mode, texture->blend,
+ &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
+ GL_TRIANGLE_STRIP, gMeshCount, false, ignoreTransform);
+ }
+
+ if (CC_UNLIKELY(useScaleTransform)) {
+ restore();
}
resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
@@ -1862,7 +2239,7 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const
float left, float top, float right, float bottom, SkPaint* paint) {
int alpha;
SkXfermode::Mode mode;
- getAlphaAndModeDirect(paint, &alpha, &mode);
+ getAlphaAndMode(paint, &alpha, &mode);
return drawPatch(bitmap, xDivs, yDivs, colors, width, height, numColors,
left, top, right, bottom, alpha, mode);
@@ -1877,22 +2254,22 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const
alpha *= mSnapshot->alpha;
- mCaches.activeTexture(0);
- Texture* texture = mCaches.textureCache.get(bitmap);
- if (!texture) return DrawGlInfo::kStatusDone;
- const AutoTexture autoCleanup(texture);
- texture->setWrap(GL_CLAMP_TO_EDGE, true);
- texture->setFilter(GL_LINEAR, true);
-
const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(),
right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors);
if (CC_LIKELY(mesh && mesh->verticesCount > 0)) {
- const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+ mCaches.activeTexture(0);
+ Texture* texture = mCaches.textureCache.get(bitmap);
+ if (!texture) return DrawGlInfo::kStatusDone;
+ const AutoTexture autoCleanup(texture);
+ texture->setWrap(GL_CLAMP_TO_EDGE, true);
+ texture->setFilter(GL_LINEAR, true);
+
+ const bool pureTranslate = currentTransform().isPureTranslate();
// Mark the current layer dirty where we are going to draw the patch
if (hasLayer() && mesh->hasEmptyQuads) {
- const float offsetX = left + mSnapshot->transform->getTranslateX();
- const float offsetY = top + mSnapshot->transform->getTranslateY();
+ const float offsetX = left + currentTransform().getTranslateX();
+ const float offsetY = top + currentTransform().getTranslateY();
const size_t count = mesh->quads.size();
for (size_t i = 0; i < count; i++) {
const Rect& bounds = mesh->quads.itemAt(i);
@@ -1902,14 +2279,14 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const
dirtyLayer(x, y, x + bounds.getWidth(), y + bounds.getHeight());
} else {
dirtyLayer(left + bounds.left, top + bounds.top,
- left + bounds.right, top + bounds.bottom, *mSnapshot->transform);
+ left + bounds.right, top + bounds.bottom, currentTransform());
}
}
}
if (CC_LIKELY(pureTranslate)) {
- const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f);
- const float y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f);
+ const float x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f);
+ const float y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f);
drawTextureMesh(x, y, x + right - left, y + bottom - top, texture->id, alpha / 255.0f,
mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset,
@@ -1926,40 +2303,26 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const
return DrawGlInfo::kStatusDrew;
}
-/**
- * Renders a convex path via tessellation. For AA paths, this function uses a similar approach to
- * that of AA lines in the drawLines() function. We expand the convex path by a half pixel in
- * screen space in all directions. However, instead of using a fragment shader to compute the
- * translucency of the color from its position, we simply use a varying parameter to define how far
- * a given pixel is from the edge. For non-AA paths, the expansion and alpha varying are not used.
- *
- * Doesn't yet support joins, caps, or path effects.
- */
-void OpenGLRenderer::drawConvexPath(const SkPath& path, SkPaint* paint) {
- int color = paint->getColor();
- SkPaint::Style style = paint->getStyle();
- SkXfermode::Mode mode = getXfermode(paint->getXfermode());
- bool isAA = paint->isAntiAlias();
-
- VertexBuffer vertexBuffer;
- // TODO: try clipping large paths to viewport
- PathRenderer::convexPathVertices(path, paint, mSnapshot->transform, vertexBuffer);
-
+status_t OpenGLRenderer::drawVertexBuffer(const VertexBuffer& vertexBuffer, SkPaint* paint,
+ bool useOffset) {
if (!vertexBuffer.getSize()) {
// no vertices to draw
- return;
+ return DrawGlInfo::kStatusDone;
}
+ int color = paint->getColor();
+ SkXfermode::Mode mode = getXfermode(paint->getXfermode());
+ bool isAA = paint->isAntiAlias();
+
setupDraw();
setupDrawNoTexture();
if (isAA) setupDrawAA();
- setupDrawVertexShape();
setupDrawColor(color, ((color >> 24) & 0xFF) * mSnapshot->alpha);
setupDrawColorFilter();
setupDrawShader();
setupDrawBlending(isAA, mode);
setupDrawProgram();
- setupDrawModelViewIdentity();
+ setupDrawModelViewIdentity(useOffset);
setupDrawColorUniforms();
setupDrawColorFilterUniforms();
setupDrawShaderIdentityUniforms();
@@ -1980,286 +2343,66 @@ void OpenGLRenderer::drawConvexPath(const SkPath& path, SkPaint* paint) {
glVertexAttribPointer(alphaSlot, 1, GL_FLOAT, GL_FALSE, gAlphaVertexStride, alphaCoords);
}
- SkRect bounds = PathRenderer::computePathBounds(path, paint);
- dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, *mSnapshot->transform);
-
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getSize());
if (isAA) {
glDisableVertexAttribArray(alphaSlot);
}
+
+ return DrawGlInfo::kStatusDrew;
}
/**
- * We draw lines as quads (tristrips). Using GL_LINES can be difficult because the rasterization
- * rules for those lines produces some unexpected results, and may vary between hardware devices.
- * The basics of lines-as-quads is easy; we simply find the normal to the line and position the
- * corners of the quads on either side of each line endpoint, separated by the strokeWidth
- * of the line. Hairlines are more involved because we need to account for transform scaling
- * to end up with a one-pixel-wide line in screen space..
- * Anti-aliased lines add another factor to the approach. We use a specialized fragment shader
- * in combination with values that we calculate and pass down in this method. The basic approach
- * is that the quad we create contains both the core line area plus a bounding area in which
- * the translucent/AA pixels are drawn. The values we calculate tell the shader what
- * proportion of the width and the length of a given segment is represented by the boundary
- * region. The quad ends up being exactly .5 pixel larger in all directions than the non-AA quad.
- * The bounding region is actually 1 pixel wide on all sides (half pixel on the outside, half pixel
- * on the inside). This ends up giving the result we want, with pixels that are completely
- * 'inside' the line area being filled opaquely and the other pixels being filled according to
- * how far into the boundary region they are, which is determined by shader interpolation.
+ * Renders a convex path via tessellation. For AA paths, this function uses a similar approach to
+ * that of AA lines in the drawLines() function. We expand the convex path by a half pixel in
+ * screen space in all directions. However, instead of using a fragment shader to compute the
+ * translucency of the color from its position, we simply use a varying parameter to define how far
+ * a given pixel is from the edge. For non-AA paths, the expansion and alpha varying are not used.
+ *
+ * Doesn't yet support joins, caps, or path effects.
*/
-status_t OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
- if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;
-
- const bool isAA = paint->isAntiAlias();
- // We use half the stroke width here because we're going to position the quad
- // corner vertices half of the width away from the line endpoints
- float halfStrokeWidth = paint->getStrokeWidth() * 0.5f;
- // A stroke width of 0 has a special meaning in Skia:
- // it draws a line 1 px wide regardless of current transform
- bool isHairLine = paint->getStrokeWidth() == 0.0f;
-
- float inverseScaleX = 1.0f;
- float inverseScaleY = 1.0f;
- bool scaled = false;
-
- int alpha;
- SkXfermode::Mode mode;
-
- int generatedVerticesCount = 0;
- int verticesCount = count;
- if (count > 4) {
- // Polyline: account for extra vertices needed for continuous tri-strip
- verticesCount += (count - 4);
- }
-
- if (isHairLine || isAA) {
- // The quad that we use for AA and hairlines needs to account for scaling. For hairlines
- // the line on the screen should always be one pixel wide regardless of scale. For
- // AA lines, we only want one pixel of translucent boundary around the quad.
- if (CC_UNLIKELY(!mSnapshot->transform->isPureTranslate())) {
- Matrix4 *mat = mSnapshot->transform;
- float m00 = mat->data[Matrix4::kScaleX];
- float m01 = mat->data[Matrix4::kSkewY];
- float m10 = mat->data[Matrix4::kSkewX];
- float m11 = mat->data[Matrix4::kScaleY];
-
- float scaleX = sqrtf(m00 * m00 + m01 * m01);
- float scaleY = sqrtf(m10 * m10 + m11 * m11);
-
- inverseScaleX = (scaleX != 0) ? (inverseScaleX / scaleX) : 0;
- inverseScaleY = (scaleY != 0) ? (inverseScaleY / scaleY) : 0;
-
- if (inverseScaleX != 1.0f || inverseScaleY != 1.0f) {
- scaled = true;
- }
- }
- }
-
- getAlphaAndMode(paint, &alpha, &mode);
-
- mCaches.enableScissor();
-
- setupDraw();
- setupDrawNoTexture();
- if (isAA) {
- setupDrawAA();
- }
- setupDrawColor(paint->getColor(), alpha);
- setupDrawColorFilter();
- setupDrawShader();
- setupDrawBlending(isAA, mode);
- setupDrawProgram();
- setupDrawModelViewIdentity(true);
- setupDrawColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawShaderIdentityUniforms();
+status_t OpenGLRenderer::drawConvexPath(const SkPath& path, SkPaint* paint) {
+ VertexBuffer vertexBuffer;
+ // TODO: try clipping large paths to viewport
+ PathTessellator::tessellatePath(path, paint, mSnapshot->transform, vertexBuffer);
- if (isHairLine) {
- // Set a real stroke width to be used in quad construction
- halfStrokeWidth = isAA? 1 : .5;
- } else if (isAA && !scaled) {
- // Expand boundary to enable AA calculations on the quad border
- halfStrokeWidth += .5f;
+ if (hasLayer()) {
+ SkRect bounds = path.getBounds();
+ PathTessellator::expandBoundsForStroke(bounds, paint, false);
+ dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, currentTransform());
}
- int widthSlot;
- int lengthSlot;
-
- Vertex lines[verticesCount];
- Vertex* vertices = &lines[0];
-
- AAVertex wLines[verticesCount];
- AAVertex* aaVertices = &wLines[0];
-
- if (CC_UNLIKELY(!isAA)) {
- setupDrawVertices(vertices);
- } else {
- void* widthCoords = ((GLbyte*) aaVertices) + gVertexAAWidthOffset;
- void* lengthCoords = ((GLbyte*) aaVertices) + gVertexAALengthOffset;
- // innerProportion is the ratio of the inner (non-AA) part of the line to the total
- // AA stroke width (the base stroke width expanded by a half pixel on either side).
- // This value is used in the fragment shader to determine how to fill fragments.
- // We will need to calculate the actual width proportion on each segment for
- // scaled non-hairlines, since the boundary proportion may differ per-axis when scaled.
- float boundaryWidthProportion = .5 - 1 / (2 * halfStrokeWidth);
- setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords,
- boundaryWidthProportion, widthSlot, lengthSlot);
- }
-
- AAVertex* prevAAVertex = NULL;
- Vertex* prevVertex = NULL;
-
- int boundaryLengthSlot = -1;
- int boundaryWidthSlot = -1;
-
- for (int i = 0; i < count; i += 4) {
- // a = start point, b = end point
- vec2 a(points[i], points[i + 1]);
- vec2 b(points[i + 2], points[i + 3]);
-
- float length = 0;
- float boundaryLengthProportion = 0;
- float boundaryWidthProportion = 0;
-
- // Find the normal to the line
- vec2 n = (b - a).copyNormalized() * halfStrokeWidth;
- float x = n.x;
- n.x = -n.y;
- n.y = x;
-
- if (isHairLine) {
- if (isAA) {
- float wideningFactor;
- if (fabs(n.x) >= fabs(n.y)) {
- wideningFactor = fabs(1.0f / n.x);
- } else {
- wideningFactor = fabs(1.0f / n.y);
- }
- n *= wideningFactor;
- }
-
- if (scaled) {
- n.x *= inverseScaleX;
- n.y *= inverseScaleY;
- }
- } else if (scaled) {
- // Extend n by .5 pixel on each side, post-transform
- vec2 extendedN = n.copyNormalized();
- extendedN /= 2;
- extendedN.x *= inverseScaleX;
- extendedN.y *= inverseScaleY;
-
- float extendedNLength = extendedN.length();
- // We need to set this value on the shader prior to drawing
- boundaryWidthProportion = .5 - extendedNLength / (halfStrokeWidth + extendedNLength);
- n += extendedN;
- }
-
- // aa lines expand the endpoint vertices to encompass the AA boundary
- if (isAA) {
- vec2 abVector = (b - a);
- length = abVector.length();
- abVector.normalize();
-
- if (scaled) {
- abVector.x *= inverseScaleX;
- abVector.y *= inverseScaleY;
- float abLength = abVector.length();
- boundaryLengthProportion = .5 - abLength / (length + abLength);
- } else {
- boundaryLengthProportion = .5 - .5 / (length + 1);
- }
-
- abVector /= 2;
- a -= abVector;
- b += abVector;
- }
-
- // Four corners of the rectangle defining a thick line
- vec2 p1 = a - n;
- vec2 p2 = a + n;
- vec2 p3 = b + n;
- vec2 p4 = b - n;
-
-
- const float left = fmin(p1.x, fmin(p2.x, fmin(p3.x, p4.x)));
- const float right = fmax(p1.x, fmax(p2.x, fmax(p3.x, p4.x)));
- const float top = fmin(p1.y, fmin(p2.y, fmin(p3.y, p4.y)));
- const float bottom = fmax(p1.y, fmax(p2.y, fmax(p3.y, p4.y)));
-
- if (!quickRejectNoScissor(left, top, right, bottom)) {
- if (!isAA) {
- if (prevVertex != NULL) {
- // Issue two repeat vertices to create degenerate triangles to bridge
- // between the previous line and the new one. This is necessary because
- // we are creating a single triangle_strip which will contain
- // potentially discontinuous line segments.
- Vertex::set(vertices++, prevVertex->position[0], prevVertex->position[1]);
- Vertex::set(vertices++, p1.x, p1.y);
- generatedVerticesCount += 2;
- }
-
- Vertex::set(vertices++, p1.x, p1.y);
- Vertex::set(vertices++, p2.x, p2.y);
- Vertex::set(vertices++, p4.x, p4.y);
- Vertex::set(vertices++, p3.x, p3.y);
-
- prevVertex = vertices - 1;
- generatedVerticesCount += 4;
- } else {
- if (!isHairLine && scaled) {
- // Must set width proportions per-segment for scaled non-hairlines to use the
- // correct AA boundary dimensions
- if (boundaryWidthSlot < 0) {
- boundaryWidthSlot =
- mCaches.currentProgram->getUniform("boundaryWidth");
- }
-
- glUniform1f(boundaryWidthSlot, boundaryWidthProportion);
- }
-
- if (boundaryLengthSlot < 0) {
- boundaryLengthSlot = mCaches.currentProgram->getUniform("boundaryLength");
- }
-
- glUniform1f(boundaryLengthSlot, boundaryLengthProportion);
-
- if (prevAAVertex != NULL) {
- // Issue two repeat vertices to create degenerate triangles to bridge
- // between the previous line and the new one. This is necessary because
- // we are creating a single triangle_strip which will contain
- // potentially discontinuous line segments.
- AAVertex::set(aaVertices++,prevAAVertex->position[0],
- prevAAVertex->position[1], prevAAVertex->width, prevAAVertex->length);
- AAVertex::set(aaVertices++, p4.x, p4.y, 1, 1);
- generatedVerticesCount += 2;
- }
+ return drawVertexBuffer(vertexBuffer, paint);
+}
- AAVertex::set(aaVertices++, p4.x, p4.y, 1, 1);
- AAVertex::set(aaVertices++, p1.x, p1.y, 1, 0);
- AAVertex::set(aaVertices++, p3.x, p3.y, 0, 1);
- AAVertex::set(aaVertices++, p2.x, p2.y, 0, 0);
+/**
+ * We create tristrips for the lines much like shape stroke tessellation, using a per-vertex alpha
+ * and additional geometry for defining an alpha slope perimeter.
+ *
+ * Using GL_LINES can be difficult because the rasterization rules for those lines produces some
+ * unexpected results, and may vary between hardware devices. Previously we used a varying-base
+ * in-shader alpha region, but found it to be taxing on some GPUs.
+ *
+ * TODO: try using a fixed input buffer for non-capped lines as in text rendering. this may reduce
+ * memory transfer by removing need for degenerate vertices.
+ */
+status_t OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
+ if (mSnapshot->isIgnored() || count < 4) return DrawGlInfo::kStatusDone;
- prevAAVertex = aaVertices - 1;
- generatedVerticesCount += 4;
- }
+ count &= ~0x3; // round down to nearest four
- dirtyLayer(a.x == b.x ? left - 1 : left, a.y == b.y ? top - 1 : top,
- a.x == b.x ? right: right, a.y == b.y ? bottom: bottom,
- *mSnapshot->transform);
- }
- }
+ VertexBuffer buffer;
+ SkRect bounds;
+ PathTessellator::tessellateLines(points, count, paint, mSnapshot->transform, bounds, buffer);
- if (generatedVerticesCount > 0) {
- glDrawArrays(GL_TRIANGLE_STRIP, 0, generatedVerticesCount);
+ if (quickReject(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom)) {
+ return DrawGlInfo::kStatusDone;
}
- if (isAA) {
- finishDrawAALine(widthSlot, lengthSlot);
- }
+ dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, currentTransform());
- return DrawGlInfo::kStatusDrew;
+ bool useOffset = !paint->isAntiAlias();
+ return drawVertexBuffer(buffer, paint, useOffset);
}
status_t OpenGLRenderer::drawPoints(float* points, int count, SkPaint* paint) {
@@ -2277,6 +2420,7 @@ status_t OpenGLRenderer::drawPoints(float* points, int count, SkPaint* paint) {
strokeWidth = 1.0f;
}
const float halfWidth = strokeWidth / 2;
+
int alpha;
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
@@ -2315,7 +2459,7 @@ status_t OpenGLRenderer::drawPoints(float* points, int count, SkPaint* paint) {
float top = points[i + 1] - halfWidth;
float bottom = points [i + 1] + halfWidth;
- dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
+ dirtyLayer(left, top, right, bottom, currentTransform());
}
glDrawArrays(GL_POINTS, 0, generatedVerticesCount);
@@ -2350,13 +2494,14 @@ status_t OpenGLRenderer::drawShape(float left, float top, const PathTexture* tex
status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
+ if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p) ||
+ (p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
if (p->getPathEffect() != 0) {
mCaches.activeTexture(0);
- const PathTexture* texture = mCaches.roundRectShapeCache.getRoundRect(
+ const PathTexture* texture = mCaches.pathCache.getRoundRect(
right - left, bottom - top, rx, ry, p);
return drawShape(left, top, texture, p);
}
@@ -2370,19 +2515,18 @@ status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float
ry += outset;
}
path.addRoundRect(rect, rx, ry);
- drawConvexPath(path, p);
-
- return DrawGlInfo::kStatusDrew;
+ return drawConvexPath(path, p);
}
status_t OpenGLRenderer::drawCircle(float x, float y, float radius, SkPaint* p) {
if (mSnapshot->isIgnored() || quickRejectPreStroke(x - radius, y - radius,
- x + radius, y + radius, p)) {
+ x + radius, y + radius, p) ||
+ (p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
if (p->getPathEffect() != 0) {
mCaches.activeTexture(0);
- const PathTexture* texture = mCaches.circleShapeCache.getCircle(radius, p);
+ const PathTexture* texture = mCaches.pathCache.getCircle(radius, p);
return drawShape(x - radius, y - radius, texture, p);
}
@@ -2392,20 +2536,19 @@ status_t OpenGLRenderer::drawCircle(float x, float y, float radius, SkPaint* p)
} else {
path.addCircle(x, y, radius);
}
- drawConvexPath(path, p);
-
- return DrawGlInfo::kStatusDrew;
+ return drawConvexPath(path, p);
}
status_t OpenGLRenderer::drawOval(float left, float top, float right, float bottom,
SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
+ if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p) ||
+ (p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
if (p->getPathEffect() != 0) {
mCaches.activeTexture(0);
- const PathTexture* texture = mCaches.ovalShapeCache.getOval(right - left, bottom - top, p);
+ const PathTexture* texture = mCaches.pathCache.getOval(right - left, bottom - top, p);
return drawShape(left, top, texture, p);
}
@@ -2415,14 +2558,13 @@ status_t OpenGLRenderer::drawOval(float left, float top, float right, float bott
rect.outset(p->getStrokeWidth() / 2, p->getStrokeWidth() / 2);
}
path.addOval(rect);
- drawConvexPath(path, p);
-
- return DrawGlInfo::kStatusDrew;
+ return drawConvexPath(path, p);
}
status_t OpenGLRenderer::drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
+ if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p) ||
+ (p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
@@ -2431,9 +2573,9 @@ status_t OpenGLRenderer::drawArc(float left, float top, float right, float botto
}
// TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180)
- if (p->getStyle() != SkPaint::kStroke_Style || p->getPathEffect() != 0 || p->getStrokeCap() != SkPaint::kButt_Cap || useCenter) {
+ if (p->getStyle() != SkPaint::kStroke_Style || p->getPathEffect() != 0 || useCenter) {
mCaches.activeTexture(0);
- const PathTexture* texture = mCaches.arcShapeCache.getArc(right - left, bottom - top,
+ const PathTexture* texture = mCaches.pathCache.getArc(right - left, bottom - top,
startAngle, sweepAngle, useCenter, p);
return drawShape(left, top, texture, p);
}
@@ -2451,16 +2593,15 @@ status_t OpenGLRenderer::drawArc(float left, float top, float right, float botto
if (useCenter) {
path.close();
}
- drawConvexPath(path, p);
-
- return DrawGlInfo::kStatusDrew;
+ return drawConvexPath(path, p);
}
// See SkPaintDefaults.h
#define SkPaintDefaults_MiterLimit SkIntToScalar(4)
status_t OpenGLRenderer::drawRect(float left, float top, float right, float bottom, SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
+ if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p) ||
+ (p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
@@ -2470,7 +2611,7 @@ status_t OpenGLRenderer::drawRect(float left, float top, float right, float bott
p->getStrokeMiter() != SkPaintDefaults_MiterLimit) {
mCaches.activeTexture(0);
const PathTexture* texture =
- mCaches.rectShapeCache.getRect(right - left, bottom - top, p);
+ mCaches.pathCache.getRect(right - left, bottom - top, p);
return drawShape(left, top, texture, p);
}
@@ -2480,20 +2621,17 @@ status_t OpenGLRenderer::drawRect(float left, float top, float right, float bott
rect.outset(p->getStrokeWidth() / 2, p->getStrokeWidth() / 2);
}
path.addRect(rect);
- drawConvexPath(path, p);
-
- return DrawGlInfo::kStatusDrew;
+ return drawConvexPath(path, p);
}
- if (p->isAntiAlias() && !mSnapshot->transform->isSimple()) {
+ if (p->isAntiAlias() && !currentTransform().isSimple()) {
SkPath path;
path.addRect(left, top, right, bottom);
- drawConvexPath(path, p);
+ return drawConvexPath(path, p);
} else {
drawColorRect(left, top, right, bottom, p->getColor(), getXfermode(p->getXfermode()));
+ return DrawGlInfo::kStatusDrew;
}
-
- return DrawGlInfo::kStatusDrew;
}
void OpenGLRenderer::drawTextShadow(SkPaint* paint, const char* text, int bytesCount, int count,
@@ -2505,15 +2643,15 @@ void OpenGLRenderer::drawTextShadow(SkPaint* paint, const char* text, int bytesC
// if shader-based correction is enabled
mCaches.dropShadowCache.setFontRenderer(fontRenderer);
const ShadowTexture* shadow = mCaches.dropShadowCache.get(
- paint, text, bytesCount, count, mShadowRadius, positions);
+ paint, text, bytesCount, count, mDrawModifiers.mShadowRadius, positions);
const AutoTexture autoCleanup(shadow);
- const float sx = x - shadow->left + mShadowDx;
- const float sy = y - shadow->top + mShadowDy;
+ const float sx = x - shadow->left + mDrawModifiers.mShadowDx;
+ const float sy = y - shadow->top + mDrawModifiers.mShadowDy;
- const int shadowAlpha = ((mShadowColor >> 24) & 0xFF) * mSnapshot->alpha;
- int shadowColor = mShadowColor;
- if (mShader) {
+ const int shadowAlpha = ((mDrawModifiers.mShadowColor >> 24) & 0xFF) * mSnapshot->alpha;
+ int shadowColor = mDrawModifiers.mShadowColor;
+ if (mDrawModifiers.mShader) {
shadowColor = 0xffffffff;
}
@@ -2534,72 +2672,102 @@ void OpenGLRenderer::drawTextShadow(SkPaint* paint, const char* text, int bytesC
glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
}
+bool OpenGLRenderer::canSkipText(const SkPaint* paint) const {
+ float alpha = (mDrawModifiers.mHasShadow ? 1.0f : paint->getAlpha()) * mSnapshot->alpha;
+ return alpha == 0.0f && getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode;
+}
+
+class TextSetupFunctor: public Functor {
+public:
+ TextSetupFunctor(OpenGLRenderer& renderer, float x, float y, bool pureTranslate,
+ int alpha, SkXfermode::Mode mode, SkPaint* paint): Functor(),
+ renderer(renderer), x(x), y(y), pureTranslate(pureTranslate),
+ alpha(alpha), mode(mode), paint(paint) {
+ }
+ ~TextSetupFunctor() { }
+
+ status_t operator ()(int what, void* data) {
+ renderer.setupDraw();
+ renderer.setupDrawTextGamma(paint);
+ renderer.setupDrawDirtyRegionsDisabled();
+ renderer.setupDrawWithTexture(true);
+ renderer.setupDrawAlpha8Color(paint->getColor(), alpha);
+ renderer.setupDrawColorFilter();
+ renderer.setupDrawShader();
+ renderer.setupDrawBlending(true, mode);
+ renderer.setupDrawProgram();
+ renderer.setupDrawModelView(x, y, x, y, pureTranslate, true);
+ // Calling setupDrawTexture with the name 0 will enable the
+ // uv attributes and increase the texture unit count
+ // texture binding will be performed by the font renderer as
+ // needed
+ renderer.setupDrawTexture(0);
+ renderer.setupDrawPureColorUniforms();
+ renderer.setupDrawColorFilterUniforms();
+ renderer.setupDrawShaderUniforms(pureTranslate);
+ renderer.setupDrawTextGammaUniforms();
+
+ return NO_ERROR;
+ }
+
+ OpenGLRenderer& renderer;
+ float x;
+ float y;
+ bool pureTranslate;
+ int alpha;
+ SkXfermode::Mode mode;
+ SkPaint* paint;
+};
+
status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count,
const float* positions, SkPaint* paint) {
- if (text == NULL || count == 0 || mSnapshot->isIgnored() ||
- (paint->getAlpha() * mSnapshot->alpha == 0 && paint->getXfermode() == NULL)) {
+ if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
return DrawGlInfo::kStatusDone;
}
// NOTE: Skia does not support perspective transform on drawPosText yet
- if (!mSnapshot->transform->isSimple()) {
+ if (!currentTransform().isSimple()) {
return DrawGlInfo::kStatusDone;
}
float x = 0.0f;
float y = 0.0f;
- const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+ const bool pureTranslate = currentTransform().isPureTranslate();
if (pureTranslate) {
- x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
- y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
+ x = (int) floorf(x + currentTransform().getTranslateX() + 0.5f);
+ y = (int) floorf(y + currentTransform().getTranslateY() + 0.5f);
}
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()),
- paint->getTextSize());
+ fontRenderer.setFont(paint, mat4::identity());
int alpha;
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
- if (CC_UNLIKELY(mHasShadow)) {
- drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer, alpha, mode,
- 0.0f, 0.0f);
+ if (CC_UNLIKELY(mDrawModifiers.mHasShadow)) {
+ drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
+ alpha, mode, 0.0f, 0.0f);
}
// Pick the appropriate texture filtering
- bool linearFilter = mSnapshot->transform->changesBounds();
+ bool linearFilter = currentTransform().changesBounds();
if (pureTranslate && !linearFilter) {
linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
}
-
- mCaches.activeTexture(0);
- setupDraw();
- setupDrawTextGamma(paint);
- setupDrawDirtyRegionsDisabled();
- setupDrawWithTexture(true);
- setupDrawAlpha8Color(paint->getColor(), alpha);
- setupDrawColorFilter();
- setupDrawShader();
- setupDrawBlending(true, mode);
- setupDrawProgram();
- setupDrawModelView(x, y, x, y, pureTranslate, true);
- setupDrawTexture(fontRenderer.getTexture(linearFilter));
- setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawShaderUniforms(pureTranslate);
- setupDrawTextGammaUniforms();
+ fontRenderer.setTextureFiltering(linearFilter);
const Rect* clip = pureTranslate ? mSnapshot->clipRect : &mSnapshot->getLocalClip();
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
const bool hasActiveLayer = hasLayer();
+ TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint);
if (fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
- positions, hasActiveLayer ? &bounds : NULL)) {
+ positions, hasActiveLayer ? &bounds : NULL, &functor)) {
if (hasActiveLayer) {
if (!pureTranslate) {
- mSnapshot->transform->mapRect(bounds);
+ currentTransform().mapRect(bounds);
}
dirtyLayerUnchecked(bounds, getRegion());
}
@@ -2608,10 +2776,25 @@ status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count
return DrawGlInfo::kStatusDrew;
}
+mat4 OpenGLRenderer::findBestFontTransform(const mat4& transform) const {
+ mat4 fontTransform;
+ if (CC_LIKELY(transform.isPureTranslate())) {
+ fontTransform = mat4::identity();
+ } else {
+ if (CC_UNLIKELY(transform.isPerspective())) {
+ fontTransform = mat4::identity();
+ } else {
+ float sx, sy;
+ currentTransform().decomposeScale(sx, sy);
+ fontTransform.loadScale(sx, sy, 1.0f);
+ }
+ }
+ return fontTransform;
+}
+
status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
float x, float y, const float* positions, SkPaint* paint, float length) {
- if (text == NULL || count == 0 || mSnapshot->isIgnored() ||
- (paint->getAlpha() * mSnapshot->alpha == 0 && paint->getXfermode() == NULL)) {
+ if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
return DrawGlInfo::kStatusDone;
}
@@ -2635,76 +2818,66 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
const float oldX = x;
const float oldY = y;
- const bool pureTranslate = mSnapshot->transform->isPureTranslate();
- if (CC_LIKELY(pureTranslate)) {
- x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
- y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
- }
-#if DEBUG_GLYPHS
- ALOGD("OpenGLRenderer drawText() with FontID=%d",
- SkTypeface::UniqueID(paint->getTypeface()));
-#endif
+ const mat4& transform = currentTransform();
+ const bool pureTranslate = transform.isPureTranslate();
- FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()),
- paint->getTextSize());
+ if (CC_LIKELY(pureTranslate)) {
+ x = (int) floorf(x + transform.getTranslateX() + 0.5f);
+ y = (int) floorf(y + transform.getTranslateY() + 0.5f);
+ }
int alpha;
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
- if (CC_UNLIKELY(mHasShadow)) {
- drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer, alpha, mode,
- oldX, oldY);
- }
+ FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- // Pick the appropriate texture filtering
- bool linearFilter = mSnapshot->transform->changesBounds();
- if (pureTranslate && !linearFilter) {
- linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
+ if (CC_UNLIKELY(mDrawModifiers.mHasShadow)) {
+ fontRenderer.setFont(paint, mat4::identity());
+ drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
+ alpha, mode, oldX, oldY);
}
- // The font renderer will always use texture unit 0
- mCaches.activeTexture(0);
- setupDraw();
- setupDrawTextGamma(paint);
- setupDrawDirtyRegionsDisabled();
- setupDrawWithTexture(true);
- setupDrawAlpha8Color(paint->getColor(), alpha);
- setupDrawColorFilter();
- setupDrawShader();
- setupDrawBlending(true, mode);
- setupDrawProgram();
- setupDrawModelView(x, y, x, y, pureTranslate, true);
- // See comment above; the font renderer must use texture unit 0
- // assert(mTextureUnit == 0)
- setupDrawTexture(fontRenderer.getTexture(linearFilter));
- setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawShaderUniforms(pureTranslate);
- setupDrawTextGammaUniforms();
+ const bool hasActiveLayer = hasLayer();
- const Rect* clip = pureTranslate ? mSnapshot->clipRect :
- (mSnapshot->hasPerspectiveTransform() ? NULL : &mSnapshot->getLocalClip());
- Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
+ // 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.
+ mat4 fontTransform = findBestFontTransform(transform);
+ fontRenderer.setFont(paint, fontTransform);
- const bool hasActiveLayer = hasLayer();
+ // Pick the appropriate texture filtering
+ bool linearFilter = !pureTranslate || fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
+ fontRenderer.setTextureFiltering(linearFilter);
+
+ // TODO: Implement better clipping for scaled/rotated text
+ const Rect* clip = !pureTranslate ? NULL : mSnapshot->clipRect;
+ Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
bool status;
+ TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint);
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,
- positions, hasActiveLayer ? &bounds : NULL);
+ positions, hasActiveLayer ? &bounds : NULL, &functor);
} else {
status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
- positions, hasActiveLayer ? &bounds : NULL);
+ positions, hasActiveLayer ? &bounds : NULL, &functor);
}
if (status && hasActiveLayer) {
if (!pureTranslate) {
- mSnapshot->transform->mapRect(bounds);
+ transform.mapRect(bounds);
}
dirtyLayerUnchecked(bounds, getRegion());
}
@@ -2716,20 +2889,18 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count, SkPath* path,
float hOffset, float vOffset, SkPaint* paint) {
- if (text == NULL || count == 0 || mSnapshot->isIgnored() ||
- (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
+ if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
return DrawGlInfo::kStatusDone;
}
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()),
- paint->getTextSize());
+ fontRenderer.setFont(paint, mat4::identity());
+ fontRenderer.setTextureFiltering(true);
int alpha;
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
- mCaches.activeTexture(0);
setupDraw();
setupDrawTextGamma(paint);
setupDrawDirtyRegionsDisabled();
@@ -2740,7 +2911,11 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co
setupDrawBlending(true, mode);
setupDrawProgram();
setupDrawModelView(0.0f, 0.0f, 0.0f, 0.0f, false, true);
- setupDrawTexture(fontRenderer.getTexture(true));
+ // Calling setupDrawTexture with the name 0 will enable the
+ // uv attributes and increase the texture unit count
+ // texture binding will be performed by the font renderer as
+ // needed
+ setupDrawTexture(0);
setupDrawPureColorUniforms();
setupDrawColorFilterUniforms();
setupDrawShaderUniforms(false);
@@ -2754,7 +2929,7 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co
if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
hOffset, vOffset, hasActiveLayer ? &bounds : NULL)) {
if (hasActiveLayer) {
- mSnapshot->transform->mapRect(bounds);
+ currentTransform().mapRect(bounds);
dirtyLayerUnchecked(bounds, getRegion());
}
}
@@ -2767,7 +2942,6 @@ status_t OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
mCaches.activeTexture(0);
- // TODO: Perform early clip test before we rasterize the path
const PathTexture* texture = mCaches.pathCache.get(path, paint);
if (!texture) return DrawGlInfo::kStatusDone;
const AutoTexture autoCleanup(texture);
@@ -2780,7 +2954,7 @@ status_t OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
return DrawGlInfo::kStatusDrew;
}
-status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* paint) {
+status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) {
if (!layer) {
return DrawGlInfo::kStatusDone;
}
@@ -2790,7 +2964,7 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* pain
transform = &layer->getTransform();
if (!transform->isIdentity()) {
save(0);
- mSnapshot->transform->multiply(*transform);
+ currentTransform().multiply(*transform);
}
}
@@ -2806,22 +2980,19 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* pain
return DrawGlInfo::kStatusDone;
}
- bool debugLayerUpdate = false;
- if (updateLayer(layer, true)) {
- debugLayerUpdate = mCaches.debugLayersUpdates;
- }
+ updateLayer(layer, true);
mCaches.setScissorEnabled(mScissorOptimizationDisabled || !clip.contains(transformed));
mCaches.activeTexture(0);
if (CC_LIKELY(!layer->region.isEmpty())) {
- SkiaColorFilter* oldFilter = mColorFilter;
- mColorFilter = layer->getColorFilter();
+ SkiaColorFilter* oldFilter = mDrawModifiers.mColorFilter;
+ mDrawModifiers.mColorFilter = layer->getColorFilter();
if (layer->region.isRect()) {
composeLayerRect(layer, layer->regionRect);
} else if (layer->mesh) {
- const float a = layer->getAlpha() / 255.0f;
+ const float a = getLayerAlpha(layer);
setupDraw();
setupDrawWithTexture();
setupDrawColor(a, a, a, a);
@@ -2831,9 +3002,9 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* pain
setupDrawPureColorUniforms();
setupDrawColorFilterUniforms();
setupDrawTexture(layer->getTexture());
- if (CC_LIKELY(mSnapshot->transform->isPureTranslate())) {
- int tx = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
- int ty = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
+ if (CC_LIKELY(currentTransform().isPureTranslate())) {
+ int tx = (int) floorf(x + currentTransform().getTranslateX() + 0.5f);
+ int ty = (int) floorf(y + currentTransform().getTranslateY() + 0.5f);
layer->setFilter(GL_NEAREST);
setupDrawModelViewTranslate(tx, ty,
@@ -2855,9 +3026,10 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* pain
#endif
}
- mColorFilter = oldFilter;
+ mDrawModifiers.mColorFilter = oldFilter;
- if (debugLayerUpdate) {
+ if (layer->debugDrawUpdate) {
+ layer->debugDrawUpdate = false;
drawColorRect(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight(),
0x7f00ff00, SkXfermode::kSrcOver_Mode);
}
@@ -2875,13 +3047,13 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* pain
///////////////////////////////////////////////////////////////////////////////
void OpenGLRenderer::resetShader() {
- mShader = NULL;
+ mDrawModifiers.mShader = NULL;
}
void OpenGLRenderer::setupShader(SkiaShader* shader) {
- mShader = shader;
- if (mShader) {
- mShader->set(&mCaches.textureCache, &mCaches.gradientCache);
+ mDrawModifiers.mShader = shader;
+ if (mDrawModifiers.mShader) {
+ mDrawModifiers.mShader->set(&mCaches.textureCache, &mCaches.gradientCache);
}
}
@@ -2890,11 +3062,11 @@ void OpenGLRenderer::setupShader(SkiaShader* shader) {
///////////////////////////////////////////////////////////////////////////////
void OpenGLRenderer::resetColorFilter() {
- mColorFilter = NULL;
+ mDrawModifiers.mColorFilter = NULL;
}
void OpenGLRenderer::setupColorFilter(SkiaColorFilter* filter) {
- mColorFilter = filter;
+ mDrawModifiers.mColorFilter = filter;
}
///////////////////////////////////////////////////////////////////////////////
@@ -2902,15 +3074,15 @@ void OpenGLRenderer::setupColorFilter(SkiaColorFilter* filter) {
///////////////////////////////////////////////////////////////////////////////
void OpenGLRenderer::resetShadow() {
- mHasShadow = false;
+ mDrawModifiers.mHasShadow = false;
}
void OpenGLRenderer::setupShadow(float radius, float dx, float dy, int color) {
- mHasShadow = true;
- mShadowRadius = radius;
- mShadowDx = dx;
- mShadowDy = dy;
- mShadowColor = color;
+ mDrawModifiers.mHasShadow = true;
+ mDrawModifiers.mShadowRadius = radius;
+ mDrawModifiers.mShadowDx = dx;
+ mDrawModifiers.mShadowDy = dy;
+ mDrawModifiers.mShadowColor = color;
}
///////////////////////////////////////////////////////////////////////////////
@@ -2918,22 +3090,25 @@ void OpenGLRenderer::setupShadow(float radius, float dx, float dy, int color) {
///////////////////////////////////////////////////////////////////////////////
void OpenGLRenderer::resetPaintFilter() {
- mHasDrawFilter = false;
+ mDrawModifiers.mHasDrawFilter = false;
}
void OpenGLRenderer::setupPaintFilter(int clearBits, int setBits) {
- mHasDrawFilter = true;
- mPaintFilterClearBits = clearBits & SkPaint::kAllFlags;
- mPaintFilterSetBits = setBits & SkPaint::kAllFlags;
+ mDrawModifiers.mHasDrawFilter = true;
+ mDrawModifiers.mPaintFilterClearBits = clearBits & SkPaint::kAllFlags;
+ mDrawModifiers.mPaintFilterSetBits = setBits & SkPaint::kAllFlags;
}
SkPaint* OpenGLRenderer::filterPaint(SkPaint* paint) {
- if (CC_LIKELY(!mHasDrawFilter || !paint)) return paint;
+ if (CC_LIKELY(!mDrawModifiers.mHasDrawFilter || !paint)) {
+ return paint;
+ }
uint32_t flags = paint->getFlags();
mFilteredPaint = *paint;
- mFilteredPaint.setFlags((flags & ~mPaintFilterClearBits) | mPaintFilterSetBits);
+ mFilteredPaint.setFlags((flags & ~mDrawModifiers.mPaintFilterClearBits) |
+ mDrawModifiers.mPaintFilterSetBits);
return &mFilteredPaint;
}
@@ -3026,10 +3201,88 @@ void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float
}
}
+status_t OpenGLRenderer::drawRects(const float* rects, int count, SkPaint* paint) {
+ if (mSnapshot->isIgnored()) {
+ return DrawGlInfo::kStatusDone;
+ }
+
+ int color = paint->getColor();
+ // If a shader is set, preserve only the alpha
+ if (mDrawModifiers.mShader) {
+ color |= 0x00ffffff;
+ }
+ SkXfermode::Mode mode = getXfermode(paint->getXfermode());
+
+ return drawColorRects(rects, count, color, mode);
+}
+
+status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color,
+ SkXfermode::Mode mode, bool ignoreTransform, bool dirty, bool clip) {
+ if (count == 0) {
+ return DrawGlInfo::kStatusDone;
+ }
+
+ float left = FLT_MAX;
+ float top = FLT_MAX;
+ float right = FLT_MIN;
+ float bottom = FLT_MIN;
+
+ int vertexCount = 0;
+ Vertex mesh[count * 6];
+ Vertex* vertex = mesh;
+
+ 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, b);
+ Vertex::set(vertex++, l, t);
+ Vertex::set(vertex++, r, t);
+ Vertex::set(vertex++, l, b);
+ Vertex::set(vertex++, r, t);
+ Vertex::set(vertex++, r, b);
+
+ vertexCount += 6;
+
+ left = fminf(left, l);
+ top = fminf(top, t);
+ right = fmaxf(right, r);
+ bottom = fmaxf(bottom, b);
+ }
+
+ if (clip && quickReject(left, top, right, bottom)) {
+ return DrawGlInfo::kStatusDone;
+ }
+
+ setupDraw();
+ setupDrawNoTexture();
+ setupDrawColor(color, ((color >> 24) & 0xFF) * mSnapshot->alpha);
+ setupDrawShader();
+ setupDrawColorFilter();
+ setupDrawBlending(mode);
+ setupDrawProgram();
+ setupDrawDirtyRegionsDisabled();
+ setupDrawModelView(0.0f, 0.0f, 1.0f, 1.0f, ignoreTransform, true);
+ setupDrawColorUniforms();
+ setupDrawShaderUniforms();
+ setupDrawColorFilterUniforms();
+ setupDrawVertices((GLvoid*) &mesh[0].position[0]);
+
+ if (dirty && hasLayer()) {
+ dirtyLayer(left, top, right, bottom, currentTransform());
+ }
+
+ glDrawArrays(GL_TRIANGLES, 0, vertexCount);
+
+ return DrawGlInfo::kStatusDrew;
+}
+
void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom,
int color, SkXfermode::Mode mode, bool ignoreTransform) {
// If a shader is set, preserve only the alpha
- if (mShader) {
+ if (mDrawModifiers.mShader) {
color |= 0x00ffffff;
}
@@ -3057,9 +3310,9 @@ void OpenGLRenderer::drawTextureRect(float left, float top, float right, float b
texture->setWrap(GL_CLAMP_TO_EDGE, true);
- if (CC_LIKELY(mSnapshot->transform->isPureTranslate())) {
- const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f);
- const float y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f);
+ if (CC_LIKELY(currentTransform().isPureTranslate())) {
+ const float x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f);
+ const float y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f);
texture->setFilter(GL_NEAREST, true);
drawTextureMesh(x, y, x + texture->width, y + texture->height, texture->id,
@@ -3090,17 +3343,15 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b
setupDrawColorFilter();
setupDrawBlending(blend, mode, swapSrcDst);
setupDrawProgram();
- if (!dirty) {
- setupDrawDirtyRegionsDisabled();
- }
+ if (!dirty) setupDrawDirtyRegionsDisabled();
if (!ignoreScale) {
setupDrawModelView(left, top, right, bottom, ignoreTransform);
} else {
setupDrawModelViewTranslate(left, top, right, bottom, ignoreTransform);
}
+ setupDrawTexture(texture);
setupDrawPureColorUniforms();
setupDrawColorFilterUniforms();
- setupDrawTexture(texture);
setupDrawMesh(vertices, texCoords, vbo);
glDrawArrays(drawMode, 0, elementsCount);
@@ -3108,6 +3359,33 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b
finishDrawTexture();
}
+void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, float bottom,
+ GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode,
+ GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
+ bool ignoreTransform, bool dirty) {
+
+ setupDraw();
+ setupDrawWithTexture(true);
+ if (hasColor) {
+ setupDrawAlpha8Color(color, alpha);
+ }
+ setupDrawColorFilter();
+ setupDrawShader();
+ setupDrawBlending(true, mode);
+ setupDrawProgram();
+ if (!dirty) setupDrawDirtyRegionsDisabled();
+ setupDrawModelView(left, top, right, bottom, ignoreTransform);
+ setupDrawTexture(texture);
+ setupDrawPureColorUniforms();
+ setupDrawColorFilterUniforms();
+ setupDrawShaderUniforms();
+ setupDrawMesh(vertices, texCoords);
+
+ glDrawArrays(drawMode, 0, elementsCount);
+
+ finishDrawTexture();
+}
+
void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
ProgramDescription& description, bool swapSrcDst) {
blend = blend || mode != SkXfermode::kSrcOver_Mode;
@@ -3119,7 +3397,7 @@ void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
// If the blend mode cannot be implemented using shaders, fall
// back to the default SrcOver blend mode instead
if (CC_UNLIKELY(mode > SkXfermode::kScreen_Mode)) {
- if (CC_UNLIKELY(mCaches.extensions.hasFramebufferFetch())) {
+ if (CC_UNLIKELY(mExtensions.hasFramebufferFetch())) {
description.framebufferMode = mode;
description.swapSrcDst = swapSrcDst;
@@ -3170,10 +3448,24 @@ void OpenGLRenderer::resetDrawTextureTexCoords(float u1, float v1, float u2, flo
TextureVertex::setUV(v++, u2, v2);
}
-void OpenGLRenderer::getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) {
+void OpenGLRenderer::getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) const {
getAlphaAndModeDirect(paint, alpha, mode);
+ if (mDrawModifiers.mOverrideLayerAlpha < 1.0f) {
+ // if drawing a layer, ignore the paint's alpha
+ *alpha = mDrawModifiers.mOverrideLayerAlpha;
+ }
*alpha *= mSnapshot->alpha;
}
+float OpenGLRenderer::getLayerAlpha(Layer* layer) const {
+ float alpha;
+ if (mDrawModifiers.mOverrideLayerAlpha < 1.0f) {
+ alpha = mDrawModifiers.mOverrideLayerAlpha;
+ } else {
+ alpha = layer->getAlpha() / 255.0f;
+ }
+ return alpha * mSnapshot->alpha;
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index c5e4c8e78d93..dd7a5a2d4624 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -48,11 +48,46 @@
namespace android {
namespace uirenderer {
+struct DrawModifiers {
+ SkiaShader* mShader;
+ SkiaColorFilter* mColorFilter;
+ float mOverrideLayerAlpha;
+
+ // Drop shadow
+ bool mHasShadow;
+ float mShadowRadius;
+ float mShadowDx;
+ float mShadowDy;
+ int mShadowColor;
+
+ // Draw filters
+ bool mHasDrawFilter;
+ int mPaintFilterClearBits;
+ int mPaintFilterSetBits;
+};
+
+enum StateDeferFlags {
+ kStateDeferFlag_Draw = 0x1,
+ kStateDeferFlag_Clip = 0x2
+};
+
+struct DeferredDisplayState {
+ Rect mBounds; // local bounds, mapped with matrix to be in screen space coordinates, clipped.
+
+ // the below are set and used by the OpenGLRenderer at record and deferred playback
+ Rect mClip;
+ mat4 mMatrix;
+ DrawModifiers mDrawModifiers;
+ float mAlpha;
+};
+
///////////////////////////////////////////////////////////////////////////////
// Renderer
///////////////////////////////////////////////////////////////////////////////
class DisplayList;
+class TextSetupFunctor;
+class VertexBuffer;
/**
* OpenGL renderer used to draw accelerated 2D graphics. The API is a
@@ -64,6 +99,19 @@ public:
virtual ~OpenGLRenderer();
/**
+ * Sets the name of this renderer. The name is optional and
+ * empty by default. If the pointer is null the name is set
+ * to the empty string.
+ */
+ ANDROID_API void setName(const char* name);
+
+ /**
+ * Returns the name of this renderer as UTF8 string.
+ * The returned pointer is never null.
+ */
+ ANDROID_API const char* getName() const;
+
+ /**
* Read externally defined properties to control the behavior
* of the renderer.
*/
@@ -146,16 +194,28 @@ public:
virtual void restore();
virtual void restoreToCount(int saveCount);
+ ANDROID_API int saveLayer(float left, float top, float right, float bottom,
+ SkPaint* paint, int flags) {
+ SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode;
+ if (paint) mode = getXfermode(paint->getXfermode());
+ return saveLayer(left, top, right, bottom, paint ? paint->getAlpha() : 255, mode, flags);
+ }
+ ANDROID_API int saveLayerAlpha(float left, float top, float right, float bottom,
+ int alpha, int flags) {
+ return saveLayer(left, top, right, bottom, alpha, SkXfermode::kSrcOver_Mode, flags);
+ }
virtual int saveLayer(float left, float top, float right, float bottom,
- SkPaint* p, int flags);
- virtual int saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, int flags);
+ int alpha, SkXfermode::Mode mode, int flags);
+
+ int saveLayerDeferred(float left, float top, float right, float bottom,
+ int alpha, SkXfermode::Mode mode, int flags);
virtual void translate(float dx, float dy);
virtual void rotate(float degrees);
virtual void scale(float sx, float sy);
virtual void skew(float sx, float sy);
+ bool hasRectToRectTransform();
ANDROID_API void getMatrix(SkMatrix* matrix);
virtual void setMatrix(SkMatrix* matrix);
virtual void concatMatrix(SkMatrix* matrix);
@@ -164,12 +224,13 @@ public:
ANDROID_API bool quickReject(float left, float top, float right, float bottom);
bool quickRejectNoScissor(float left, float top, float right, float bottom);
virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
+ virtual bool clipPath(SkPath* path, SkRegion::Op op);
+ virtual bool clipRegion(SkRegion* region, SkRegion::Op op);
virtual Rect* getClipRect();
- virtual status_t drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t flags,
- uint32_t level = 0);
- virtual void outputDisplayList(DisplayList* displayList, uint32_t level = 0);
- virtual status_t drawLayer(Layer* layer, float x, float y, SkPaint* paint);
+ virtual status_t drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t replayFlags);
+ virtual void outputDisplayList(DisplayList* displayList);
+ virtual status_t drawLayer(Layer* layer, float x, float y);
virtual status_t drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
virtual status_t drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint);
virtual status_t drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
@@ -201,6 +262,7 @@ public:
const float* positions, SkPaint* paint);
virtual status_t drawText(const char* text, int bytesCount, int count, float x, float y,
const float* positions, SkPaint* paint, float length = -1.0f);
+ virtual status_t drawRects(const float* rects, int count, SkPaint* paint);
virtual void resetShader();
virtual void setupShader(SkiaShader* shader);
@@ -214,17 +276,44 @@ public:
virtual void resetPaintFilter();
virtual void setupPaintFilter(int clearBits, int setBits);
+ // If this value is set to < 1.0, it overrides alpha set on layer (see drawBitmap, drawLayer)
+ void setOverrideLayerAlpha(float alpha) { mDrawModifiers.mOverrideLayerAlpha = alpha; }
+
SkPaint* filterPaint(SkPaint* paint);
+ bool storeDisplayState(DeferredDisplayState& state, int stateDeferFlags);
+ void restoreDisplayState(const DeferredDisplayState& state);
+
+ const DrawModifiers& getDrawModifiers() { return mDrawModifiers; }
+ void setDrawModifiers(const DrawModifiers& drawModifiers) { mDrawModifiers = drawModifiers; }
+
+ ANDROID_API bool isCurrentTransformSimple() {
+ return mSnapshot->transform->isSimple();
+ }
+
+ Caches& getCaches() {
+ return mCaches;
+ }
+
+ // simple rect clip
+ bool isCurrentClipSimple() {
+ return mSnapshot->clipRegion->isEmpty();
+ }
+
/**
- * Sets the alpha on the current snapshot. This alpha value will be modulated
+ * Scales the alpha on the current snapshot. This alpha value will be modulated
* with other alpha values when drawing primitives.
*/
- void setAlpha(float alpha) {
- mSnapshot->alpha = alpha;
+ void scaleAlpha(float alpha) {
+ mSnapshot->alpha *= alpha;
}
/**
+ * Inserts a named event marker in the stream of GL commands.
+ */
+ void eventMark(const char* name) const;
+
+ /**
* Inserts a named group marker in the stream of GL commands. This marker
* can be used by tools to group commands into logical groups. A call to
* this method must always be followed later on by a call to endMark().
@@ -239,7 +328,8 @@ public:
/**
* 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.
+ * 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
@@ -262,6 +352,12 @@ public:
}
}
+ /**
+ * Return the best transform to use to rasterize text given a full
+ * transform matrix.
+ */
+ mat4 findBestFontTransform(const mat4& transform) const;
+
protected:
/**
* Computes the projection matrix, initialize the first snapshot
@@ -270,6 +366,18 @@ protected:
void initViewport(int width, int height);
/**
+ * 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);
+
+ /**
+ * Indicates the start of rendering. This method will setup the
+ * initial OpenGL state (viewport, clearing the buffer, etc.)
+ */
+ status_t startFrame();
+
+ /**
* Clears the underlying surface if needed.
*/
virtual status_t clear(float left, float top, float right, float bottom, bool opaque);
@@ -280,6 +388,19 @@ protected:
void resumeAfterLayer();
/**
+ * This method is called whenever a stencil buffer is required. Subclasses
+ * should override this method and call attachStencilBufferToLayer() on the
+ * appropriate layer(s).
+ */
+ virtual void ensureStencilBuffer();
+
+ /**
+ * Obtains a stencil render buffer (allocating it if necessary) and
+ * attaches it to the specified layer.
+ */
+ void attachStencilBufferToLayer(Layer* layer);
+
+ /**
* Compose the layer defined in the current snapshot with the layer
* defined by the previous snapshot.
*
@@ -298,28 +419,28 @@ protected:
/**
* Returns the current snapshot.
*/
- sp<Snapshot> getSnapshot() {
+ sp<Snapshot> getSnapshot() const {
return mSnapshot;
}
/**
* Returns the region of the current layer.
*/
- virtual Region* getRegion() {
+ virtual Region* getRegion() const {
return mSnapshot->region;
}
/**
* Indicates whether rendering is currently targeted at a layer.
*/
- virtual bool hasLayer() {
+ virtual bool hasLayer() const {
return (mSnapshot->flags & Snapshot::kFlagFboTarget) && mSnapshot->region;
}
/**
* Returns the name of the FBO this renderer is rendering into.
*/
- virtual GLint getTargetFbo() {
+ virtual GLint getTargetFbo() const {
return 0;
}
@@ -333,13 +454,21 @@ protected:
/**
* 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.
+ * alpha will be 255 and the xfermode will be SRC_OVER. Accounts for both
+ * snapshot alpha, and 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
*/
- inline void getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode);
+ inline void getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) const;
+
+ /**
+ * Gets the alpha from a layer, accounting for snapshot alpha and overrideLayerAlpha
+ *
+ * @param layer The layer from which the alpha is extracted
+ */
+ inline float getLayerAlpha(Layer* layer) const;
/**
* Safely retrieves the mode from the specified xfermode. If the specified
@@ -356,16 +485,19 @@ protected:
/**
* Set to true to suppress error checks at the end of a frame.
*/
- virtual bool suppressErrorChecks() {
+ virtual bool suppressErrorChecks() const {
return false;
}
- Caches& getCaches() {
- return mCaches;
- }
-
private:
/**
+ * Discards the content of the framebuffer if supported by the driver.
+ * This method should be called at the beginning of a frame to optimize
+ * rendering on some tiler architectures.
+ */
+ void discardFramebuffer(float left, float top, float right, float bottom);
+
+ /**
* Ensures the state of the renderer is the same as the state of
* the GL context.
*/
@@ -373,12 +505,21 @@ private:
/**
* Tells the GPU what part of the screen is about to be redrawn.
+ * This method will use the clip rect that we started drawing the
+ * frame with.
* This method needs to be invoked every time getTargetFbo() is
* bound again.
*/
void startTiling(const sp<Snapshot>& snapshot, bool opaque = 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);
+
+ /**
* Tells the GPU that we are done drawing the frame or that we
* are switching to another render target.
*/
@@ -409,6 +550,12 @@ private:
void setScissorFromClip();
/**
+ * Sets the clipping region using the stencil buffer. The clip region
+ * is defined by the current snapshot's clipRegion member.
+ */
+ void setStencilFromClip();
+
+ /**
* Performs a quick reject but does not affect the scissor. Returns
* the transformed rect to test and the current clip.
*/
@@ -421,6 +568,17 @@ private:
bool quickRejectPreStroke(float left, float top, float right, float bottom, SkPaint* paint);
/**
+ * Given the local bounds of the layer, calculates ...
+ */
+ void calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool fboLayer);
+
+ /**
+ * Given the local bounds + clip of the layer, updates current snapshot's empty/invisible
+ */
+ void updateSnapshotIgnoreForLayer(const Rect& bounds, const Rect& clip,
+ bool fboLayer, int alpha);
+
+ /**
* Creates a new layer stored in the specified snapshot.
*
* @param snapshot The snapshot associated with the new layer
@@ -486,7 +644,8 @@ private:
/**
* Draws a colored rectangle with the specified color. The specified coordinates
- * are transformed by the current snapshot's transform matrix.
+ * are transformed by the current snapshot's transform matrix unless specified
+ * otherwise.
*
* @param left The left coordinate of the rectangle
* @param top The top coordinate of the rectangle
@@ -500,6 +659,23 @@ private:
int color, SkXfermode::Mode mode, bool ignoreTransform = false);
/**
+ * Draws a series of colored rectangles with the specified color. The specified
+ * coordinates are transformed by the current snapshot's transform matrix unless
+ * specified otherwise.
+ *
+ * @param rects A list of rectangles, 4 floats (left, top, right, bottom)
+ * per rectangle
+ * @param color The rectangles' ARGB color, defined as a packed 32 bits word
+ * @param mode The Skia xfermode to use
+ * @param ignoreTransform True if the current transform should be ignored
+ * @param dirty True if calling this method should dirty the current layer
+ * @param clip True if the rects should be clipped, false otherwise
+ */
+ status_t drawColorRects(const float* rects, int count, int color,
+ SkXfermode::Mode mode, bool ignoreTransform = false,
+ bool dirty = true, bool clip = true);
+
+ /**
* Draws the shape represented by the specified path texture.
* This method invokes drawPathTexture() but takes into account
* the extra left/top offset and the texture offset to correctly
@@ -524,12 +700,22 @@ private:
void drawAlphaBitmap(Texture* texture, float left, float top, SkPaint* paint);
/**
+ * Renders a strip of polygons with the specified paint, used for tessellated geometry.
+ *
+ * @param vertexBuffer The VertexBuffer to be drawn
+ * @param paint The paint to render with
+ * @param useOffset Offset the vertexBuffer (used in drawing non-AA lines)
+ */
+ status_t drawVertexBuffer(const VertexBuffer& vertexBuffer, SkPaint* paint,
+ bool useOffset = false);
+
+ /**
* Renders the convex hull defined by the specified path as a strip of polygons.
*
* @param path The hull of the path to draw
* @param paint The paint to render with
*/
- void drawConvexPath(const SkPath& path, SkPaint* paint);
+ status_t drawConvexPath(const SkPath& path, SkPaint* paint);
/**
* Draws a textured rectangle with the specified texture. The specified coordinates
@@ -589,6 +775,11 @@ private:
bool swapSrcDst = false, bool ignoreTransform = false, GLuint vbo = 0,
bool ignoreScale = false, bool dirty = true);
+ void drawAlpha8TextureMesh(float left, float top, float right, float bottom,
+ GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode,
+ GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
+ bool ignoreTransform, bool dirty = true);
+
/**
* Draws text underline and strike-through if needed.
*
@@ -645,6 +836,11 @@ private:
void resetDrawTextureTexCoords(float u1, float v1, float u2, float v2);
/**
+ * Returns true if the specified paint will draw invisible text.
+ */
+ bool canSkipText(const SkPaint* paint) const;
+
+ /**
* Binds the specified texture. The texture unit must have been selected
* prior to calling this method.
*/
@@ -688,12 +884,11 @@ private:
* Various methods to setup OpenGL rendering.
*/
void setupDrawWithTexture(bool isAlpha8 = false);
+ void setupDrawWithTextureAndColor(bool isAlpha8 = false);
void setupDrawWithExternalTexture();
void setupDrawNoTexture();
void setupDrawAA();
- void setupDrawVertexShape();
void setupDrawPoint(float pointSize);
- void setupDrawColor(int color);
void setupDrawColor(int color, int alpha);
void setupDrawColor(float r, float g, float b, float a);
void setupDrawAlpha8Color(int color, int alpha);
@@ -724,16 +919,15 @@ private:
void setupDrawTextureTransformUniforms(mat4& transform);
void setupDrawTextGammaUniforms();
void setupDrawMesh(GLvoid* vertices, GLvoid* texCoords = NULL, GLuint vbo = 0);
+ void setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLvoid* colors);
void setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords);
void setupDrawVertices(GLvoid* vertices);
- void setupDrawAALine(GLvoid* vertices, GLvoid* distanceCoords, GLvoid* lengthCoords,
- float strokeWidth, int& widthSlot, int& lengthSlot);
- void finishDrawAALine(const int widthSlot, const int lengthSlot);
void finishDrawTexture();
void accountForClear(SkXfermode::Mode mode);
bool updateLayer(Layer* layer, bool inFrame);
void updateLayers();
+ void flushLayers();
/**
* Renders the specified region as a series of rectangles. This method
@@ -741,6 +935,19 @@ private:
*/
void drawRegionRects(const Region& region);
+ /**
+ * Renders the specified region as a series of rectangles. The region
+ * must be in screen-space coordinates.
+ */
+ void drawRegionRects(const SkRegion& region, int color, SkXfermode::Mode mode,
+ bool dirty = false);
+
+ /**
+ * Draws the current clip region if any. Only when DEBUG_CLIP_REGIONS
+ * is turned on.
+ */
+ void debugClip();
+
void debugOverdraw(bool enable, bool clear);
void renderOverdraw();
@@ -751,6 +958,10 @@ private:
mDirtyClip = true;
}
+ inline mat4& currentTransform() const {
+ return *mSnapshot->transform;
+ }
+
// Dimensions of the drawing surface
int mWidth, mHeight;
@@ -767,32 +978,22 @@ private:
// Current state
sp<Snapshot> mSnapshot;
// State used to define the clipping region
- sp<Snapshot> mTilingSnapshot;
-
- // Shaders
- SkiaShader* mShader;
-
- // Color filters
- SkiaColorFilter* mColorFilter;
+ Rect mTilingClip;
+ // Is the target render surface opaque
+ bool mOpaque;
+ // Is a frame currently being rendered
+ bool mFrameStarted;
// Used to draw textured quads
TextureVertex mMeshVertices[4];
- // Drop shadow
- bool mHasShadow;
- float mShadowRadius;
- float mShadowDx;
- float mShadowDy;
- int mShadowColor;
-
- // Draw filters
- bool mHasDrawFilter;
- int mPaintFilterClearBits;
- int mPaintFilterSetBits;
+ // shader, filters, and shadow
+ DrawModifiers mDrawModifiers;
SkPaint mFilteredPaint;
// Various caches
Caches& mCaches;
+ Extensions& mExtensions;
// List of rectangles to clear after saveLayer() is invoked
Vector<Rect*> mLayers;
@@ -801,9 +1002,6 @@ private:
// List of layers to update at the beginning of a frame
Vector<Layer*> mLayerUpdates;
- // Indentity matrix
- const mat4 mIdentity;
-
// Indicates whether the clip must be restored
bool mDirtyClip;
@@ -829,7 +1027,12 @@ private:
// No-ops start/endTiling when set
bool mSuppressTiling;
+ // Optional name of the renderer
+ String8 mName;
+
friend class DisplayListRenderer;
+ friend class Layer;
+ friend class TextSetupFunctor;
}; // class OpenGLRenderer
diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp
index 902c82f60e38..45c619ee01d4 100644
--- a/libs/hwui/Patch.cpp
+++ b/libs/hwui/Patch.cpp
@@ -37,7 +37,7 @@ Patch::Patch(const uint32_t xCount, const uint32_t yCount, const int8_t emptyQua
// 2 triangles per patch, 3 vertices per triangle
uint32_t maxVertices = ((xCount + 1) * (yCount + 1) - emptyQuads) * 2 * 3;
mVertices = new TextureVertex[maxVertices];
- mUploaded = false;
+ mAllocatedVerticesCount = 0;
verticesCount = 0;
hasEmptyQuads = emptyQuads > 0;
@@ -68,38 +68,37 @@ void Patch::copy(const int32_t* xDivs, const int32_t* yDivs) {
memcpy(mYDivs, yDivs, mYCount * sizeof(int32_t));
}
-void Patch::copy(const int32_t* yDivs) {
- memcpy(mYDivs, yDivs, mYCount * sizeof(int32_t));
-}
-
void Patch::updateColorKey(const uint32_t colorKey) {
mColorKey = colorKey;
}
-bool Patch::matches(const int32_t* xDivs, const int32_t* yDivs, const uint32_t colorKey) {
+bool Patch::matches(const int32_t* xDivs, const int32_t* yDivs,
+ const uint32_t colorKey, const int8_t emptyQuads) {
+
+ bool matches = true;
+
+ if (mEmptyQuads != emptyQuads) {
+ mEmptyQuads = emptyQuads;
+ hasEmptyQuads = emptyQuads > 0;
+ matches = false;
+ }
+
if (mColorKey != colorKey) {
updateColorKey(colorKey);
- copy(xDivs, yDivs);
- return false;
+ matches = false;
}
- for (uint32_t i = 0; i < mXCount; i++) {
- if (mXDivs[i] != xDivs[i]) {
- // The Y divs may or may not match, copy everything
- copy(xDivs, yDivs);
- return false;
- }
+ if (memcmp(mXDivs, xDivs, mXCount * sizeof(int32_t))) {
+ memcpy(mXDivs, xDivs, mXCount * sizeof(int32_t));
+ matches = false;
}
- for (uint32_t i = 0; i < mYCount; i++) {
- if (mYDivs[i] != yDivs[i]) {
- // We know all the X divs match, copy only Y divs
- copy(yDivs);
- return false;
- }
+ if (memcmp(mYDivs, yDivs, mYCount * sizeof(int32_t))) {
+ memcpy(mYDivs, yDivs, mYCount * sizeof(int32_t));
+ matches = false;
}
- return true;
+ return matches;
}
///////////////////////////////////////////////////////////////////////////////
@@ -123,8 +122,6 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight,
float rescaleX = 1.0f;
float rescaleY = 1.0f;
- const float meshWidth = right - left;
-
if (xStretchCount > 0) {
uint32_t stretchSize = 0;
for (uint32_t i = 1; i < mXCount; i += 2) {
@@ -203,10 +200,10 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight,
if (verticesCount > 0) {
Caches& caches = Caches::getInstance();
caches.bindMeshBuffer(meshBuffer);
- if (!mUploaded) {
+ if (mAllocatedVerticesCount < verticesCount) {
glBufferData(GL_ARRAY_BUFFER, sizeof(TextureVertex) * verticesCount,
mVertices, GL_DYNAMIC_DRAW);
- mUploaded = true;
+ mAllocatedVerticesCount = verticesCount;
} else {
glBufferSubData(GL_ARRAY_BUFFER, 0,
sizeof(TextureVertex) * verticesCount, mVertices);
diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h
index 0518d911d7d8..ee7bf700c355 100644
--- a/libs/hwui/Patch.h
+++ b/libs/hwui/Patch.h
@@ -25,7 +25,6 @@
#include "Rect.h"
#include "Vertex.h"
-#include "utils/Compare.h"
namespace android {
namespace uirenderer {
@@ -45,7 +44,7 @@ namespace uirenderer {
* indices to render the vertices.
*/
struct Patch {
- Patch(const uint32_t xCount, const uint32_t yCount, const int8_t emptyQuads = 0);
+ Patch(const uint32_t xCount, const uint32_t yCount, const int8_t emptyQuads);
~Patch();
void updateVertices(const float bitmapWidth, const float bitmapHeight,
@@ -53,7 +52,8 @@ struct Patch {
void updateColorKey(const uint32_t colorKey);
void copy(const int32_t* xDivs, const int32_t* yDivs);
- bool matches(const int32_t* xDivs, const int32_t* yDivs, const uint32_t colorKey);
+ bool matches(const int32_t* xDivs, const int32_t* yDivs,
+ const uint32_t colorKey, const int8_t emptyQuads);
GLuint meshBuffer;
uint32_t verticesCount;
@@ -62,7 +62,7 @@ struct Patch {
private:
TextureVertex* mVertices;
- bool mUploaded;
+ uint32_t mAllocatedVerticesCount;
int32_t* mXDivs;
int32_t* mYDivs;
@@ -72,8 +72,6 @@ private:
uint32_t mYCount;
int8_t mEmptyQuads;
- void copy(const int32_t* yDivs);
-
void generateRow(TextureVertex*& vertex, float y1, float y2,
float v1, float v2, float stretchX, float rescaleX,
float width, float bitmapWidth, uint32_t& quadCount);
diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp
index 9702c3d50c1f..62e38d3dbcbe 100644
--- a/libs/hwui/PatchCache.cpp
+++ b/libs/hwui/PatchCache.cpp
@@ -42,6 +42,35 @@ PatchCache::~PatchCache() {
// Caching
///////////////////////////////////////////////////////////////////////////////
+int PatchCache::PatchDescription::compare(
+ const PatchCache::PatchDescription& lhs, const PatchCache::PatchDescription& rhs) {
+ int deltaInt = lhs.bitmapWidth - rhs.bitmapWidth;
+ if (deltaInt != 0) return deltaInt;
+
+ deltaInt = lhs.bitmapHeight - rhs.bitmapHeight;
+ if (deltaInt != 0) return deltaInt;
+
+ if (lhs.pixelWidth < rhs.pixelWidth) return -1;
+ if (lhs.pixelWidth > rhs.pixelWidth) return +1;
+
+ if (lhs.pixelHeight < rhs.pixelHeight) return -1;
+ if (lhs.pixelHeight > rhs.pixelHeight) return +1;
+
+ deltaInt = lhs.xCount - rhs.xCount;
+ if (deltaInt != 0) return deltaInt;
+
+ deltaInt = lhs.yCount - rhs.yCount;
+ if (deltaInt != 0) return deltaInt;
+
+ deltaInt = lhs.emptyCount - rhs.emptyCount;
+ if (deltaInt != 0) return deltaInt;
+
+ deltaInt = lhs.colorKey - rhs.colorKey;
+ if (deltaInt != 0) return deltaInt;
+
+ return 0;
+}
+
void PatchCache::clear() {
size_t count = mCache.size();
for (size_t i = 0; i < count; i++) {
@@ -50,7 +79,7 @@ void PatchCache::clear() {
mCache.clear();
}
-Patch* PatchCache::get(const float bitmapWidth, const float bitmapHeight,
+Patch* PatchCache::get(const uint32_t bitmapWidth, const uint32_t bitmapHeight,
const float pixelWidth, const float pixelHeight,
const int32_t* xDivs, const int32_t* yDivs, const uint32_t* colors,
const uint32_t width, const uint32_t height, const int8_t numColors) {
@@ -97,7 +126,7 @@ Patch* PatchCache::get(const float bitmapWidth, const float bitmapHeight,
}
mCache.add(description, mesh);
- } else if (!mesh->matches(xDivs, yDivs, colorKey)) {
+ } else if (!mesh->matches(xDivs, yDivs, colorKey, transparentQuads)) {
PATCH_LOGD("Patch mesh does not match, refreshing vertices");
mesh->updateVertices(bitmapWidth, bitmapHeight, 0.0f, 0.0f, pixelWidth, pixelHeight);
}
diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h
index 505798aec3a7..0822cba4de35 100644
--- a/libs/hwui/PatchCache.h
+++ b/libs/hwui/PatchCache.h
@@ -19,7 +19,6 @@
#include <utils/KeyedVector.h>
-#include "utils/Compare.h"
#include "Debug.h"
#include "Patch.h"
@@ -47,7 +46,7 @@ public:
PatchCache(uint32_t maxCapacity);
~PatchCache();
- Patch* get(const float bitmapWidth, const float bitmapHeight,
+ Patch* get(const uint32_t bitmapWidth, const uint32_t bitmapHeight,
const float pixelWidth, const float pixelHeight,
const int32_t* xDivs, const int32_t* yDivs, const uint32_t* colors,
const uint32_t width, const uint32_t height, const int8_t numColors);
@@ -70,7 +69,7 @@ private:
xCount(0), yCount(0), emptyCount(0), colorKey(0) {
}
- PatchDescription(const float bitmapWidth, const float bitmapHeight,
+ PatchDescription(const uint32_t bitmapWidth, const uint32_t bitmapHeight,
const float pixelWidth, const float pixelHeight,
const uint32_t xCount, const uint32_t yCount,
const int8_t emptyCount, const uint32_t colorKey):
@@ -80,28 +79,29 @@ private:
emptyCount(emptyCount), colorKey(colorKey) {
}
- bool operator<(const PatchDescription& rhs) const {
- LTE_FLOAT(bitmapWidth) {
- LTE_FLOAT(bitmapHeight) {
- LTE_FLOAT(pixelWidth) {
- LTE_FLOAT(pixelHeight) {
- LTE_INT(xCount) {
- LTE_INT(yCount) {
- LTE_INT(emptyCount) {
- LTE_INT(colorKey) return false;
- }
- }
- }
- }
- }
- }
- }
- return false;
+ static int compare(const PatchDescription& lhs, const PatchDescription& rhs);
+
+ bool operator==(const PatchDescription& other) const {
+ return compare(*this, other) == 0;
+ }
+
+ bool operator!=(const PatchDescription& other) const {
+ return compare(*this, other) != 0;
+ }
+
+ friend inline int strictly_order_type(const PatchDescription& lhs,
+ const PatchDescription& rhs) {
+ return PatchDescription::compare(lhs, rhs) < 0;
+ }
+
+ friend inline int compare_type(const PatchDescription& lhs,
+ const PatchDescription& rhs) {
+ return PatchDescription::compare(lhs, rhs);
}
private:
- float bitmapWidth;
- float bitmapHeight;
+ uint32_t bitmapWidth;
+ uint32_t bitmapHeight;
float pixelWidth;
float pixelHeight;
uint32_t xCount;
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 71a4ed7966ed..fdb10e2f2c55 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -15,24 +15,85 @@
*/
#define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
-#include <utils/threads.h>
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#include <SkPath.h>
+#include <SkRect.h>
+#include <utils/JenkinsHash.h>
+#include <utils/Trace.h>
+
+#include "Caches.h"
#include "PathCache.h"
-#include "Properties.h"
+
+#include "thread/Signal.h"
+#include "thread/Task.h"
+#include "thread/TaskProcessor.h"
namespace android {
namespace uirenderer {
-// Defined in ShapeCache.h
+///////////////////////////////////////////////////////////////////////////////
+// Cache entries
+///////////////////////////////////////////////////////////////////////////////
-void computePathBounds(const SkPath* path, const SkPaint* paint,
+PathDescription::PathDescription():
+ type(kShapeNone),
+ join(SkPaint::kDefault_Join),
+ cap(SkPaint::kDefault_Cap),
+ style(SkPaint::kFill_Style),
+ miter(4.0f),
+ strokeWidth(1.0f),
+ pathEffect(NULL) {
+ memset(&shape, 0, sizeof(Shape));
+}
+
+PathDescription::PathDescription(ShapeType type, SkPaint* paint):
+ type(type),
+ join(paint->getStrokeJoin()),
+ cap(paint->getStrokeCap()),
+ style(paint->getStyle()),
+ miter(paint->getStrokeMiter()),
+ strokeWidth(paint->getStrokeWidth()),
+ pathEffect(paint->getPathEffect()) {
+ memset(&shape, 0, sizeof(Shape));
+}
+
+hash_t PathDescription::hash() const {
+ uint32_t hash = JenkinsHashMix(0, type);
+ hash = JenkinsHashMix(hash, join);
+ hash = JenkinsHashMix(hash, cap);
+ hash = JenkinsHashMix(hash, style);
+ hash = JenkinsHashMix(hash, android::hash_type(miter));
+ hash = JenkinsHashMix(hash, android::hash_type(strokeWidth));
+ hash = JenkinsHashMix(hash, android::hash_type(pathEffect));
+ hash = JenkinsHashMixBytes(hash, (uint8_t*) &shape, sizeof(Shape));
+ return JenkinsHashWhiten(hash);
+}
+
+int PathDescription::compare(const PathDescription& rhs) const {
+ return memcmp(this, &rhs, sizeof(PathDescription));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Utilities
+///////////////////////////////////////////////////////////////////////////////
+
+bool PathCache::canDrawAsConvexPath(SkPath* path, SkPaint* paint) {
+ // NOTE: This should only be used after PathTessellator handles joins properly
+ return paint->getPathEffect() == NULL && path->getConvexity() == SkPath::kConvex_Convexity;
+}
+
+void PathCache::computePathBounds(const SkPath* path, const SkPaint* paint,
float& left, float& top, float& offset, uint32_t& width, uint32_t& height) {
const SkRect& bounds = path->getBounds();
- computeBounds(bounds, paint, left, top, offset, width, height);
+ PathCache::computeBounds(bounds, paint, left, top, offset, width, height);
}
-void computeBounds(const SkRect& bounds, const SkPaint* paint,
+void PathCache::computeBounds(const SkRect& bounds, const SkPaint* paint,
float& left, float& top, float& offset, uint32_t& width, uint32_t& height) {
const float pathWidth = fmax(bounds.width(), 1.0f);
const float pathHeight = fmax(bounds.height(), 1.0f);
@@ -46,40 +107,274 @@ void computeBounds(const SkRect& bounds, const SkPaint* paint,
height = uint32_t(pathHeight + offset * 2.0 + 0.5);
}
+static void initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height) {
+ bitmap.setConfig(SkBitmap::kA8_Config, width, height);
+ bitmap.allocPixels();
+ bitmap.eraseColor(0);
+}
+
+static void initPaint(SkPaint& paint) {
+ // Make sure the paint is opaque, color, alpha, filter, etc.
+ // will be applied later when compositing the alpha8 texture
+ paint.setColor(0xff000000);
+ paint.setAlpha(255);
+ paint.setColorFilter(NULL);
+ paint.setMaskFilter(NULL);
+ paint.setShader(NULL);
+ SkXfermode* mode = SkXfermode::Create(SkXfermode::kSrc_Mode);
+ SkSafeUnref(paint.setXfermode(mode));
+}
+
+static void drawPath(const SkPath *path, const SkPaint* paint, SkBitmap& bitmap,
+ float left, float top, float offset, uint32_t width, uint32_t height) {
+ initBitmap(bitmap, width, height);
+
+ SkPaint pathPaint(*paint);
+ initPaint(pathPaint);
+
+ SkCanvas canvas(bitmap);
+ canvas.translate(-left + offset, -top + offset);
+ canvas.drawPath(*path, pathPaint);
+}
+
+static PathTexture* createTexture(float left, float top, float offset,
+ uint32_t width, uint32_t height, uint32_t id) {
+ PathTexture* texture = new PathTexture();
+ texture->left = left;
+ texture->top = top;
+ texture->offset = offset;
+ texture->width = width;
+ texture->height = height;
+ texture->generation = id;
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// 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, NULL) > 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_PATH_CACHE_SIZE);
+ }
+ init();
+}
+
+PathCache::~PathCache() {
+ mCache.clear();
+}
+
+void PathCache::init() {
+ mCache.setOnEntryRemovedListener(this);
+
+ GLint maxTextureSize;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+ mMaxTextureSize = maxTextureSize;
+
+ mDebugEnabled = readDebugLevel() & kDebugCaches;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t PathCache::getSize() {
+ return mSize;
+}
+
+uint32_t PathCache::getMaxSize() {
+ return mMaxSize;
+}
+
+void PathCache::setMaxSize(uint32_t maxSize) {
+ mMaxSize = maxSize;
+ while (mSize > mMaxSize) {
+ mCache.removeOldest();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void PathCache::operator()(PathDescription& entry, PathTexture*& texture) {
+ removeTexture(texture);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+void PathCache::removeTexture(PathTexture* texture) {
+ if (texture) {
+ const uint32_t size = texture->width * texture->height;
+ mSize -= size;
+
+ PATH_LOGD("PathCache::delete name, size, mSize = %d, %d, %d",
+ texture->id, size, mSize);
+ if (mDebugEnabled) {
+ ALOGD("Shape deleted, size = %d", size);
+ }
+
+ if (texture->id) {
+ glDeleteTextures(1, &texture->id);
+ }
+ delete texture;
+ }
+}
+
+void PathCache::purgeCache(uint32_t width, uint32_t height) {
+ const uint32_t size = width * height;
+ // Don't even try to cache a bitmap that's bigger than the cache
+ if (size < mMaxSize) {
+ while (mSize + size > mMaxSize) {
+ mCache.removeOldest();
+ }
+ }
+}
+
+void PathCache::trim() {
+ while (mSize > mMaxSize) {
+ mCache.removeOldest();
+ }
+}
+
+PathTexture* PathCache::addTexture(const PathDescription& entry, const SkPath *path,
+ const SkPaint* paint) {
+ ATRACE_CALL();
+
+ float left, top, offset;
+ uint32_t width, height;
+ computePathBounds(path, paint, left, top, offset, width, height);
+
+ if (!checkTextureSize(width, height)) return NULL;
+
+ purgeCache(width, height);
+
+ SkBitmap bitmap;
+ drawPath(path, paint, bitmap, left, top, offset, width, height);
+
+ PathTexture* texture = createTexture(left, top, offset, width, height,
+ path->getGenerationID());
+ generateTexture(entry, &bitmap, texture);
+
+ return texture;
+}
+
+void PathCache::generateTexture(const PathDescription& entry, SkBitmap* bitmap,
+ PathTexture* texture, bool addToCache) {
+ generateTexture(*bitmap, texture);
+
+ uint32_t size = texture->width * texture->height;
+ if (size < mMaxSize) {
+ mSize += size;
+ PATH_LOGD("PathCache::get/create: name, size, mSize = %d, %d, %d",
+ texture->id, size, mSize);
+ if (mDebugEnabled) {
+ ALOGD("Shape created, size = %d", size);
+ }
+ if (addToCache) {
+ mCache.put(entry, texture);
+ }
+ } else {
+ texture->cleanup = true;
+ }
+}
+
+void PathCache::clear() {
+ mCache.clear();
+}
+
+void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) {
+ SkAutoLockPixels alp(bitmap);
+ if (!bitmap.readyToDraw()) {
+ ALOGE("Cannot generate texture from bitmap");
+ return;
+ }
+
+ glGenTextures(1, &texture->id);
+
+ glBindTexture(GL_TEXTURE_2D, 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->setFilter(GL_LINEAR);
+ texture->setWrap(GL_CLAMP_TO_EDGE);
+}
+
///////////////////////////////////////////////////////////////////////////////
-// Path cache
+// Path precaching
///////////////////////////////////////////////////////////////////////////////
-PathCache::PathCache(): ShapeCache<PathCacheEntry>("path",
- PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE) {
+PathCache::PathProcessor::PathProcessor(Caches& caches):
+ TaskProcessor<SkBitmap*>(&caches.tasks), mMaxTextureSize(caches.maxTextureSize) {
+}
+
+void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
+ sp<PathTask> t = static_cast<PathTask* >(task.get());
+ ATRACE_NAME("pathPrecache");
+
+ float left, top, offset;
+ uint32_t width, height;
+ PathCache::computePathBounds(t->path, t->paint, left, top, offset, width, height);
+
+ PathTexture* texture = t->texture;
+ 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(NULL);
+ }
}
-void PathCache::remove(SkPath* path) {
- // TODO: Linear search...
- Vector<size_t> pathsToRemove;
- for (size_t i = 0; i < mCache.size(); i++) {
- if (mCache.getKeyAt(i).path == path) {
- pathsToRemove.push(i);
- removeTexture(mCache.getValueAt(i));
+///////////////////////////////////////////////////////////////////////////////
+// Paths
+///////////////////////////////////////////////////////////////////////////////
+
+void PathCache::remove(const path_pair_t& pair) {
+ Vector<PathDescription> pathsToRemove;
+ LruCache<PathDescription, PathTexture*>::Iterator i(mCache);
+
+ while (i.next()) {
+ const PathDescription& key = i.key();
+ if (key.type == kShapePath &&
+ (key.shape.path.mPath == pair.getFirst() ||
+ key.shape.path.mPath == pair.getSecond())) {
+ pathsToRemove.push(key);
}
}
- mCache.setOnEntryRemovedListener(NULL);
for (size_t i = 0; i < pathsToRemove.size(); i++) {
- // This will work because pathsToRemove is sorted
- // and because the cache is a sorted keyed vector
- mCache.removeAt(pathsToRemove.itemAt(i) - i);
+ mCache.remove(pathsToRemove.itemAt(i));
}
- mCache.setOnEntryRemovedListener(this);
}
void PathCache::removeDeferred(SkPath* path) {
- Mutex::Autolock _l(mLock);
- mGarbage.push(path);
+ Mutex::Autolock l(mLock);
+ mGarbage.push(path_pair_t(path, const_cast<SkPath*>(path->getSourcePath())));
}
void PathCache::clearGarbage() {
- Mutex::Autolock _l(mLock);
+ Mutex::Autolock l(mLock);
size_t count = mGarbage.size();
for (size_t i = 0; i < count; i++) {
remove(mGarbage.itemAt(i));
@@ -87,23 +382,223 @@ void PathCache::clearGarbage() {
mGarbage.clear();
}
-PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
+/**
+ * To properly handle path mutations at draw time we always make a copy
+ * of paths objects when recording display lists. The source path points
+ * to the path we originally copied the path from. This ensures we use
+ * the original path as a cache key the first time a path is inserted
+ * in the cache. The source path is also used to reclaim garbage when a
+ * Dalvik Path object is collected.
+ */
+static SkPath* getSourcePath(SkPath* path) {
const SkPath* sourcePath = path->getSourcePath();
if (sourcePath && sourcePath->getGenerationID() == path->getGenerationID()) {
- path = const_cast<SkPath*>(sourcePath);
+ return const_cast<SkPath*>(sourcePath);
}
+ return path;
+}
- PathCacheEntry entry(path, paint);
- PathTexture* texture = mCache.get(entry);
+PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
+ path = getSourcePath(path);
- float left, top, offset;
- uint32_t width, height;
+ PathDescription entry(kShapePath, paint);
+ entry.shape.path.mPath = path;
+
+ PathTexture* texture = mCache.get(entry);
if (!texture) {
texture = addTexture(entry, path, paint);
+ } else {
+ // A bitmap is attached to the texture, this means we need to
+ // upload it as a GL texture
+ const sp<Task<SkBitmap*> >& task = texture->task();
+ if (task != NULL) {
+ // But we must first wait for the worker thread to be done
+ // producing the bitmap, so let's wait
+ SkBitmap* bitmap = task->getResult();
+ if (bitmap) {
+ generateTexture(entry, bitmap, texture, false);
+ texture->clearTask();
+ } else {
+ ALOGW("Path too large to be rendered into a texture");
+ texture->clearTask();
+ texture = NULL;
+ mCache.remove(entry);
+ }
+ } else if (path->getGenerationID() != texture->generation) {
+ // The size of the path might have changed so we first
+ // remove the entry from the cache
+ mCache.remove(entry);
+ texture = addTexture(entry, path, paint);
+ }
+ }
+
+ return texture;
+}
+
+void PathCache::precache(SkPath* path, SkPaint* paint) {
+ if (!Caches::getInstance().tasks.canRunTasks()) {
+ return;
+ }
+
+ path = getSourcePath(path);
+
+ PathDescription entry(kShapePath, paint);
+ entry.shape.path.mPath = path;
+
+ PathTexture* texture = mCache.get(entry);
+
+ bool generate = false;
+ if (!texture) {
+ generate = true;
} else if (path->getGenerationID() != texture->generation) {
mCache.remove(entry);
- texture = addTexture(entry, path, paint);
+ generate = true;
+ }
+
+ if (generate) {
+ // It is important to specify the generation ID so we do not
+ // attempt to precache the same path several times
+ texture = createTexture(0.0f, 0.0f, 0.0f, 0, 0, path->getGenerationID());
+ sp<PathTask> task = new PathTask(path, paint, texture);
+ texture->setTask(task);
+
+ // During the precaching phase we insert path texture objects into
+ // the cache that do not point to any GL texture. They are instead
+ // treated as a task for the precaching worker thread. This is why
+ // we do not check the cache limit when inserting these objects.
+ // The conversion into GL texture will happen in get(), when a client
+ // asks for a path texture. This is also when the cache limit will
+ // be enforced.
+ mCache.put(entry, texture);
+
+ if (mProcessor == NULL) {
+ mProcessor = new PathProcessor(Caches::getInstance());
+ }
+ mProcessor->add(task);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Rounded rects
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getRoundRect(float width, float height,
+ float rx, float ry, SkPaint* paint) {
+ PathDescription entry(kShapeRoundRect, paint);
+ entry.shape.roundRect.mWidth = width;
+ entry.shape.roundRect.mHeight = height;
+ entry.shape.roundRect.mRx = rx;
+ entry.shape.roundRect.mRy = ry;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ SkRect r;
+ r.set(0.0f, 0.0f, width, height);
+ path.addRoundRect(r, rx, ry, SkPath::kCW_Direction);
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Circles
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getCircle(float radius, SkPaint* paint) {
+ PathDescription entry(kShapeCircle, paint);
+ entry.shape.circle.mRadius = radius;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ path.addCircle(radius, radius, radius, SkPath::kCW_Direction);
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Ovals
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getOval(float width, float height, SkPaint* paint) {
+ PathDescription entry(kShapeOval, paint);
+ entry.shape.oval.mWidth = width;
+ entry.shape.oval.mHeight = height;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ SkRect r;
+ r.set(0.0f, 0.0f, width, height);
+ path.addOval(r, SkPath::kCW_Direction);
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Rects
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getRect(float width, float height, SkPaint* paint) {
+ PathDescription entry(kShapeRect, paint);
+ entry.shape.rect.mWidth = width;
+ entry.shape.rect.mHeight = height;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ SkRect r;
+ r.set(0.0f, 0.0f, width, height);
+ path.addRect(r, SkPath::kCW_Direction);
+
+ texture = addTexture(entry, &path, paint);
+ }
+
+ return texture;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Arcs
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::getArc(float width, float height,
+ float startAngle, float sweepAngle, bool useCenter, SkPaint* paint) {
+ PathDescription entry(kShapeArc, paint);
+ entry.shape.arc.mWidth = width;
+ entry.shape.arc.mHeight = height;
+ entry.shape.arc.mStartAngle = startAngle;
+ entry.shape.arc.mSweepAngle = sweepAngle;
+ entry.shape.arc.mUseCenter = useCenter;
+
+ PathTexture* texture = get(entry);
+
+ if (!texture) {
+ SkPath path;
+ SkRect r;
+ r.set(0.0f, 0.0f, width, height);
+ if (useCenter) {
+ path.moveTo(r.centerX(), r.centerY());
+ }
+ path.arcTo(r, startAngle, sweepAngle, !useCenter);
+ if (useCenter) {
+ path.close();
+ }
+
+ texture = addTexture(entry, &path, paint);
}
return texture;
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 4904a587f6f3..dd1f9967ca43 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -17,60 +17,204 @@
#ifndef ANDROID_HWUI_PATH_CACHE_H
#define ANDROID_HWUI_PATH_CACHE_H
+#include <GLES2/gl2.h>
+
+#include <utils/LruCache.h>
+#include <utils/Mutex.h>
#include <utils/Vector.h>
#include "Debug.h"
-#include "ShapeCache.h"
+#include "Properties.h"
+#include "Texture.h"
+#include "utils/Pair.h"
-#include "utils/Compare.h"
+class SkBitmap;
+class SkCanvas;
+class SkPaint;
+class SkPath;
+class SkRect;
namespace android {
namespace uirenderer {
+class Caches;
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#if DEBUG_PATHS
+ #define PATH_LOGD(...) ALOGD(__VA_ARGS__)
+#else
+ #define PATH_LOGD(...)
+#endif
+
///////////////////////////////////////////////////////////////////////////////
// Classes
///////////////////////////////////////////////////////////////////////////////
-struct PathCacheEntry: public ShapeCacheEntry {
- PathCacheEntry(SkPath* path, SkPaint* paint):
- ShapeCacheEntry(ShapeCacheEntry::kShapePath, paint) {
- this->path = path;
+/**
+ * Alpha texture used to represent a path.
+ */
+struct PathTexture: public Texture {
+ PathTexture(): Texture() {
}
- PathCacheEntry(): ShapeCacheEntry() {
- path = NULL;
+ ~PathTexture() {
+ clearTask();
}
- bool lessThan(const ShapeCacheEntry& r) const {
- const PathCacheEntry& rhs = (const PathCacheEntry&) r;
- LTE_INT(path) {
- return false;
+ /**
+ * Left coordinate of the path bounds.
+ */
+ float left;
+ /**
+ * Top coordinate of the path bounds.
+ */
+ float top;
+ /**
+ * Offset to draw the path at the correct origin.
+ */
+ float offset;
+
+ sp<Task<SkBitmap*> > task() const {
+ return mTask;
+ }
+
+ void setTask(const sp<Task<SkBitmap*> >& task) {
+ mTask = task;
+ }
+
+ void clearTask() {
+ if (mTask != NULL) {
+ mTask.clear();
}
- return false;
}
- SkPath* path;
+private:
+ sp<Task<SkBitmap*> > mTask;
+}; // struct PathTexture
+
+enum ShapeType {
+ kShapeNone,
+ kShapeRect,
+ kShapeRoundRect,
+ kShapeCircle,
+ kShapeOval,
+ kShapeArc,
+ kShapePath
+};
-}; // PathCacheEntry
+struct PathDescription {
+ ShapeType type;
+ SkPaint::Join join;
+ SkPaint::Cap cap;
+ SkPaint::Style style;
+ float miter;
+ float strokeWidth;
+ SkPathEffect* pathEffect;
+ union Shape {
+ struct Path {
+ SkPath* mPath;
+ } path;
+ struct RoundRect {
+ float mWidth;
+ float mHeight;
+ float mRx;
+ float mRy;
+ } roundRect;
+ struct Circle {
+ float mRadius;
+ } circle;
+ struct Oval {
+ float mWidth;
+ float mHeight;
+ } oval;
+ struct Rect {
+ float mWidth;
+ float mHeight;
+ } rect;
+ struct Arc {
+ float mWidth;
+ float mHeight;
+ float mStartAngle;
+ float mSweepAngle;
+ bool mUseCenter;
+ } arc;
+ } shape;
+
+ PathDescription();
+ PathDescription(ShapeType shapeType, SkPaint* paint);
+
+ hash_t hash() const;
+
+ int compare(const PathDescription& rhs) const;
+
+ bool operator==(const PathDescription& other) const {
+ return compare(other) == 0;
+ }
+
+ bool operator!=(const PathDescription& other) const {
+ return compare(other) != 0;
+ }
+
+ friend inline int strictly_order_type(
+ const PathDescription& lhs, const PathDescription& rhs) {
+ return lhs.compare(rhs) < 0;
+ }
+
+ friend inline int compare_type(const PathDescription& lhs, const PathDescription& rhs) {
+ return lhs.compare(rhs);
+ }
+
+ friend inline hash_t hash_type(const PathDescription& entry) {
+ return entry.hash();
+ }
+};
/**
- * A simple LRU path cache. The cache has a maximum size expressed in bytes.
+ * A simple LRU shape cache. The cache has a maximum size expressed in bytes.
* Any texture added to the cache causing the cache to grow beyond the maximum
* allowed size will also cause the oldest texture to be kicked out.
*/
-class PathCache: public ShapeCache<PathCacheEntry> {
+class PathCache: public OnEntryRemoved<PathDescription, PathTexture*> {
public:
PathCache();
+ ~PathCache();
/**
- * Returns the texture associated with the specified path. If the texture
- * cannot be found in the cache, a new texture is generated.
+ * Used as a callback when an entry is removed from the cache.
+ * Do not invoke directly.
*/
- PathTexture* get(SkPath* path, SkPaint* paint);
+ void operator()(PathDescription& path, PathTexture*& texture);
+
/**
- * Removes an entry.
+ * Clears the cache. This causes all textures 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.
*/
- void remove(SkPath* path);
+ uint32_t getMaxSize();
+ /**
+ * Returns the current size of the cache in bytes.
+ */
+ uint32_t getSize();
+
+ PathTexture* getRoundRect(float width, float height, float rx, float ry, SkPaint* paint);
+ PathTexture* getCircle(float radius, SkPaint* paint);
+ PathTexture* getOval(float width, float height, SkPaint* paint);
+ PathTexture* getRect(float width, float height, SkPaint* paint);
+ PathTexture* getArc(float width, float height, float startAngle, float sweepAngle,
+ bool useCenter, SkPaint* paint);
+ PathTexture* get(SkPath* path, SkPaint* paint);
+
/**
* Removes the specified path. This is meant to be called from threads
* that are not the EGL context thread.
@@ -80,9 +224,108 @@ public:
* Process deferred removals.
*/
void clearGarbage();
+ /**
+ * Trims the contents of the cache, removing items until it's under its
+ * specified limit.
+ *
+ * Trimming is used for caches that support pre-caching from a worker
+ * thread. During pre-caching the maximum limit of the cache can be
+ * exceeded for the duration of the frame. It is therefore required to
+ * trim the cache at the end of the frame to keep the total amount of
+ * memory used under control.
+ */
+ void trim();
+
+ /**
+ * Precaches the specified path using background threads.
+ */
+ void precache(SkPath* path, SkPaint* paint);
+
+ static bool canDrawAsConvexPath(SkPath* path, SkPaint* paint);
+ static void computePathBounds(const SkPath* path, const SkPaint* paint,
+ float& left, float& top, float& offset, uint32_t& width, uint32_t& height);
+ static void computeBounds(const SkRect& bounds, const SkPaint* paint,
+ float& left, float& top, float& offset, uint32_t& width, uint32_t& height);
private:
- Vector<SkPath*> mGarbage;
+ typedef Pair<SkPath*, SkPath*> path_pair_t;
+
+ PathTexture* addTexture(const PathDescription& entry,
+ const SkPath *path, const SkPaint* paint);
+ PathTexture* addTexture(const PathDescription& entry, SkBitmap* bitmap);
+
+ /**
+ * Generates the texture from a bitmap into the specified texture structure.
+ */
+ void generateTexture(SkBitmap& bitmap, Texture* texture);
+ void generateTexture(const PathDescription& entry, SkBitmap* bitmap, PathTexture* texture,
+ bool addToCache = true);
+
+ PathTexture* get(const PathDescription& entry) {
+ return mCache.get(entry);
+ }
+
+ /**
+ * Removes an entry.
+ * The pair must define first=path, second=sourcePath
+ */
+ void remove(const path_pair_t& pair);
+
+ /**
+ * Ensures there is enough space in the cache for a texture of the specified
+ * dimensions.
+ */
+ void purgeCache(uint32_t width, uint32_t height);
+
+ void removeTexture(PathTexture* texture);
+
+ bool checkTextureSize(uint32_t width, uint32_t height) {
+ if (width > mMaxTextureSize || height > mMaxTextureSize) {
+ ALOGW("Shape too large to be rendered into a texture (%dx%d, max=%dx%d)",
+ width, height, mMaxTextureSize, mMaxTextureSize);
+ return false;
+ }
+ return true;
+ }
+
+ void init();
+
+ class PathTask: public Task<SkBitmap*> {
+ public:
+ PathTask(SkPath* path, SkPaint* paint, PathTexture* texture):
+ path(path), paint(paint), texture(texture) {
+ }
+
+ ~PathTask() {
+ delete future()->get();
+ }
+
+ SkPath* path;
+ SkPaint* paint;
+ PathTexture* texture;
+ };
+
+ class PathProcessor: public TaskProcessor<SkBitmap*> {
+ public:
+ PathProcessor(Caches& caches);
+ ~PathProcessor() { }
+
+ virtual void onProcess(const sp<Task<SkBitmap*> >& task);
+
+ private:
+ uint32_t mMaxTextureSize;
+ };
+
+ LruCache<PathDescription, PathTexture*> mCache;
+ uint32_t mSize;
+ uint32_t mMaxSize;
+ GLuint mMaxTextureSize;
+
+ bool mDebugEnabled;
+
+ sp<PathProcessor> mProcessor;
+
+ Vector<path_pair_t> mGarbage;
mutable Mutex mLock;
}; // class PathCache
diff --git a/libs/hwui/PathRenderer.cpp b/libs/hwui/PathRenderer.cpp
deleted file mode 100644
index dd13d7962577..000000000000
--- a/libs/hwui/PathRenderer.cpp
+++ /dev/null
@@ -1,721 +0,0 @@
-/*
- * Copyright (C) 2012 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 "PathRenderer"
-#define LOG_NDEBUG 1
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#define VERTEX_DEBUG 0
-
-#include <SkPath.h>
-#include <SkPaint.h>
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <sys/types.h>
-
-#include <utils/Log.h>
-#include <utils/Trace.h>
-
-#include "PathRenderer.h"
-#include "Matrix.h"
-#include "Vector.h"
-#include "Vertex.h"
-
-namespace android {
-namespace uirenderer {
-
-#define THRESHOLD 0.5f
-
-SkRect PathRenderer::computePathBounds(const SkPath& path, const SkPaint* paint) {
- SkRect bounds = path.getBounds();
- if (paint->getStyle() != SkPaint::kFill_Style) {
- float outset = paint->getStrokeWidth() * 0.5f;
- bounds.outset(outset, outset);
- }
- return bounds;
-}
-
-void computeInverseScales(const mat4 *transform, float &inverseScaleX, float& inverseScaleY) {
- if (CC_UNLIKELY(!transform->isPureTranslate())) {
- float m00 = transform->data[Matrix4::kScaleX];
- float m01 = transform->data[Matrix4::kSkewY];
- float m10 = transform->data[Matrix4::kSkewX];
- float m11 = transform->data[Matrix4::kScaleY];
- float scaleX = sqrt(m00 * m00 + m01 * m01);
- float scaleY = sqrt(m10 * m10 + m11 * m11);
- inverseScaleX = (scaleX != 0) ? (1.0f / scaleX) : 1.0f;
- inverseScaleY = (scaleY != 0) ? (1.0f / scaleY) : 1.0f;
- } else {
- inverseScaleX = 1.0f;
- inverseScaleY = 1.0f;
- }
-}
-
-inline void copyVertex(Vertex* destPtr, const Vertex* srcPtr) {
- Vertex::set(destPtr, srcPtr->position[0], srcPtr->position[1]);
-}
-
-inline void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) {
- AlphaVertex::set(destPtr, srcPtr->position[0], srcPtr->position[1], srcPtr->alpha);
-}
-
-/**
- * Produces a pseudo-normal for a vertex, given the normals of the two incoming lines. If the offset
- * from each vertex in a perimeter is calculated, the resultant lines connecting the offset vertices
- * will be offset by 1.0
- *
- * Note that we can't add and normalize the two vectors, that would result in a rectangle having an
- * offset of (sqrt(2)/2, sqrt(2)/2) at each corner, instead of (1, 1)
- *
- * NOTE: assumes angles between normals 90 degrees or less
- */
-inline vec2 totalOffsetFromNormals(const vec2& normalA, const vec2& normalB) {
- return (normalA + normalB) / (1 + fabs(normalA.dot(normalB)));
-}
-
-inline void scaleOffsetForStrokeWidth(vec2& offset, float halfStrokeWidth,
- float inverseScaleX, float inverseScaleY) {
- if (halfStrokeWidth == 0.0f) {
- // hairline - compensate for scale
- offset.x *= 0.5f * inverseScaleX;
- offset.y *= 0.5f * inverseScaleY;
- } else {
- offset *= halfStrokeWidth;
- }
-}
-
-void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) {
- Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size());
-
- int currentIndex = 0;
- // zig zag between all previous points on the inside of the hull to create a
- // triangle strip that fills the hull
- int srcAindex = 0;
- int srcBindex = perimeter.size() - 1;
- while (srcAindex <= srcBindex) {
- copyVertex(&buffer[currentIndex++], &perimeter[srcAindex]);
- if (srcAindex == srcBindex) break;
- copyVertex(&buffer[currentIndex++], &perimeter[srcBindex]);
- srcAindex++;
- srcBindex--;
- }
-}
-
-void getStrokeVerticesFromPerimeter(const Vector<Vertex>& perimeter, float halfStrokeWidth,
- VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
- Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size() * 2 + 2);
-
- int currentIndex = 0;
- const Vertex* last = &(perimeter[perimeter.size() - 1]);
- const Vertex* current = &(perimeter[0]);
- vec2 lastNormal(current->position[1] - last->position[1],
- last->position[0] - current->position[0]);
- lastNormal.normalize();
- for (unsigned int i = 0; i < perimeter.size(); i++) {
- const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
- vec2 nextNormal(next->position[1] - current->position[1],
- current->position[0] - next->position[0]);
- nextNormal.normalize();
-
- vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
- scaleOffsetForStrokeWidth(totalOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
-
- Vertex::set(&buffer[currentIndex++],
- current->position[0] + totalOffset.x,
- current->position[1] + totalOffset.y);
-
- Vertex::set(&buffer[currentIndex++],
- current->position[0] - totalOffset.x,
- current->position[1] - totalOffset.y);
-
- last = current;
- current = next;
- lastNormal = nextNormal;
- }
-
- // wrap around to beginning
- copyVertex(&buffer[currentIndex++], &buffer[0]);
- copyVertex(&buffer[currentIndex++], &buffer[1]);
-}
-
-void getStrokeVerticesFromUnclosedVertices(const Vector<Vertex>& vertices, float halfStrokeWidth,
- VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
- Vertex* buffer = vertexBuffer.alloc<Vertex>(vertices.size() * 2);
-
- int currentIndex = 0;
- const Vertex* current = &(vertices[0]);
- vec2 lastNormal;
- for (unsigned int i = 0; i < vertices.size() - 1; i++) {
- const Vertex* next = &(vertices[i + 1]);
- vec2 nextNormal(next->position[1] - current->position[1],
- current->position[0] - next->position[0]);
- nextNormal.normalize();
-
- vec2 totalOffset;
- if (i == 0) {
- totalOffset = nextNormal;
- } else {
- totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
- }
- scaleOffsetForStrokeWidth(totalOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
-
- Vertex::set(&buffer[currentIndex++],
- current->position[0] + totalOffset.x,
- current->position[1] + totalOffset.y);
-
- Vertex::set(&buffer[currentIndex++],
- current->position[0] - totalOffset.x,
- current->position[1] - totalOffset.y);
-
- current = next;
- lastNormal = nextNormal;
- }
-
- vec2 totalOffset = lastNormal;
- scaleOffsetForStrokeWidth(totalOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
-
- Vertex::set(&buffer[currentIndex++],
- current->position[0] + totalOffset.x,
- current->position[1] + totalOffset.y);
- Vertex::set(&buffer[currentIndex++],
- current->position[0] - totalOffset.x,
- current->position[1] - totalOffset.y);
-#if VERTEX_DEBUG
- for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
- ALOGD("point at %f %f", buffer[i].position[0], buffer[i].position[1]);
- }
-#endif
-}
-
-void getFillVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer,
- float inverseScaleX, float inverseScaleY) {
- AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2);
-
- // generate alpha points - fill Alpha vertex gaps in between each point with
- // alpha 0 vertex, offset by a scaled normal.
- int currentIndex = 0;
- const Vertex* last = &(perimeter[perimeter.size() - 1]);
- const Vertex* current = &(perimeter[0]);
- vec2 lastNormal(current->position[1] - last->position[1],
- last->position[0] - current->position[0]);
- lastNormal.normalize();
- for (unsigned int i = 0; i < perimeter.size(); i++) {
- const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
- vec2 nextNormal(next->position[1] - current->position[1],
- current->position[0] - next->position[0]);
- nextNormal.normalize();
-
- // AA point offset from original point is that point's normal, such that each side is offset
- // by .5 pixels
- vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
- totalOffset.x *= 0.5f * inverseScaleX;
- totalOffset.y *= 0.5f * inverseScaleY;
-
- AlphaVertex::set(&buffer[currentIndex++],
- current->position[0] + totalOffset.x,
- current->position[1] + totalOffset.y,
- 0.0f);
- AlphaVertex::set(&buffer[currentIndex++],
- current->position[0] - totalOffset.x,
- current->position[1] - totalOffset.y,
- 1.0f);
-
- last = current;
- current = next;
- lastNormal = nextNormal;
- }
-
- // wrap around to beginning
- copyAlphaVertex(&buffer[currentIndex++], &buffer[0]);
- copyAlphaVertex(&buffer[currentIndex++], &buffer[1]);
-
- // zig zag between all previous points on the inside of the hull to create a
- // triangle strip that fills the hull, repeating the first inner point to
- // create degenerate tris to start inside path
- int srcAindex = 0;
- int srcBindex = perimeter.size() - 1;
- while (srcAindex <= srcBindex) {
- copyAlphaVertex(&buffer[currentIndex++], &buffer[srcAindex * 2 + 1]);
- if (srcAindex == srcBindex) break;
- copyAlphaVertex(&buffer[currentIndex++], &buffer[srcBindex * 2 + 1]);
- srcAindex++;
- srcBindex--;
- }
-
-#if VERTEX_DEBUG
- for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
- ALOGD("point at %f %f, alpha %f", buffer[i].position[0], buffer[i].position[1], buffer[i].alpha);
- }
-#endif
-}
-
-
-void getStrokeVerticesFromUnclosedVerticesAA(const Vector<Vertex>& vertices, float halfStrokeWidth,
- VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
- AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * vertices.size() + 2);
-
- // avoid lines smaller than hairline since they break triangle based sampling. instead reducing
- // alpha value (TODO: support different X/Y scale)
- float maxAlpha = 1.0f;
- if (halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
- halfStrokeWidth * inverseScaleX < 0.5f) {
- maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX;
- halfStrokeWidth = 0.0f;
- }
-
- // there is no outer/inner here, using them for consistency with below approach
- int offset = 2 * (vertices.size() - 2);
- int currentAAOuterIndex = 2;
- int currentAAInnerIndex = 2 * offset + 5; // reversed
- int currentStrokeIndex = currentAAInnerIndex + 7;
-
- const Vertex* last = &(vertices[0]);
- const Vertex* current = &(vertices[1]);
- vec2 lastNormal(current->position[1] - last->position[1],
- last->position[0] - current->position[0]);
- lastNormal.normalize();
-
- {
- // start cap
- vec2 totalOffset = lastNormal;
- vec2 AAOffset = totalOffset;
- AAOffset.x *= 0.5f * inverseScaleX;
- AAOffset.y *= 0.5f * inverseScaleY;
-
- vec2 innerOffset = totalOffset;
- scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
- vec2 outerOffset = innerOffset + AAOffset;
- innerOffset -= AAOffset;
-
- // TODO: support square cap by changing this offset to incorporate halfStrokeWidth
- vec2 capAAOffset(AAOffset.y, -AAOffset.x);
- AlphaVertex::set(&buffer[0],
- last->position[0] + outerOffset.x + capAAOffset.x,
- last->position[1] + outerOffset.y + capAAOffset.y,
- 0.0f);
- AlphaVertex::set(&buffer[1],
- last->position[0] + innerOffset.x - capAAOffset.x,
- last->position[1] + innerOffset.y - capAAOffset.y,
- maxAlpha);
-
- AlphaVertex::set(&buffer[2 * offset + 6],
- last->position[0] - outerOffset.x + capAAOffset.x,
- last->position[1] - outerOffset.y + capAAOffset.y,
- 0.0f);
- AlphaVertex::set(&buffer[2 * offset + 7],
- last->position[0] - innerOffset.x - capAAOffset.x,
- last->position[1] - innerOffset.y - capAAOffset.y,
- maxAlpha);
- copyAlphaVertex(&buffer[2 * offset + 8], &buffer[0]);
- copyAlphaVertex(&buffer[2 * offset + 9], &buffer[1]);
- copyAlphaVertex(&buffer[2 * offset + 10], &buffer[1]); // degenerate tris (the only two!)
- copyAlphaVertex(&buffer[2 * offset + 11], &buffer[2 * offset + 7]);
- }
-
- for (unsigned int i = 1; i < vertices.size() - 1; i++) {
- const Vertex* next = &(vertices[i + 1]);
- vec2 nextNormal(next->position[1] - current->position[1],
- current->position[0] - next->position[0]);
- nextNormal.normalize();
-
- vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
- vec2 AAOffset = totalOffset;
- AAOffset.x *= 0.5f * inverseScaleX;
- AAOffset.y *= 0.5f * inverseScaleY;
-
- vec2 innerOffset = totalOffset;
- scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
- vec2 outerOffset = innerOffset + AAOffset;
- innerOffset -= AAOffset;
-
- AlphaVertex::set(&buffer[currentAAOuterIndex++],
- current->position[0] + outerOffset.x,
- current->position[1] + outerOffset.y,
- 0.0f);
- AlphaVertex::set(&buffer[currentAAOuterIndex++],
- current->position[0] + innerOffset.x,
- current->position[1] + innerOffset.y,
- maxAlpha);
-
- AlphaVertex::set(&buffer[currentStrokeIndex++],
- current->position[0] + innerOffset.x,
- current->position[1] + innerOffset.y,
- maxAlpha);
- AlphaVertex::set(&buffer[currentStrokeIndex++],
- current->position[0] - innerOffset.x,
- current->position[1] - innerOffset.y,
- maxAlpha);
-
- AlphaVertex::set(&buffer[currentAAInnerIndex--],
- current->position[0] - innerOffset.x,
- current->position[1] - innerOffset.y,
- maxAlpha);
- AlphaVertex::set(&buffer[currentAAInnerIndex--],
- current->position[0] - outerOffset.x,
- current->position[1] - outerOffset.y,
- 0.0f);
-
- last = current;
- current = next;
- lastNormal = nextNormal;
- }
-
- {
- // end cap
- vec2 totalOffset = lastNormal;
- vec2 AAOffset = totalOffset;
- AAOffset.x *= 0.5f * inverseScaleX;
- AAOffset.y *= 0.5f * inverseScaleY;
-
- vec2 innerOffset = totalOffset;
- scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
- vec2 outerOffset = innerOffset + AAOffset;
- innerOffset -= AAOffset;
-
- // TODO: support square cap by changing this offset to incorporate halfStrokeWidth
- vec2 capAAOffset(-AAOffset.y, AAOffset.x);
-
- AlphaVertex::set(&buffer[offset + 2],
- current->position[0] + outerOffset.x + capAAOffset.x,
- current->position[1] + outerOffset.y + capAAOffset.y,
- 0.0f);
- AlphaVertex::set(&buffer[offset + 3],
- current->position[0] + innerOffset.x - capAAOffset.x,
- current->position[1] + innerOffset.y - capAAOffset.y,
- maxAlpha);
-
- AlphaVertex::set(&buffer[offset + 4],
- current->position[0] - outerOffset.x + capAAOffset.x,
- current->position[1] - outerOffset.y + capAAOffset.y,
- 0.0f);
- AlphaVertex::set(&buffer[offset + 5],
- current->position[0] - innerOffset.x - capAAOffset.x,
- current->position[1] - innerOffset.y - capAAOffset.y,
- maxAlpha);
-
- copyAlphaVertex(&buffer[vertexBuffer.getSize() - 2], &buffer[offset + 3]);
- copyAlphaVertex(&buffer[vertexBuffer.getSize() - 1], &buffer[offset + 5]);
- }
-
-#if VERTEX_DEBUG
- for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
- ALOGD("point at %f %f, alpha %f", buffer[i].position[0], buffer[i].position[1], buffer[i].alpha);
- }
-#endif
-}
-
-
-void getStrokeVerticesFromPerimeterAA(const Vector<Vertex>& perimeter, float halfStrokeWidth,
- VertexBuffer& vertexBuffer, float inverseScaleX, float inverseScaleY) {
- AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * perimeter.size() + 8);
-
- // avoid lines smaller than hairline since they break triangle based sampling. instead reducing
- // alpha value (TODO: support different X/Y scale)
- float maxAlpha = 1.0f;
- if (halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
- halfStrokeWidth * inverseScaleX < 0.5f) {
- maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX;
- halfStrokeWidth = 0.0f;
- }
-
- int offset = 2 * perimeter.size() + 3;
- int currentAAOuterIndex = 0;
- int currentStrokeIndex = offset;
- int currentAAInnerIndex = offset * 2;
-
- const Vertex* last = &(perimeter[perimeter.size() - 1]);
- const Vertex* current = &(perimeter[0]);
- vec2 lastNormal(current->position[1] - last->position[1],
- last->position[0] - current->position[0]);
- lastNormal.normalize();
- for (unsigned int i = 0; i < perimeter.size(); i++) {
- const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
- vec2 nextNormal(next->position[1] - current->position[1],
- current->position[0] - next->position[0]);
- nextNormal.normalize();
-
- vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
- vec2 AAOffset = totalOffset;
- AAOffset.x *= 0.5f * inverseScaleX;
- AAOffset.y *= 0.5f * inverseScaleY;
-
- vec2 innerOffset = totalOffset;
- scaleOffsetForStrokeWidth(innerOffset, halfStrokeWidth, inverseScaleX, inverseScaleY);
- vec2 outerOffset = innerOffset + AAOffset;
- innerOffset -= AAOffset;
-
- AlphaVertex::set(&buffer[currentAAOuterIndex++],
- current->position[0] + outerOffset.x,
- current->position[1] + outerOffset.y,
- 0.0f);
- AlphaVertex::set(&buffer[currentAAOuterIndex++],
- current->position[0] + innerOffset.x,
- current->position[1] + innerOffset.y,
- maxAlpha);
-
- AlphaVertex::set(&buffer[currentStrokeIndex++],
- current->position[0] + innerOffset.x,
- current->position[1] + innerOffset.y,
- maxAlpha);
- AlphaVertex::set(&buffer[currentStrokeIndex++],
- current->position[0] - innerOffset.x,
- current->position[1] - innerOffset.y,
- maxAlpha);
-
- AlphaVertex::set(&buffer[currentAAInnerIndex++],
- current->position[0] - innerOffset.x,
- current->position[1] - innerOffset.y,
- maxAlpha);
- AlphaVertex::set(&buffer[currentAAInnerIndex++],
- current->position[0] - outerOffset.x,
- current->position[1] - outerOffset.y,
- 0.0f);
-
- last = current;
- current = next;
- lastNormal = nextNormal;
- }
-
- // wrap each strip around to beginning, creating degenerate tris to bridge strips
- copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[0]);
- copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]);
- copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]);
-
- copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset]);
- copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]);
- copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]);
-
- copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset]);
- copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset + 1]);
- // don't need to create last degenerate tri
-
-#if VERTEX_DEBUG
- for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) {
- ALOGD("point at %f %f, alpha %f", buffer[i].position[0], buffer[i].position[1], buffer[i].alpha);
- }
-#endif
-}
-
-void PathRenderer::convexPathVertices(const SkPath &path, const SkPaint* paint,
- const mat4 *transform, VertexBuffer& vertexBuffer) {
- ATRACE_CALL();
-
- SkPaint::Style style = paint->getStyle();
- bool isAA = paint->isAntiAlias();
-
- float inverseScaleX, inverseScaleY;
- computeInverseScales(transform, inverseScaleX, inverseScaleY);
-
- Vector<Vertex> tempVertices;
- float threshInvScaleX = inverseScaleX;
- float threshInvScaleY = inverseScaleY;
- if (style == SkPaint::kStroke_Style) {
- // alter the bezier recursion threshold values we calculate in order to compensate for
- // expansion done after the path vertices are found
- SkRect bounds = path.getBounds();
- if (!bounds.isEmpty()) {
- threshInvScaleX *= bounds.width() / (bounds.width() + paint->getStrokeWidth());
- threshInvScaleY *= bounds.height() / (bounds.height() + paint->getStrokeWidth());
- }
- }
-
- // force close if we're filling the path, since fill path expects closed perimeter.
- bool forceClose = style != SkPaint::kStroke_Style;
- bool wasClosed = convexPathPerimeterVertices(path, forceClose, threshInvScaleX * threshInvScaleX,
- threshInvScaleY * threshInvScaleY, tempVertices);
-
- if (!tempVertices.size()) {
- // path was empty, return without allocating vertex buffer
- return;
- }
-
-#if VERTEX_DEBUG
- for (unsigned int i = 0; i < tempVertices.size(); i++) {
- ALOGD("orig path: point at %f %f", tempVertices[i].position[0], tempVertices[i].position[1]);
- }
-#endif
-
- if (style == SkPaint::kStroke_Style) {
- float halfStrokeWidth = paint->getStrokeWidth() * 0.5f;
- if (!isAA) {
- if (wasClosed) {
- getStrokeVerticesFromPerimeter(tempVertices, halfStrokeWidth, vertexBuffer,
- inverseScaleX, inverseScaleY);
- } else {
- getStrokeVerticesFromUnclosedVertices(tempVertices, halfStrokeWidth, vertexBuffer,
- inverseScaleX, inverseScaleY);
- }
-
- } else {
- if (wasClosed) {
- getStrokeVerticesFromPerimeterAA(tempVertices, halfStrokeWidth, vertexBuffer,
- inverseScaleX, inverseScaleY);
- } else {
- getStrokeVerticesFromUnclosedVerticesAA(tempVertices, halfStrokeWidth, vertexBuffer,
- inverseScaleX, inverseScaleY);
- }
- }
- } else {
- // For kStrokeAndFill style, the path should be adjusted externally, as it will be treated as a fill here.
- if (!isAA) {
- getFillVerticesFromPerimeter(tempVertices, vertexBuffer);
- } else {
- getFillVerticesFromPerimeterAA(tempVertices, vertexBuffer, inverseScaleX, inverseScaleY);
- }
- }
-}
-
-
-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);
-}
-
-bool PathRenderer::convexPathPerimeterVertices(const SkPath& path, bool forceClose,
- float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
- ATRACE_CALL();
-
- // TODO: to support joins other than sharp miter, join vertices should be labelled in the
- // perimeter, or resolved into more vertices. Reconsider forceClose-ing in that case.
- SkPath::Iter iter(path, forceClose);
- SkPoint pts[4];
- SkPath::Verb v;
- Vertex* newVertex = 0;
- while (SkPath::kDone_Verb != (v = iter.next(pts))) {
- switch (v) {
- case SkPath::kMove_Verb:
- pushToVector(outputVertices, pts[0].x(), pts[0].y());
- ALOGV("Move to pos %f %f", pts[0].x(), pts[0].y());
- break;
- case SkPath::kClose_Verb:
- ALOGV("Close at pos %f %f", pts[0].x(), pts[0].y());
- 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());
- break;
- case SkPath::kQuad_Verb:
- ALOGV("kQuad_Verb");
- recursiveQuadraticBezierVertices(
- pts[0].x(), pts[0].y(),
- pts[2].x(), pts[2].y(),
- pts[1].x(), pts[1].y(),
- sqrInvScaleX, sqrInvScaleY, outputVertices);
- break;
- case SkPath::kCubic_Verb:
- ALOGV("kCubic_Verb");
- recursiveCubicBezierVertices(
- pts[0].x(), pts[0].y(),
- pts[1].x(), pts[1].y(),
- pts[3].x(), pts[3].y(),
- pts[2].x(), pts[2].y(),
- sqrInvScaleX, sqrInvScaleY, outputVertices);
- break;
- default:
- break;
- }
- }
-
- int size = outputVertices.size();
- if (size >= 2 && outputVertices[0].position[0] == outputVertices[size - 1].position[0] &&
- outputVertices[0].position[1] == outputVertices[size - 1].position[1]) {
- outputVertices.pop();
- return true;
- }
- return false;
-}
-
-void PathRenderer::recursiveCubicBezierVertices(
- float p1x, float p1y, float c1x, float c1y,
- float p2x, float p2y, float c2x, float c2y,
- float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
- float dx = p2x - p1x;
- float dy = p2y - p1y;
- float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx);
- float d2 = fabs((c2x - p2x) * dy - (c2y - p2y) * dx);
- float d = d1 + d2;
-
- // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
-
- if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
- // below thresh, draw line by adding endpoint
- pushToVector(outputVertices, p2x, p2y);
- } else {
- float p1c1x = (p1x + c1x) * 0.5f;
- float p1c1y = (p1y + c1y) * 0.5f;
- float p2c2x = (p2x + c2x) * 0.5f;
- float p2c2y = (p2y + c2y) * 0.5f;
-
- float c1c2x = (c1x + c2x) * 0.5f;
- float c1c2y = (c1y + c2y) * 0.5f;
-
- float p1c1c2x = (p1c1x + c1c2x) * 0.5f;
- float p1c1c2y = (p1c1y + c1c2y) * 0.5f;
-
- float p2c1c2x = (p2c2x + c1c2x) * 0.5f;
- float p2c1c2y = (p2c2y + c1c2y) * 0.5f;
-
- float mx = (p1c1c2x + p2c1c2x) * 0.5f;
- float my = (p1c1c2y + p2c1c2y) * 0.5f;
-
- recursiveCubicBezierVertices(
- p1x, p1y, p1c1x, p1c1y,
- mx, my, p1c1c2x, p1c1c2y,
- sqrInvScaleX, sqrInvScaleY, outputVertices);
- recursiveCubicBezierVertices(
- mx, my, p2c1c2x, p2c1c2y,
- p2x, p2y, p2c2x, p2c2y,
- sqrInvScaleX, sqrInvScaleY, outputVertices);
- }
-}
-
-void PathRenderer::recursiveQuadraticBezierVertices(
- float ax, float ay,
- float bx, float by,
- float cx, float cy,
- float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
- float dx = bx - ax;
- float dy = by - ay;
- float d = (cx - bx) * dy - (cy - by) * dx;
-
- if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
- // below thresh, draw line by adding endpoint
- pushToVector(outputVertices, bx, by);
- } else {
- float acx = (ax + cx) * 0.5f;
- float bcx = (bx + cx) * 0.5f;
- float acy = (ay + cy) * 0.5f;
- float bcy = (by + cy) * 0.5f;
-
- // midpoint
- float mx = (acx + bcx) * 0.5f;
- float my = (acy + bcy) * 0.5f;
-
- recursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy,
- sqrInvScaleX, sqrInvScaleY, outputVertices);
- recursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy,
- sqrInvScaleX, sqrInvScaleY, outputVertices);
- }
-}
-
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp
new file mode 100644
index 000000000000..0879b1b240ed
--- /dev/null
+++ b/libs/hwui/PathTessellator.cpp
@@ -0,0 +1,971 @@
+/*
+ * Copyright (C) 2012 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 "PathTessellator"
+#define LOG_NDEBUG 1
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#define VERTEX_DEBUG 0
+
+#if VERTEX_DEBUG
+#define DEBUG_DUMP_ALPHA_BUFFER() \
+ for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) { \
+ ALOGD("point %d at %f %f, alpha %f", \
+ i, buffer[i].position[0], buffer[i].position[1], buffer[i].alpha); \
+ }
+#define DEBUG_DUMP_BUFFER() \
+ for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) { \
+ ALOGD("point %d at %f %f", i, buffer[i].position[0], buffer[i].position[1]); \
+ }
+#else
+#define DEBUG_DUMP_ALPHA_BUFFER()
+#define DEBUG_DUMP_BUFFER()
+#endif
+
+#include <SkPath.h>
+#include <SkPaint.h>
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Log.h>
+#include <utils/Trace.h>
+
+#include "PathTessellator.h"
+#include "Matrix.h"
+#include "Vector.h"
+#include "Vertex.h"
+
+namespace android {
+namespace uirenderer {
+
+#define THRESHOLD 0.5f
+#define ROUND_CAP_THRESH 0.25f
+#define PI 3.1415926535897932f
+
+void PathTessellator::expandBoundsForStroke(SkRect& bounds, const SkPaint* paint,
+ bool forceExpand) {
+ if (forceExpand || paint->getStyle() != SkPaint::kFill_Style) {
+ float outset = paint->getStrokeWidth() * 0.5f;
+ if (outset == 0) outset = 0.5f; // account for hairline
+ bounds.outset(outset, outset);
+ }
+}
+
+inline void copyVertex(Vertex* destPtr, const Vertex* srcPtr) {
+ Vertex::set(destPtr, srcPtr->position[0], srcPtr->position[1]);
+}
+
+inline void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) {
+ AlphaVertex::set(destPtr, srcPtr->position[0], srcPtr->position[1], srcPtr->alpha);
+}
+
+/**
+ * Produces a pseudo-normal for a vertex, given the normals of the two incoming lines. If the offset
+ * from each vertex in a perimeter is calculated, the resultant lines connecting the offset vertices
+ * will be offset by 1.0
+ *
+ * Note that we can't add and normalize the two vectors, that would result in a rectangle having an
+ * offset of (sqrt(2)/2, sqrt(2)/2) at each corner, instead of (1, 1)
+ *
+ * NOTE: assumes angles between normals 90 degrees or less
+ */
+inline vec2 totalOffsetFromNormals(const vec2& normalA, const vec2& normalB) {
+ return (normalA + normalB) / (1 + fabs(normalA.dot(normalB)));
+}
+
+/**
+ * Structure used for storing useful information about the SkPaint and scale used for tessellating
+ */
+struct PaintInfo {
+public:
+ PaintInfo(const SkPaint* paint, const mat4 *transform) :
+ style(paint->getStyle()), cap(paint->getStrokeCap()), isAA(paint->isAntiAlias()),
+ inverseScaleX(1.0f), inverseScaleY(1.0f),
+ halfStrokeWidth(paint->getStrokeWidth() * 0.5f), maxAlpha(1.0f) {
+ // compute inverse scales
+ if (CC_UNLIKELY(!transform->isPureTranslate())) {
+ float m00 = transform->data[Matrix4::kScaleX];
+ float m01 = transform->data[Matrix4::kSkewY];
+ float m10 = transform->data[Matrix4::kSkewX];
+ float m11 = transform->data[Matrix4::kScaleY];
+ float scaleX = sqrt(m00 * m00 + m01 * m01);
+ float scaleY = sqrt(m10 * m10 + m11 * m11);
+ inverseScaleX = (scaleX != 0) ? (1.0f / scaleX) : 1.0f;
+ inverseScaleY = (scaleY != 0) ? (1.0f / scaleY) : 1.0f;
+ }
+
+ if (isAA && halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
+ 2 * halfStrokeWidth < inverseScaleX) {
+ maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX;
+ halfStrokeWidth = 0.0f;
+ }
+ }
+
+ SkPaint::Style style;
+ SkPaint::Cap cap;
+ bool isAA;
+ float inverseScaleX;
+ float inverseScaleY;
+ float halfStrokeWidth;
+ float maxAlpha;
+
+ inline void scaleOffsetForStrokeWidth(vec2& offset) const {
+ if (halfStrokeWidth == 0.0f) {
+ // hairline - compensate for scale
+ offset.x *= 0.5f * inverseScaleX;
+ offset.y *= 0.5f * inverseScaleY;
+ } else {
+ offset *= halfStrokeWidth;
+ }
+ }
+
+ /**
+ * NOTE: the input will not always be a normal, especially for sharp edges - it should be the
+ * result of totalOffsetFromNormals (see documentation there)
+ */
+ inline vec2 deriveAAOffset(const vec2& offset) const {
+ return vec2(offset.x * 0.5f * inverseScaleX,
+ offset.y * 0.5f * inverseScaleY);
+ }
+
+ /**
+ * Returns the number of cap divisions beyond the minimum 2 (kButt_Cap/kSquareCap will return 0)
+ * Should only be used when stroking and drawing caps
+ */
+ inline int capExtraDivisions() const {
+ if (cap == SkPaint::kRound_Cap) {
+ if (halfStrokeWidth == 0.0f) return 2;
+
+ // ROUND_CAP_THRESH is the maximum error for polygonal approximation of the round cap
+ const float errConst = (-ROUND_CAP_THRESH / halfStrokeWidth + 1);
+ const float targetCosVal = 2 * errConst * errConst - 1;
+ int neededDivisions = (int)(ceilf(PI / acos(targetCosVal)/2)) * 2;
+ return neededDivisions;
+ }
+ return 0;
+ }
+};
+
+void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) {
+ Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size());
+
+ int currentIndex = 0;
+ // zig zag between all previous points on the inside of the hull to create a
+ // triangle strip that fills the hull
+ int srcAindex = 0;
+ int srcBindex = perimeter.size() - 1;
+ while (srcAindex <= srcBindex) {
+ copyVertex(&buffer[currentIndex++], &perimeter[srcAindex]);
+ if (srcAindex == srcBindex) break;
+ copyVertex(&buffer[currentIndex++], &perimeter[srcBindex]);
+ srcAindex++;
+ srcBindex--;
+ }
+}
+
+/*
+ * Fills a vertexBuffer with non-alpha vertices, zig-zagging at each perimeter point to create a
+ * tri-strip as wide as the stroke.
+ *
+ * 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) {
+ Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size() * 2 + 2);
+
+ int currentIndex = 0;
+ const Vertex* last = &(perimeter[perimeter.size() - 1]);
+ const Vertex* current = &(perimeter[0]);
+ vec2 lastNormal(current->position[1] - last->position[1],
+ last->position[0] - current->position[0]);
+ lastNormal.normalize();
+ for (unsigned int i = 0; i < perimeter.size(); i++) {
+ const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
+ vec2 nextNormal(next->position[1] - current->position[1],
+ current->position[0] - next->position[0]);
+ nextNormal.normalize();
+
+ vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ paintInfo.scaleOffsetForStrokeWidth(totalOffset);
+
+ Vertex::set(&buffer[currentIndex++],
+ current->position[0] + totalOffset.x,
+ current->position[1] + totalOffset.y);
+
+ Vertex::set(&buffer[currentIndex++],
+ current->position[0] - totalOffset.x,
+ current->position[1] - totalOffset.y);
+
+ last = current;
+ current = next;
+ lastNormal = nextNormal;
+ }
+
+ // wrap around to beginning
+ copyVertex(&buffer[currentIndex++], &buffer[0]);
+ copyVertex(&buffer[currentIndex++], &buffer[1]);
+
+ DEBUG_DUMP_BUFFER();
+}
+
+/**
+ * Fills a vertexBuffer with non-alpha vertices similar to getStrokeVerticesFromPerimeter, except:
+ *
+ * 1 - Doesn't need to wrap around, since the input vertices are unclosed
+ *
+ * 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 int extra = paintInfo.capExtraDivisions();
+ const int allocSize = (vertices.size() + extra) * 2;
+
+ Vertex* buffer = vertexBuffer.alloc<Vertex>(allocSize);
+
+ if (extra > 0) {
+ // tessellate both round caps
+ const int last = vertices.size() - 1;
+ float beginTheta = atan2(
+ - (vertices[0].position[0] - vertices[1].position[0]),
+ vertices[0].position[1] - vertices[1].position[1]);
+ float endTheta = atan2(
+ - (vertices[last].position[0] - vertices[last - 1].position[0]),
+ vertices[last].position[1] - vertices[last - 1].position[1]);
+
+ const float dTheta = PI / (extra + 1);
+ const float radialScale = 2.0f / (1 + cos(dTheta));
+
+ int capOffset;
+ for (int i = 0; i < extra; i++) {
+ if (i < extra / 2) {
+ capOffset = extra - 2 * i - 1;
+ } else {
+ capOffset = 2 * i - extra;
+ }
+
+ beginTheta += dTheta;
+ vec2 beginRadialOffset(cos(beginTheta), sin(beginTheta));
+ paintInfo.scaleOffsetForStrokeWidth(beginRadialOffset);
+ Vertex::set(&buffer[capOffset],
+ vertices[0].position[0] + beginRadialOffset.x,
+ vertices[0].position[1] + beginRadialOffset.y);
+
+ endTheta += dTheta;
+ vec2 endRadialOffset(cos(endTheta), sin(endTheta));
+ paintInfo.scaleOffsetForStrokeWidth(endRadialOffset);
+ Vertex::set(&buffer[allocSize - 1 - capOffset],
+ vertices[last].position[0] + endRadialOffset.x,
+ vertices[last].position[1] + endRadialOffset.y);
+ }
+ }
+
+ int currentIndex = extra;
+ const Vertex* current = &(vertices[0]);
+ vec2 lastNormal;
+ for (unsigned int i = 0; i < vertices.size() - 1; i++) {
+ const Vertex* next = &(vertices[i + 1]);
+ vec2 nextNormal(next->position[1] - current->position[1],
+ current->position[0] - next->position[0]);
+ nextNormal.normalize();
+
+ vec2 totalOffset;
+ if (i == 0) {
+ totalOffset = nextNormal;
+ } else {
+ totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ }
+ paintInfo.scaleOffsetForStrokeWidth(totalOffset);
+
+ Vertex::set(&buffer[currentIndex++],
+ current->position[0] + totalOffset.x,
+ current->position[1] + totalOffset.y);
+
+ Vertex::set(&buffer[currentIndex++],
+ current->position[0] - totalOffset.x,
+ current->position[1] - totalOffset.y);
+
+ current = next;
+ lastNormal = nextNormal;
+ }
+
+ vec2 totalOffset = lastNormal;
+ paintInfo.scaleOffsetForStrokeWidth(totalOffset);
+
+ Vertex::set(&buffer[currentIndex++],
+ current->position[0] + totalOffset.x,
+ current->position[1] + totalOffset.y);
+ Vertex::set(&buffer[currentIndex++],
+ current->position[0] - totalOffset.x,
+ current->position[1] - totalOffset.y);
+
+ DEBUG_DUMP_BUFFER();
+}
+
+/**
+ * Populates a vertexBuffer with AlphaVertices to create an anti-aliased fill shape tessellation
+ *
+ * 1 - create the AA perimeter of unit width, by zig-zagging at each point around the perimeter of
+ * the shape (using 2 * perimeter.size() vertices)
+ *
+ * 2 - wrap around to the beginning to complete the perimeter (2 vertices)
+ *
+ * 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) {
+ AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2);
+
+ // generate alpha points - fill Alpha vertex gaps in between each point with
+ // alpha 0 vertex, offset by a scaled normal.
+ int currentIndex = 0;
+ const Vertex* last = &(perimeter[perimeter.size() - 1]);
+ const Vertex* current = &(perimeter[0]);
+ vec2 lastNormal(current->position[1] - last->position[1],
+ last->position[0] - current->position[0]);
+ lastNormal.normalize();
+ for (unsigned int i = 0; i < perimeter.size(); i++) {
+ const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
+ vec2 nextNormal(next->position[1] - current->position[1],
+ current->position[0] - next->position[0]);
+ nextNormal.normalize();
+
+ // AA point offset from original point is that point's normal, such that each side is offset
+ // by .5 pixels
+ vec2 totalOffset = paintInfo.deriveAAOffset(totalOffsetFromNormals(lastNormal, nextNormal));
+
+ AlphaVertex::set(&buffer[currentIndex++],
+ current->position[0] + totalOffset.x,
+ current->position[1] + totalOffset.y,
+ 0.0f);
+ AlphaVertex::set(&buffer[currentIndex++],
+ current->position[0] - totalOffset.x,
+ current->position[1] - totalOffset.y,
+ 1.0f);
+
+ last = current;
+ current = next;
+ lastNormal = nextNormal;
+ }
+
+ // wrap around to beginning
+ copyAlphaVertex(&buffer[currentIndex++], &buffer[0]);
+ copyAlphaVertex(&buffer[currentIndex++], &buffer[1]);
+
+ // zig zag between all previous points on the inside of the hull to create a
+ // triangle strip that fills the hull, repeating the first inner point to
+ // create degenerate tris to start inside path
+ int srcAindex = 0;
+ int srcBindex = perimeter.size() - 1;
+ while (srcAindex <= srcBindex) {
+ copyAlphaVertex(&buffer[currentIndex++], &buffer[srcAindex * 2 + 1]);
+ if (srcAindex == srcBindex) break;
+ copyAlphaVertex(&buffer[currentIndex++], &buffer[srcBindex * 2 + 1]);
+ srcAindex++;
+ srcBindex--;
+ }
+
+ DEBUG_DUMP_BUFFER();
+}
+
+/**
+ * Stores geometry for a single, AA-perimeter (potentially rounded) cap
+ *
+ * For explanation of constants and general methodoloyg, see comments for
+ * getStrokeVerticesFromUnclosedVerticesAA() below.
+ */
+inline void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>& vertices,
+ AlphaVertex* buffer, bool isFirst, vec2 normal, int offset) {
+ const int extra = paintInfo.capExtraDivisions();
+ const int extraOffset = (extra + 1) / 2;
+ const int capIndex = isFirst
+ ? 2 * offset + 6 + 2 * (extra + extraOffset)
+ : offset + 2 + 2 * extraOffset;
+ if (isFirst) normal *= -1;
+
+ // TODO: this normal should be scaled by radialScale if extra != 0, see totalOffsetFromNormals()
+ vec2 AAOffset = paintInfo.deriveAAOffset(normal);
+
+ vec2 strokeOffset = normal;
+ paintInfo.scaleOffsetForStrokeWidth(strokeOffset);
+ vec2 outerOffset = strokeOffset + AAOffset;
+ vec2 innerOffset = strokeOffset - AAOffset;
+
+ vec2 capAAOffset;
+ if (paintInfo.cap != SkPaint::kRound_Cap) {
+ // if the cap is square or butt, the inside primary cap vertices will be inset in two
+ // directions - both normal to the stroke, and parallel to it.
+ capAAOffset = vec2(-AAOffset.y, AAOffset.x);
+ }
+
+ // determine referencePoint, the center point for the 4 primary cap vertices
+ const Vertex* point = isFirst ? vertices.begin() : (vertices.end() - 1);
+ vec2 referencePoint(point->position[0], point->position[1]);
+ 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)
+ referencePoint += vec2(-strokeOffset.y, strokeOffset.x);
+ }
+
+ AlphaVertex::set(&buffer[capIndex + 0],
+ referencePoint.x + outerOffset.x + capAAOffset.x,
+ referencePoint.y + outerOffset.y + capAAOffset.y,
+ 0.0f);
+ AlphaVertex::set(&buffer[capIndex + 1],
+ referencePoint.x + innerOffset.x - capAAOffset.x,
+ referencePoint.y + innerOffset.y - capAAOffset.y,
+ paintInfo.maxAlpha);
+
+ bool isRound = paintInfo.cap == SkPaint::kRound_Cap;
+
+ const int postCapIndex = (isRound && isFirst) ? (2 * extraOffset - 2) : capIndex + (2 * extra);
+ AlphaVertex::set(&buffer[postCapIndex + 2],
+ referencePoint.x - outerOffset.x + capAAOffset.x,
+ referencePoint.y - outerOffset.y + capAAOffset.y,
+ 0.0f);
+ AlphaVertex::set(&buffer[postCapIndex + 3],
+ referencePoint.x - innerOffset.x - capAAOffset.x,
+ referencePoint.y - innerOffset.y - capAAOffset.y,
+ paintInfo.maxAlpha);
+
+ if (isRound) {
+ const float dTheta = PI / (extra + 1);
+ const float radialScale = 2.0f / (1 + cos(dTheta));
+ float theta = atan2(normal.y, normal.x);
+ int capPerimIndex = capIndex + 2;
+
+ for (int i = 0; i < extra; i++) {
+ theta += dTheta;
+
+ vec2 radialOffset(cos(theta), sin(theta));
+
+ // scale to compensate for pinching at sharp angles, see totalOffsetFromNormals()
+ radialOffset *= radialScale;
+
+ AAOffset = paintInfo.deriveAAOffset(radialOffset);
+ paintInfo.scaleOffsetForStrokeWidth(radialOffset);
+ AlphaVertex::set(&buffer[capPerimIndex++],
+ referencePoint.x + radialOffset.x + AAOffset.x,
+ referencePoint.y + radialOffset.y + AAOffset.y,
+ 0.0f);
+ AlphaVertex::set(&buffer[capPerimIndex++],
+ referencePoint.x + radialOffset.x - AAOffset.x,
+ referencePoint.y + radialOffset.y - AAOffset.y,
+ paintInfo.maxAlpha);
+
+ if (isFirst && i == extra - extraOffset) {
+ //copy most recent two points to first two points
+ copyAlphaVertex(&buffer[0], &buffer[capPerimIndex - 2]);
+ copyAlphaVertex(&buffer[1], &buffer[capPerimIndex - 1]);
+
+ capPerimIndex = 2; // start writing the rest of the round cap at index 2
+ }
+ }
+
+ if (isFirst) {
+ const int startCapFillIndex = capIndex + 2 * (extra - extraOffset) + 4;
+ int capFillIndex = startCapFillIndex;
+ for (int i = 0; i < extra + 2; i += 2) {
+ copyAlphaVertex(&buffer[capFillIndex++], &buffer[1 + i]);
+ // TODO: to support odd numbers of divisions, break here on the last iteration
+ copyAlphaVertex(&buffer[capFillIndex++], &buffer[startCapFillIndex - 3 - i]);
+ }
+ } else {
+ int capFillIndex = 6 * vertices.size() + 2 + 6 * extra - (extra + 2);
+ for (int i = 0; i < extra + 2; i += 2) {
+ copyAlphaVertex(&buffer[capFillIndex++], &buffer[capIndex + 1 + i]);
+ // TODO: to support odd numbers of divisions, break here on the last iteration
+ copyAlphaVertex(&buffer[capFillIndex++], &buffer[capIndex + 3 + 2 * extra - i]);
+ }
+ }
+ return;
+ }
+ if (isFirst) {
+ copyAlphaVertex(&buffer[0], &buffer[postCapIndex + 2]);
+ copyAlphaVertex(&buffer[1], &buffer[postCapIndex + 3]);
+ copyAlphaVertex(&buffer[postCapIndex + 4], &buffer[1]); // degenerate tris (the only two!)
+ copyAlphaVertex(&buffer[postCapIndex + 5], &buffer[postCapIndex + 1]);
+ } else {
+ copyAlphaVertex(&buffer[6 * vertices.size()], &buffer[postCapIndex + 1]);
+ copyAlphaVertex(&buffer[6 * vertices.size() + 1], &buffer[postCapIndex + 3]);
+ }
+}
+
+/*
+the geometry for an aa, capped stroke consists of the following:
+
+ # vertices | function
+----------------------------------------------------------------------
+a) 2 | Start AA perimeter
+b) 2, 2 * roundDivOff | First half of begin cap's perimeter
+ |
+ 2 * middlePts | 'Outer' or 'Top' AA perimeter half (between caps)
+ |
+a) 4 | End cap's
+b) 2, 2 * roundDivs, 2 | AA perimeter
+ |
+ 2 * middlePts | 'Inner' or 'bottom' AA perimeter half
+ |
+a) 6 | Begin cap's perimeter
+b) 2, 2*(rD - rDO + 1), | Last half of begin cap's perimeter
+ roundDivs, 2 |
+ |
+ 2 * middlePts | Stroke's full opacity center strip
+ |
+a) 2 | end stroke
+b) 2, roundDivs | (and end cap fill, for round)
+
+Notes:
+* rows starting with 'a)' denote the Butt or Square cap vertex use, 'b)' denote Round
+
+* 'middlePts' is (number of points in the unclosed input vertex list, minus 2) times two
+
+* 'roundDivs' or 'rD' is the number of extra vertices (beyond the minimum of 2) that define the
+ round cap's shape, and is at least two. This will increase with cap size to sufficiently
+ define the cap's level of tessellation.
+
+* 'roundDivOffset' or 'rDO' is the point about halfway along the start cap's round perimeter, where
+ the stream of vertices for the AA perimeter starts. By starting and ending the perimeter at
+ this offset, the fill of the stroke is drawn from this point with minimal extra vertices.
+
+This means the outer perimeter starts at:
+ outerIndex = (2) OR (2 + 2 * roundDivOff)
+the inner perimeter (since it is filled in reverse) starts at:
+ innerIndex = outerIndex + (4 * middlePts) + ((4) OR (4 + 2 * roundDivs)) - 1
+the stroke starts at:
+ strokeIndex = innerIndex + 1 + ((6) OR (6 + 3 * roundDivs - 2 * roundDivOffset))
+
+The total needed allocated space is either:
+ 2 + 4 + 6 + 2 + 3 * (2 * middlePts) = 14 + 6 * middlePts = 2 + 6 * pts
+or, for rounded caps:
+ (2 + 2 * rDO) + (4 + 2 * rD) + (2 * (rD - rDO + 1)
+ + roundDivs + 4) + (2 + roundDivs) + 3 * (2 * middlePts)
+ = 14 + 6 * middlePts + 6 * roundDivs
+ = 2 + 6 * pts + 6 * roundDivs
+ */
+void getStrokeVerticesFromUnclosedVerticesAA(const PaintInfo& paintInfo,
+ const Vector<Vertex>& vertices, VertexBuffer& vertexBuffer) {
+
+ const int extra = paintInfo.capExtraDivisions();
+ const int allocSize = 6 * vertices.size() + 2 + 6 * extra;
+
+ AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(allocSize);
+
+ const int extraOffset = (extra + 1) / 2;
+ int offset = 2 * (vertices.size() - 2);
+ // there is no outer/inner here, using them for consistency with below approach
+ int currentAAOuterIndex = 2 + 2 * extraOffset;
+ int currentAAInnerIndex = currentAAOuterIndex + (2 * offset) + 3 + (2 * extra);
+ int currentStrokeIndex = currentAAInnerIndex + 7 + (3 * extra - 2 * extraOffset);
+
+ const Vertex* last = &(vertices[0]);
+ const Vertex* current = &(vertices[1]);
+ vec2 lastNormal(current->position[1] - last->position[1],
+ last->position[0] - current->position[0]);
+ lastNormal.normalize();
+
+ // TODO: use normal from bezier traversal for cap, instead of from vertices
+ storeCapAA(paintInfo, vertices, buffer, true, lastNormal, offset);
+
+ for (unsigned int i = 1; i < vertices.size() - 1; i++) {
+ const Vertex* next = &(vertices[i + 1]);
+ vec2 nextNormal(next->position[1] - current->position[1],
+ current->position[0] - next->position[0]);
+ nextNormal.normalize();
+
+ vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ vec2 AAOffset = paintInfo.deriveAAOffset(totalOffset);
+
+ vec2 innerOffset = totalOffset;
+ paintInfo.scaleOffsetForStrokeWidth(innerOffset);
+ vec2 outerOffset = innerOffset + AAOffset;
+ innerOffset -= AAOffset;
+
+ AlphaVertex::set(&buffer[currentAAOuterIndex++],
+ current->position[0] + outerOffset.x,
+ current->position[1] + outerOffset.y,
+ 0.0f);
+ AlphaVertex::set(&buffer[currentAAOuterIndex++],
+ current->position[0] + innerOffset.x,
+ current->position[1] + innerOffset.y,
+ paintInfo.maxAlpha);
+
+ AlphaVertex::set(&buffer[currentStrokeIndex++],
+ current->position[0] + innerOffset.x,
+ current->position[1] + innerOffset.y,
+ paintInfo.maxAlpha);
+ AlphaVertex::set(&buffer[currentStrokeIndex++],
+ current->position[0] - innerOffset.x,
+ current->position[1] - innerOffset.y,
+ paintInfo.maxAlpha);
+
+ AlphaVertex::set(&buffer[currentAAInnerIndex--],
+ current->position[0] - innerOffset.x,
+ current->position[1] - innerOffset.y,
+ paintInfo.maxAlpha);
+ AlphaVertex::set(&buffer[currentAAInnerIndex--],
+ current->position[0] - outerOffset.x,
+ current->position[1] - outerOffset.y,
+ 0.0f);
+
+ current = next;
+ lastNormal = nextNormal;
+ }
+
+ // TODO: use normal from bezier traversal for cap, instead of from vertices
+ storeCapAA(paintInfo, vertices, buffer, false, lastNormal, offset);
+
+ DEBUG_DUMP_ALPHA_BUFFER();
+}
+
+
+void getStrokeVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Vertex>& perimeter,
+ VertexBuffer& vertexBuffer) {
+ AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * perimeter.size() + 8);
+
+ int offset = 2 * perimeter.size() + 3;
+ int currentAAOuterIndex = 0;
+ int currentStrokeIndex = offset;
+ int currentAAInnerIndex = offset * 2;
+
+ const Vertex* last = &(perimeter[perimeter.size() - 1]);
+ const Vertex* current = &(perimeter[0]);
+ vec2 lastNormal(current->position[1] - last->position[1],
+ last->position[0] - current->position[0]);
+ lastNormal.normalize();
+ for (unsigned int i = 0; i < perimeter.size(); i++) {
+ const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
+ vec2 nextNormal(next->position[1] - current->position[1],
+ current->position[0] - next->position[0]);
+ nextNormal.normalize();
+
+ vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ vec2 AAOffset = paintInfo.deriveAAOffset(totalOffset);
+
+ vec2 innerOffset = totalOffset;
+ paintInfo.scaleOffsetForStrokeWidth(innerOffset);
+ vec2 outerOffset = innerOffset + AAOffset;
+ innerOffset -= AAOffset;
+
+ AlphaVertex::set(&buffer[currentAAOuterIndex++],
+ current->position[0] + outerOffset.x,
+ current->position[1] + outerOffset.y,
+ 0.0f);
+ AlphaVertex::set(&buffer[currentAAOuterIndex++],
+ current->position[0] + innerOffset.x,
+ current->position[1] + innerOffset.y,
+ paintInfo.maxAlpha);
+
+ AlphaVertex::set(&buffer[currentStrokeIndex++],
+ current->position[0] + innerOffset.x,
+ current->position[1] + innerOffset.y,
+ paintInfo.maxAlpha);
+ AlphaVertex::set(&buffer[currentStrokeIndex++],
+ current->position[0] - innerOffset.x,
+ current->position[1] - innerOffset.y,
+ paintInfo.maxAlpha);
+
+ AlphaVertex::set(&buffer[currentAAInnerIndex++],
+ current->position[0] - innerOffset.x,
+ current->position[1] - innerOffset.y,
+ paintInfo.maxAlpha);
+ AlphaVertex::set(&buffer[currentAAInnerIndex++],
+ current->position[0] - outerOffset.x,
+ current->position[1] - outerOffset.y,
+ 0.0f);
+
+ last = current;
+ current = next;
+ lastNormal = nextNormal;
+ }
+
+ // wrap each strip around to beginning, creating degenerate tris to bridge strips
+ copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[0]);
+ copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]);
+ copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]);
+
+ copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset]);
+ copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]);
+ copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]);
+
+ copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset]);
+ copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset + 1]);
+ // don't need to create last degenerate tri
+
+ DEBUG_DUMP_ALPHA_BUFFER();
+}
+
+void PathTessellator::tessellatePath(const SkPath &path, const SkPaint* paint,
+ const mat4 *transform, VertexBuffer& vertexBuffer) {
+ ATRACE_CALL();
+
+ const PaintInfo paintInfo(paint, transform);
+
+ Vector<Vertex> tempVertices;
+ float threshInvScaleX = paintInfo.inverseScaleX;
+ float threshInvScaleY = paintInfo.inverseScaleY;
+ if (paintInfo.style == SkPaint::kStroke_Style) {
+ // alter the bezier recursion threshold values we calculate in order to compensate for
+ // expansion done after the path vertices are found
+ SkRect bounds = path.getBounds();
+ if (!bounds.isEmpty()) {
+ threshInvScaleX *= bounds.width() / (bounds.width() + paint->getStrokeWidth());
+ threshInvScaleY *= bounds.height() / (bounds.height() + paint->getStrokeWidth());
+ }
+ }
+
+ // force close if we're filling the path, since fill path expects closed perimeter.
+ bool forceClose = paintInfo.style != SkPaint::kStroke_Style;
+ bool wasClosed = approximatePathOutlineVertices(path, forceClose,
+ threshInvScaleX * threshInvScaleX, threshInvScaleY * threshInvScaleY, tempVertices);
+
+ if (!tempVertices.size()) {
+ // path was empty, return without allocating vertex buffer
+ return;
+ }
+
+#if VERTEX_DEBUG
+ for (unsigned int i = 0; i < tempVertices.size(); i++) {
+ ALOGD("orig path: point at %f %f",
+ tempVertices[i].position[0], tempVertices[i].position[1]);
+ }
+#endif
+
+ if (paintInfo.style == SkPaint::kStroke_Style) {
+ if (!paintInfo.isAA) {
+ if (wasClosed) {
+ getStrokeVerticesFromPerimeter(paintInfo, tempVertices, vertexBuffer);
+ } else {
+ getStrokeVerticesFromUnclosedVertices(paintInfo, tempVertices, vertexBuffer);
+ }
+
+ } else {
+ if (wasClosed) {
+ getStrokeVerticesFromPerimeterAA(paintInfo, tempVertices, vertexBuffer);
+ } else {
+ getStrokeVerticesFromUnclosedVerticesAA(paintInfo, tempVertices, vertexBuffer);
+ }
+ }
+ } else {
+ // For kStrokeAndFill style, the path should be adjusted externally.
+ // It will be treated as a fill here.
+ if (!paintInfo.isAA) {
+ getFillVerticesFromPerimeter(tempVertices, vertexBuffer);
+ } else {
+ getFillVerticesFromPerimeterAA(paintInfo, tempVertices, vertexBuffer);
+ }
+ }
+}
+
+static void expandRectToCoverVertex(SkRect& rect, const Vertex& vertex) {
+ rect.fLeft = fminf(rect.fLeft, vertex.position[0]);
+ rect.fTop = fminf(rect.fTop, vertex.position[1]);
+ rect.fRight = fmaxf(rect.fRight, vertex.position[0]);
+ rect.fBottom = fmaxf(rect.fBottom, vertex.position[1]);
+}
+
+void PathTessellator::tessellateLines(const float* points, int count, SkPaint* paint,
+ const mat4* transform, SkRect& bounds, VertexBuffer& vertexBuffer) {
+ ATRACE_CALL();
+ const PaintInfo paintInfo(paint, transform);
+
+ const int extra = paintInfo.capExtraDivisions();
+ int numLines = count / 4;
+ int lineAllocSize;
+ // pre-allocate space for lines in the buffer, and degenerate tris in between
+ if (paintInfo.isAA) {
+ lineAllocSize = 6 * (2) + 2 + 6 * extra;
+ vertexBuffer.alloc<AlphaVertex>(numLines * lineAllocSize + (numLines - 1) * 2);
+ } else {
+ lineAllocSize = 2 * ((2) + extra);
+ vertexBuffer.alloc<Vertex>(numLines * lineAllocSize + (numLines - 1) * 2);
+ }
+
+ Vector<Vertex> tempVertices;
+ tempVertices.push();
+ tempVertices.push();
+ Vertex* tempVerticesData = tempVertices.editArray();
+ bounds.set(points[0], points[1], points[0], points[1]);
+ for (int i = 0; i < count; i += 4) {
+ Vertex::set(&(tempVerticesData[0]), points[i + 0], points[i + 1]);
+ Vertex::set(&(tempVerticesData[1]), points[i + 2], points[i + 3]);
+
+ if (paintInfo.isAA) {
+ getStrokeVerticesFromUnclosedVerticesAA(paintInfo, tempVertices, vertexBuffer);
+ } else {
+ getStrokeVerticesFromUnclosedVertices(paintInfo, tempVertices, vertexBuffer);
+ }
+
+ // calculate bounds
+ expandRectToCoverVertex(bounds, tempVerticesData[0]);
+ expandRectToCoverVertex(bounds, tempVerticesData[1]);
+ }
+
+ expandBoundsForStroke(bounds, paint, true); // force-expand bounds to incorporate stroke
+
+ // since multiple objects tessellated into buffer, separate them with degen tris
+ if (paintInfo.isAA) {
+ vertexBuffer.createDegenerateSeparators<AlphaVertex>(lineAllocSize);
+ } else {
+ vertexBuffer.createDegenerateSeparators<Vertex>(lineAllocSize);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Simple path line approximation
+///////////////////////////////////////////////////////////////////////////////
+
+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);
+}
+
+bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool forceClose,
+ float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
+ ATRACE_CALL();
+
+ // TODO: to support joins other than sharp miter, join vertices should be labelled in the
+ // perimeter, or resolved into more vertices. Reconsider forceClose-ing in that case.
+ SkPath::Iter iter(path, forceClose);
+ SkPoint pts[4];
+ SkPath::Verb v;
+ while (SkPath::kDone_Verb != (v = iter.next(pts))) {
+ switch (v) {
+ case SkPath::kMove_Verb:
+ pushToVector(outputVertices, pts[0].x(), pts[0].y());
+ ALOGV("Move to pos %f %f", pts[0].x(), pts[0].y());
+ break;
+ case SkPath::kClose_Verb:
+ ALOGV("Close at pos %f %f", pts[0].x(), pts[0].y());
+ 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());
+ break;
+ case SkPath::kQuad_Verb:
+ ALOGV("kQuad_Verb");
+ recursiveQuadraticBezierVertices(
+ pts[0].x(), pts[0].y(),
+ pts[2].x(), pts[2].y(),
+ pts[1].x(), pts[1].y(),
+ sqrInvScaleX, sqrInvScaleY, outputVertices);
+ break;
+ case SkPath::kCubic_Verb:
+ ALOGV("kCubic_Verb");
+ recursiveCubicBezierVertices(
+ pts[0].x(), pts[0].y(),
+ pts[1].x(), pts[1].y(),
+ pts[3].x(), pts[3].y(),
+ pts[2].x(), pts[2].y(),
+ sqrInvScaleX, sqrInvScaleY, outputVertices);
+ break;
+ default:
+ break;
+ }
+ }
+
+ int size = outputVertices.size();
+ if (size >= 2 && outputVertices[0].position[0] == outputVertices[size - 1].position[0] &&
+ outputVertices[0].position[1] == outputVertices[size - 1].position[1]) {
+ outputVertices.pop();
+ return true;
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Bezier approximation
+///////////////////////////////////////////////////////////////////////////////
+
+void PathTessellator::recursiveCubicBezierVertices(
+ float p1x, float p1y, float c1x, float c1y,
+ float p2x, float p2y, float c2x, float c2y,
+ float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
+ float dx = p2x - p1x;
+ float dy = p2y - p1y;
+ float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx);
+ float d2 = fabs((c2x - p2x) * dy - (c2y - p2y) * dx);
+ float d = d1 + d2;
+
+ // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
+
+ if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
+ // below thresh, draw line by adding endpoint
+ pushToVector(outputVertices, p2x, p2y);
+ } else {
+ float p1c1x = (p1x + c1x) * 0.5f;
+ float p1c1y = (p1y + c1y) * 0.5f;
+ float p2c2x = (p2x + c2x) * 0.5f;
+ float p2c2y = (p2y + c2y) * 0.5f;
+
+ float c1c2x = (c1x + c2x) * 0.5f;
+ float c1c2y = (c1y + c2y) * 0.5f;
+
+ float p1c1c2x = (p1c1x + c1c2x) * 0.5f;
+ float p1c1c2y = (p1c1y + c1c2y) * 0.5f;
+
+ float p2c1c2x = (p2c2x + c1c2x) * 0.5f;
+ float p2c1c2y = (p2c2y + c1c2y) * 0.5f;
+
+ float mx = (p1c1c2x + p2c1c2x) * 0.5f;
+ float my = (p1c1c2y + p2c1c2y) * 0.5f;
+
+ recursiveCubicBezierVertices(
+ p1x, p1y, p1c1x, p1c1y,
+ mx, my, p1c1c2x, p1c1c2y,
+ sqrInvScaleX, sqrInvScaleY, outputVertices);
+ recursiveCubicBezierVertices(
+ mx, my, p2c1c2x, p2c1c2y,
+ p2x, p2y, p2c2x, p2c2y,
+ sqrInvScaleX, sqrInvScaleY, outputVertices);
+ }
+}
+
+void PathTessellator::recursiveQuadraticBezierVertices(
+ float ax, float ay,
+ float bx, float by,
+ float cx, float cy,
+ float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
+ float dx = bx - ax;
+ float dy = by - ay;
+ float d = (cx - bx) * dy - (cy - by) * dx;
+
+ if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
+ // below thresh, draw line by adding endpoint
+ pushToVector(outputVertices, bx, by);
+ } else {
+ float acx = (ax + cx) * 0.5f;
+ float bcx = (bx + cx) * 0.5f;
+ float acy = (ay + cy) * 0.5f;
+ float bcy = (by + cy) * 0.5f;
+
+ // midpoint
+ float mx = (acx + bcx) * 0.5f;
+ float my = (acy + bcy) * 0.5f;
+
+ recursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy,
+ sqrInvScaleX, sqrInvScaleY, outputVertices);
+ recursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy,
+ sqrInvScaleX, sqrInvScaleY, outputVertices);
+ }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/PathRenderer.h b/libs/hwui/PathTessellator.h
index e9f347bda495..596d49d8f876 100644
--- a/libs/hwui/PathRenderer.h
+++ b/libs/hwui/PathTessellator.h
@@ -14,43 +14,65 @@
* limitations under the License.
*/
-#ifndef ANDROID_HWUI_PATH_RENDERER_H
-#define ANDROID_HWUI_PATH_RENDERER_H
+#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"
namespace android {
namespace uirenderer {
-class Matrix4;
-typedef Matrix4 mat4;
-
class VertexBuffer {
public:
VertexBuffer():
mBuffer(0),
mSize(0),
- mCleanupMethod(0)
+ mCleanupMethod(NULL)
{}
~VertexBuffer() {
- if (mCleanupMethod)
- mCleanupMethod(mBuffer);
+ if (mCleanupMethod) mCleanupMethod(mBuffer);
}
+ /**
+ This should be the only method used by the PathTessellator. Subsequent calls to alloc will
+ allocate space within the first allocation (useful if you want to eventually allocate
+ multiple regions within a single VertexBuffer, such as with PathTessellator::tesselateLines()
+ */
template <class TYPE>
TYPE* alloc(int size) {
+ if (mSize) {
+ TYPE* reallocBuffer = (TYPE*)mReallocBuffer;
+ // already have allocated the buffer, re-allocate space within
+ if (mReallocBuffer != mBuffer) {
+ // not first re-allocation, leave space for degenerate triangles to separate strips
+ reallocBuffer += 2;
+ }
+ mReallocBuffer = reallocBuffer + size;
+ return reallocBuffer;
+ }
mSize = size;
- mBuffer = (void*)new TYPE[size];
+ mReallocBuffer = mBuffer = (void*)new TYPE[size];
mCleanupMethod = &(cleanup<TYPE>);
return (TYPE*)mBuffer;
}
- void* getBuffer() { return mBuffer; }
- unsigned int getSize() { return mSize; }
+ void* getBuffer() const { return mBuffer; }
+ unsigned int getSize() const { return mSize; }
+
+ template <class TYPE>
+ void createDegenerateSeparators(int allocSize) {
+ TYPE* end = (TYPE*)mBuffer + mSize;
+ for (TYPE* degen = (TYPE*)mBuffer + allocSize; degen < end; degen += 2 + allocSize) {
+ memcpy(degen, degen - 1, sizeof(TYPE));
+ memcpy(degen + 1, degen + 2, sizeof(TYPE));
+ }
+ }
private:
template <class TYPE>
@@ -60,18 +82,24 @@ private:
void* mBuffer;
unsigned int mSize;
+
+ void* mReallocBuffer; // used for multi-allocation
+
void (*mCleanupMethod)(void*);
};
-class PathRenderer {
+class PathTessellator {
public:
- static SkRect computePathBounds(const SkPath& path, const SkPaint* paint);
+ static void expandBoundsForStroke(SkRect& bounds, const SkPaint* paint, bool forceExpand);
- static void convexPathVertices(const SkPath& path, const SkPaint* paint,
+ static void tessellatePath(const SkPath& path, const SkPaint* paint,
const mat4 *transform, VertexBuffer& vertexBuffer);
+ static void tessellateLines(const float* points, int count, SkPaint* paint,
+ const mat4* transform, SkRect& bounds, VertexBuffer& vertexBuffer);
+
private:
- static bool convexPathPerimeterVertices(const SkPath &path, bool forceClose,
+ static bool approximatePathOutlineVertices(const SkPath &path, bool forceClose,
float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex> &outputVertices);
/*
@@ -101,4 +129,4 @@ private:
}; // namespace uirenderer
}; // namespace android
-#endif // ANDROID_HWUI_PATH_RENDERER_H
+#endif // ANDROID_HWUI_PATH_TESSELLATOR_H
diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp
index f0b5553a79a7..14a23765b301 100644
--- a/libs/hwui/Program.cpp
+++ b/libs/hwui/Program.cpp
@@ -160,7 +160,7 @@ void Program::set(const mat4& projectionMatrix, const mat4& modelViewMatrix,
// up and to the left.
// This offset value is based on an assumption that some hardware may use as
// little as 12.4 precision, so we offset by slightly more than 1/16.
- p.translate(.375, .375, 0);
+ p.translate(.065, .065, 0);
}
mat4 t(transformMatrix);
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index 7e3aacf90260..e8b6d476b566 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -24,6 +24,7 @@
#include <SkXfermode.h>
+#include "Debug.h"
#include "Matrix.h"
#include "Properties.h"
@@ -81,7 +82,9 @@ namespace uirenderer {
#define PROGRAM_IS_SIMPLE_GRADIENT 41
-#define PROGRAM_IS_VERTEX_SHAPE_SHIFT 42
+#define PROGRAM_HAS_COLORS 42
+
+#define PROGRAM_HAS_DEBUG_HIGHLIGHT 43
///////////////////////////////////////////////////////////////////////////////
// Types
@@ -122,6 +125,9 @@ struct ProgramDescription {
bool hasExternalTexture;
bool hasTextureTransform;
+ // Color attribute
+ bool hasColors;
+
// Modulate, this should only be set when setColor() return true
bool modulate;
@@ -129,8 +135,7 @@ struct ProgramDescription {
bool hasBitmap;
bool isBitmapNpot;
- bool isAA;
- bool isVertexShape;
+ bool isAA; // drawing with a per-vertex alpha
bool hasGradient;
Gradient gradientType;
@@ -157,6 +162,8 @@ struct ProgramDescription {
bool hasGammaCorrection;
float gamma;
+ bool hasDebugHighlight;
+
/**
* Resets this description. All fields are reset back to the default
* values they hold after building a new instance.
@@ -167,8 +174,9 @@ struct ProgramDescription {
hasExternalTexture = false;
hasTextureTransform = false;
+ hasColors = false;
+
isAA = false;
- isVertexShape = false;
modulate = false;
@@ -196,6 +204,8 @@ struct ProgramDescription {
hasGammaCorrection = false;
gamma = 2.2f;
+
+ hasDebugHighlight = false;
}
/**
@@ -263,7 +273,8 @@ struct ProgramDescription {
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 (isVertexShape) key |= programid(0x1) << PROGRAM_IS_VERTEX_SHAPE_SHIFT;
+ if (hasColors) key |= programid(0x1) << PROGRAM_HAS_COLORS;
+ if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT;
return key;
}
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index f536adeff374..8eb85e5547eb 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -19,6 +19,7 @@
#include <utils/String8.h>
#include "Caches.h"
+#include "Dither.h"
#include "ProgramCache.h"
namespace android {
@@ -32,6 +33,9 @@ namespace uirenderer {
#define MODULATE_OP_MODULATE 1
#define MODULATE_OP_MODULATE_A8 2
+#define STR(x) STR1(x)
+#define STR1(x) #x
+
///////////////////////////////////////////////////////////////////////////////
// Vertex shaders snippets
///////////////////////////////////////////////////////////////////////////////
@@ -40,9 +44,8 @@ const char* gVS_Header_Attributes =
"attribute vec4 position;\n";
const char* gVS_Header_Attributes_TexCoords =
"attribute vec2 texCoords;\n";
-const char* gVS_Header_Attributes_AALineParameters =
- "attribute float vtxWidth;\n"
- "attribute float vtxLength;\n";
+const char* gVS_Header_Attributes_Colors =
+ "attribute vec4 colors;\n";
const char* gVS_Header_Attributes_AAVertexShapeParameters =
"attribute float vtxAlpha;\n";
const char* gVS_Header_Uniforms_TextureTransform =
@@ -52,25 +55,15 @@ const char* gVS_Header_Uniforms =
"uniform mat4 transform;\n";
const char* gVS_Header_Uniforms_IsPoint =
"uniform mediump float pointSize;\n";
-const char* gVS_Header_Uniforms_HasGradient[3] = {
- // Linear
- "uniform mat4 screenSpace;\n"
- "uniform float ditherSize;\n",
- // Circular
- "uniform mat4 screenSpace;\n"
- "uniform float ditherSize;\n",
- // Sweep
- "uniform mat4 screenSpace;\n"
- "uniform float ditherSize;\n"
-};
+const char* gVS_Header_Uniforms_HasGradient =
+ "uniform mat4 screenSpace;\n";
const char* gVS_Header_Uniforms_HasBitmap =
"uniform mat4 textureTransform;\n"
"uniform mediump vec2 textureDimension;\n";
const char* gVS_Header_Varyings_HasTexture =
"varying vec2 outTexCoords;\n";
-const char* gVS_Header_Varyings_IsAALine =
- "varying float widthProportion;\n"
- "varying float lengthProportion;\n";
+const char* gVS_Header_Varyings_HasColors =
+ "varying vec4 outColors;\n";
const char* gVS_Header_Varyings_IsAAVertexShape =
"varying float alpha;\n";
const char* gVS_Header_Varyings_HasBitmap =
@@ -100,26 +93,28 @@ const char* gVS_Main =
"\nvoid main(void) {\n";
const char* gVS_Main_OutTexCoords =
" outTexCoords = texCoords;\n";
+const char* gVS_Main_OutColors =
+ " outColors = colors;\n";
const char* gVS_Main_OutTransformedTexCoords =
" outTexCoords = (mainTextureTransform * vec4(texCoords, 0.0, 1.0)).xy;\n";
const char* gVS_Main_OutGradient[6] = {
// Linear
" linear = vec2((screenSpace * position).x, 0.5);\n"
- " ditherTexCoords = (transform * position).xy * ditherSize;\n",
+ " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
" linear = (screenSpace * position).x;\n"
- " ditherTexCoords = (transform * position).xy * ditherSize;\n",
+ " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
// Circular
" circular = (screenSpace * position).xy;\n"
- " ditherTexCoords = (transform * position).xy * ditherSize;\n",
+ " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
" circular = (screenSpace * position).xy;\n"
- " ditherTexCoords = (transform * position).xy * ditherSize;\n",
+ " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
// Sweep
" sweep = (screenSpace * position).xy;\n"
- " ditherTexCoords = (transform * position).xy * ditherSize;\n",
+ " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
" sweep = (screenSpace * position).xy;\n"
- " ditherTexCoords = (transform * position).xy * ditherSize;\n",
+ " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
};
const char* gVS_Main_OutBitmapTexCoords =
" outBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n";
@@ -129,9 +124,6 @@ const char* gVS_Main_Position =
" gl_Position = projection * transform * position;\n";
const char* gVS_Main_PointSize =
" gl_PointSize = pointSize;\n";
-const char* gVS_Main_AALine =
- " widthProportion = vtxWidth;\n"
- " lengthProportion = vtxLength;\n";
const char* gVS_Main_AAVertexShape =
" alpha = vtxAlpha;\n";
const char* gVS_Footer =
@@ -149,9 +141,6 @@ const char* gFS_Header =
"precision mediump float;\n\n";
const char* gFS_Uniforms_Color =
"uniform vec4 color;\n";
-const char* gFS_Uniforms_AALine =
- "uniform float boundaryWidth;\n"
- "uniform float boundaryLength;\n";
const char* gFS_Header_Uniforms_PointHasBitmap =
"uniform vec2 textureDimension;\n"
"uniform float pointSize;\n";
@@ -159,24 +148,14 @@ const char* gFS_Uniforms_TextureSampler =
"uniform sampler2D baseSampler;\n";
const char* gFS_Uniforms_ExternalTextureSampler =
"uniform samplerExternalOES baseSampler;\n";
-#define FS_UNIFORMS_DITHER \
- "uniform float ditherSizeSquared;\n" \
- "uniform sampler2D ditherSampler;\n"
-#define FS_UNIFORMS_GRADIENT \
- "uniform vec4 startColor;\n" \
+const char* gFS_Uniforms_Dither =
+ "uniform sampler2D ditherSampler;";
+const char* gFS_Uniforms_GradientSampler[2] = {
+ "%s\n"
+ "uniform sampler2D gradientSampler;\n",
+ "%s\n"
+ "uniform vec4 startColor;\n"
"uniform vec4 endColor;\n"
-const char* gFS_Uniforms_GradientSampler[6] = {
- // Linear
- FS_UNIFORMS_DITHER "uniform sampler2D gradientSampler;\n",
- FS_UNIFORMS_DITHER FS_UNIFORMS_GRADIENT,
-
- // Circular
- FS_UNIFORMS_DITHER "uniform sampler2D gradientSampler;\n",
- FS_UNIFORMS_DITHER FS_UNIFORMS_GRADIENT,
-
- // Sweep
- FS_UNIFORMS_DITHER "uniform sampler2D gradientSampler;\n",
- FS_UNIFORMS_DITHER FS_UNIFORMS_GRADIENT
};
const char* gFS_Uniforms_BitmapSampler =
"uniform sampler2D bitmapSampler;\n";
@@ -203,10 +182,14 @@ const char* gFS_Main_PointBitmapTexCoords =
" highp vec2 outBitmapTexCoords = outPointBitmapTexCoords + "
"((gl_PointCoord - vec2(0.5, 0.5)) * textureDimension * vec2(pointSize, pointSize));\n";
-#define FS_MAIN_DITHER \
- "texture2D(ditherSampler, ditherTexCoords).a * ditherSizeSquared"
+const char* gFS_Main_Dither[2] = {
+ // ES 2.0
+ "texture2D(ditherSampler, ditherTexCoords).a * " STR(DITHER_KERNEL_SIZE_INV_SQUARE),
+ // ES 3.0
+ "texture2D(ditherSampler, ditherTexCoords).a"
+};
const char* gFS_Main_AddDitherToGradient =
- " gradientColor += " FS_MAIN_DITHER ";\n";
+ " gradientColor += %s;\n";
// Fast cases
const char* gFS_Fast_SingleColor =
@@ -239,18 +222,18 @@ const char* gFS_Fast_SingleModulateA8Texture_ApplyGamma =
"}\n\n";
const char* gFS_Fast_SingleGradient[2] = {
"\nvoid main(void) {\n"
- " gl_FragColor = " FS_MAIN_DITHER " + texture2D(gradientSampler, linear);\n"
+ " gl_FragColor = %s + texture2D(gradientSampler, linear);\n"
"}\n\n",
"\nvoid main(void) {\n"
- " gl_FragColor = " FS_MAIN_DITHER " + mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
- "}\n\n"
+ " gl_FragColor = %s + mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
+ "}\n\n",
};
const char* gFS_Fast_SingleModulateGradient[2] = {
"\nvoid main(void) {\n"
- " gl_FragColor = " FS_MAIN_DITHER " + color.a * texture2D(gradientSampler, linear);\n"
+ " gl_FragColor = %s + color.a * texture2D(gradientSampler, linear);\n"
"}\n\n",
"\nvoid main(void) {\n"
- " gl_FragColor = " FS_MAIN_DITHER " + color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
+ " gl_FragColor = %s + color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
"}\n\n"
};
@@ -259,9 +242,6 @@ const char* gFS_Main_FetchColor =
" fragColor = color;\n";
const char* gFS_Main_ModulateColor =
" fragColor *= color.a;\n";
-const char* gFS_Main_AccountForAALine =
- " fragColor *= (1.0 - smoothstep(boundaryWidth, 0.5, abs(0.5 - widthProportion)))\n"
- " * (1.0 - smoothstep(boundaryLength, 0.5, abs(0.5 - lengthProportion)));\n";
const char* gFS_Main_AccountForAAVertexShape =
" fragColor *= alpha;\n";
@@ -340,6 +320,8 @@ const char* gFS_Main_BitmapShader_Modulate[6] = {
};
const char* gFS_Main_FragColor =
" gl_FragColor = fragColor;\n";
+const char* gFS_Main_FragColor_HasColors =
+ " gl_FragColor *= outColors;\n";
const char* gFS_Main_FragColor_Blend =
" gl_FragColor = blendFramebuffer(fragColor, gl_LastFragColor);\n";
const char* gFS_Main_FragColor_Blend_Swap =
@@ -358,6 +340,8 @@ const char* gFS_Main_ApplyColorOp[4] = {
// PorterDuff
" fragColor = blendColors(colorBlend, fragColor);\n"
};
+const char* gFS_Main_DebugHighlight =
+ " gl_FragColor.rgb = vec3(0.0, gl_FragColor.a, 0.0);\n";
const char* gFS_Footer =
"}\n\n";
@@ -415,7 +399,7 @@ const char* gBlendOps[18] = {
// Constructors/destructors
///////////////////////////////////////////////////////////////////////////////
-ProgramCache::ProgramCache() {
+ProgramCache::ProgramCache(): mHasES3(Extensions::getInstance().getMajorGlVersion() >= 3) {
}
ProgramCache::~ProgramCache() {
@@ -438,6 +422,12 @@ void ProgramCache::clear() {
Program* ProgramCache::get(const ProgramDescription& description) {
programid key = description.key();
+ if (key == (PROGRAM_KEY_TEXTURE | PROGRAM_KEY_A8_TEXTURE)) {
+ // program for A8, unmodulated, texture w/o shader (black text/path textures) is equivalent
+ // to standard texture program (bitmaps, patches). Consider them equivalent.
+ key = PROGRAM_KEY_TEXTURE;
+ }
+
ssize_t index = mCache.indexOfKey(key);
Program* program = NULL;
if (index < 0) {
@@ -472,11 +462,10 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
shader.append(gVS_Header_Attributes_TexCoords);
}
if (description.isAA) {
- if (description.isVertexShape) {
- shader.append(gVS_Header_Attributes_AAVertexShapeParameters);
- } else {
- shader.append(gVS_Header_Attributes_AALineParameters);
- }
+ shader.append(gVS_Header_Attributes_AAVertexShapeParameters);
+ }
+ if (description.hasColors) {
+ shader.append(gVS_Header_Attributes_Colors);
}
// Uniforms
shader.append(gVS_Header_Uniforms);
@@ -484,7 +473,7 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
shader.append(gVS_Header_Uniforms_TextureTransform);
}
if (description.hasGradient) {
- shader.append(gVS_Header_Uniforms_HasGradient[description.gradientType]);
+ shader.append(gVS_Header_Uniforms_HasGradient);
}
if (description.hasBitmap) {
shader.append(gVS_Header_Uniforms_HasBitmap);
@@ -497,11 +486,10 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
shader.append(gVS_Header_Varyings_HasTexture);
}
if (description.isAA) {
- if (description.isVertexShape) {
- shader.append(gVS_Header_Varyings_IsAAVertexShape);
- } else {
- shader.append(gVS_Header_Varyings_IsAALine);
- }
+ shader.append(gVS_Header_Varyings_IsAAVertexShape);
+ }
+ if (description.hasColors) {
+ shader.append(gVS_Header_Varyings_HasColors);
}
if (description.hasGradient) {
shader.append(gVS_Header_Varyings_HasGradient[gradientIndex(description)]);
@@ -520,11 +508,10 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
shader.append(gVS_Main_OutTexCoords);
}
if (description.isAA) {
- if (description.isVertexShape) {
- shader.append(gVS_Main_AAVertexShape);
- } else {
- shader.append(gVS_Main_AALine);
- }
+ shader.append(gVS_Main_AAVertexShape);
+ }
+ if (description.hasColors) {
+ shader.append(gVS_Main_OutColors);
}
if (description.hasBitmap) {
shader.append(description.isPoint ?
@@ -574,11 +561,10 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
shader.append(gVS_Header_Varyings_HasTexture);
}
if (description.isAA) {
- if (description.isVertexShape) {
- shader.append(gVS_Header_Varyings_IsAAVertexShape);
- } else {
- shader.append(gVS_Header_Varyings_IsAALine);
- }
+ shader.append(gVS_Header_Varyings_IsAAVertexShape);
+ }
+ if (description.hasColors) {
+ shader.append(gVS_Header_Varyings_HasColors);
}
if (description.hasGradient) {
shader.append(gVS_Header_Varyings_HasGradient[gradientIndex(description)]);
@@ -603,11 +589,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
} else if (description.hasExternalTexture) {
shader.append(gFS_Uniforms_ExternalTextureSampler);
}
- if (description.isAA && !description.isVertexShape) {
- shader.append(gFS_Uniforms_AALine);
- }
if (description.hasGradient) {
- shader.append(gFS_Uniforms_GradientSampler[gradientIndex(description)]);
+ shader.appendFormat(gFS_Uniforms_GradientSampler[description.isSimpleGradient],
+ gFS_Uniforms_Dither);
}
if (description.hasBitmap && description.isPoint) {
shader.append(gFS_Header_Uniforms_PointHasBitmap);
@@ -617,9 +601,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
}
// Optimization for common cases
- if (!description.isAA && !blendFramebuffer &&
+ if (!description.isAA && !blendFramebuffer && !description.hasColors &&
description.colorOp == ProgramDescription::kColorNone &&
- !description.isPoint && !description.isVertexShape) {
+ !description.isPoint && !description.hasDebugHighlight) {
bool fast = false;
const bool noShader = !description.hasGradient && !description.hasBitmap;
@@ -658,9 +642,11 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
fast = true;
} else if (singleGradient) {
if (!description.modulate) {
- shader.append(gFS_Fast_SingleGradient[description.isSimpleGradient]);
+ shader.appendFormat(gFS_Fast_SingleGradient[description.isSimpleGradient],
+ gFS_Main_Dither[mHasES3]);
} else {
- shader.append(gFS_Fast_SingleModulateGradient[description.isSimpleGradient]);
+ shader.appendFormat(gFS_Fast_SingleModulateGradient[description.isSimpleGradient],
+ gFS_Main_Dither[mHasES3]);
}
fast = true;
}
@@ -714,7 +700,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
}
if (description.hasGradient) {
shader.append(gFS_Main_FetchGradient[gradientIndex(description)]);
- shader.append(gFS_Main_AddDitherToGradient);
+ shader.appendFormat(gFS_Main_AddDitherToGradient, gFS_Main_Dither[mHasES3]);
}
if (description.hasBitmap) {
if (description.isPoint) {
@@ -754,11 +740,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
shader.append(gFS_Main_ApplyColorOp[description.colorOp]);
if (description.isAA) {
- if (description.isVertexShape) {
- shader.append(gFS_Main_AccountForAAVertexShape);
- } else {
- shader.append(gFS_Main_AccountForAALine);
- }
+ shader.append(gFS_Main_AccountForAAVertexShape);
}
// Output the fragment
@@ -768,6 +750,12 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
shader.append(!description.swapSrcDst ?
gFS_Main_FragColor_Blend : gFS_Main_FragColor_Blend_Swap);
}
+ if (description.hasColors) {
+ shader.append(gFS_Main_FragColor_HasColors);
+ }
+ if (description.hasDebugHighlight) {
+ shader.append(gFS_Main_DebugHighlight);
+ }
}
// End the shader
shader.append(gFS_Footer);
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index 6cfe0c7c36b1..38f6f99f8996 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -31,17 +31,6 @@ namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
-// Defines
-///////////////////////////////////////////////////////////////////////////////
-
-// Debug
-#if DEBUG_PROGRAMS
- #define PROGRAM_LOGD(...) ALOGD(__VA_ARGS__)
-#else
- #define PROGRAM_LOGD(...)
-#endif
-
-///////////////////////////////////////////////////////////////////////////////
// Cache
///////////////////////////////////////////////////////////////////////////////
@@ -68,6 +57,8 @@ private:
void printLongString(const String8& shader) const;
KeyedVector<programid, Program*> mCache;
+
+ const bool mHasES3;
}; // class ProgramCache
}; // namespace uirenderer
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1e8765bae9a2..e4b4f3c4fb8d 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -34,9 +34,9 @@
// Textures used by layers must have dimensions multiples of this number
#define LAYER_SIZE 64
-// Defines the size in bits of the stencil buffer
+// Defines the size in bits of the stencil buffer for the framebuffer
// Note: Only 1 bit is required for clipping but more bits are required
-// to properly implement the winding fill rule when rasterizing paths
+// to properly implement overdraw debugging
#define STENCIL_BUFFER_SIZE 8
/**
@@ -68,6 +68,20 @@ enum DebugLevel {
#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.show_overdraw"
/**
+ * Used to enable/disable non-rectangular clipping debugging.
+ *
+ * The accepted values are:
+ * "highlight", drawing commands clipped by the stencil will
+ * be colored differently
+ * "region", renders the clipping region on screen whenever
+ * the stencil is set
+ * "hide", don't show the clip
+ *
+ * The default value is "hide".
+ */
+#define PROPERTY_DEBUG_STENCIL_CLIP "debug.hwui.show_non_rect_clip"
+
+/**
* Used to enable/disable scissor optimization. The accepted values are
* "true" and "false". The default value is "false".
*
@@ -82,17 +96,30 @@ enum DebugLevel {
*/
#define PROPERTY_DISABLE_SCISSOR_OPTIMIZATION "ro.hwui.disable_scissor_opt"
+/**
+ * Disables draw operation deferral if set to "true", forcing draw
+ * commands to be issued to OpenGL in order, and processed in sequence
+ * with state-manipulation canvas commands.
+ */
+#define PROPERTY_DISABLE_DRAW_DEFER "debug.hwui.disable_draw_defer"
+
+/**
+ * Used to disable draw operation reordering when deferring draw operations
+ * Has no effect if PROPERTY_DISABLE_DRAW_DEFER is set to "true"
+ */
+#define PROPERTY_DISABLE_DRAW_REORDER "debug.hwui.disable_draw_reorder"
+
// These properties are defined in mega-bytes
#define PROPERTY_TEXTURE_CACHE_SIZE "ro.hwui.texture_cache_size"
#define PROPERTY_LAYER_CACHE_SIZE "ro.hwui.layer_cache_size"
+#define PROPERTY_RENDER_BUFFER_CACHE_SIZE "ro.hwui.r_buffer_cache_size"
#define PROPERTY_GRADIENT_CACHE_SIZE "ro.hwui.gradient_cache_size"
#define PROPERTY_PATH_CACHE_SIZE "ro.hwui.path_cache_size"
-#define PROPERTY_SHAPE_CACHE_SIZE "ro.hwui.shape_cache_size"
#define PROPERTY_DROP_SHADOW_CACHE_SIZE "ro.hwui.drop_shadow_cache_size"
#define PROPERTY_FBO_CACHE_SIZE "ro.hwui.fbo_cache_size"
// These properties are defined in percentage (range 0..1)
-#define PROPERTY_TEXTURE_CACHE_FLUSH_RATE "ro.hwui.texture_cache_flush_rate"
+#define PROPERTY_TEXTURE_CACHE_FLUSH_RATE "ro.hwui.texture_cache_flushrate"
// These properties are defined in pixels
#define PROPERTY_TEXT_SMALL_CACHE_WIDTH "ro.hwui.text_small_cache_width"
@@ -130,8 +157,8 @@ enum DebugLevel {
#define DEFAULT_TEXTURE_CACHE_SIZE 24.0f
#define DEFAULT_LAYER_CACHE_SIZE 16.0f
-#define DEFAULT_PATH_CACHE_SIZE 4.0f
-#define DEFAULT_SHAPE_CACHE_SIZE 1.0f
+#define DEFAULT_RENDER_BUFFER_CACHE_SIZE 2.0f
+#define DEFAULT_PATH_CACHE_SIZE 10.0f
#define DEFAULT_PATCH_CACHE_SIZE 512
#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
#define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 80f39ff90d1e..f50ac3ccdb00 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -159,6 +159,13 @@ public:
bottom += dy;
}
+ void outset(float delta) {
+ left -= delta;
+ top -= delta;
+ right += delta;
+ bottom += delta;
+ }
+
void snapToPixelBoundaries() {
left = floorf(left + 0.5f);
top = floorf(top + 0.5f);
@@ -171,22 +178,19 @@ public:
}
private:
- static inline float min(float a, float b) { return (a < b) ? a : b; }
- static inline float max(float a, float b) { return (a > b) ? a : b; }
-
void intersectWith(Rect& tmp) const {
- tmp.left = max(left, tmp.left);
- tmp.top = max(top, tmp.top);
- tmp.right = min(right, tmp.right);
- tmp.bottom = min(bottom, tmp.bottom);
+ tmp.left = fmaxf(left, tmp.left);
+ tmp.top = fmaxf(top, tmp.top);
+ tmp.right = fminf(right, tmp.right);
+ tmp.bottom = fminf(bottom, tmp.bottom);
}
Rect intersectWith(float l, float t, float r, float b) const {
Rect tmp;
- tmp.left = max(left, l);
- tmp.top = max(top, t);
- tmp.right = min(right, r);
- tmp.bottom = min(bottom, b);
+ tmp.left = fmaxf(left, l);
+ tmp.top = fmaxf(top, t);
+ tmp.right = fminf(right, r);
+ tmp.bottom = fminf(bottom, b);
return tmp;
}
diff --git a/libs/hwui/RenderBuffer.h b/libs/hwui/RenderBuffer.h
new file mode 100644
index 000000000000..35a516a58022
--- /dev/null
+++ b/libs/hwui/RenderBuffer.h
@@ -0,0 +1,194 @@
+/*
+ * 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_RENDER_BUFFER_H
+#define ANDROID_HWUI_RENDER_BUFFER_H
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Represents an OpenGL render buffer. Render buffers are attached
+ * to layers to perform stencil work.
+ */
+struct RenderBuffer {
+ /**
+ * Creates a new render buffer in the specified format and dimensions.
+ * The format must be one of the formats allowed by glRenderbufferStorage().
+ */
+ RenderBuffer(GLenum format, uint32_t width, uint32_t height):
+ mFormat(format), mWidth(width), mHeight(height), mAllocated(false) {
+
+ glGenRenderbuffers(1, &mName);
+ }
+
+ ~RenderBuffer() {
+ if (mName) {
+ glDeleteRenderbuffers(1, &mName);
+ }
+ }
+
+ /**
+ * Returns the GL name of this render buffer.
+ */
+ GLuint getName() const {
+ return mName;
+ }
+
+ /**
+ * Returns the format of this render buffer.
+ */
+ GLenum getFormat() const {
+ return mFormat;
+ }
+
+ /**
+ * Binds this render buffer to the current GL context.
+ */
+ void bind() const {
+ glBindRenderbuffer(GL_RENDERBUFFER, mName);
+ }
+
+ /**
+ * Indicates whether this render buffer has allocated its
+ * storage. See allocate() and resize().
+ */
+ bool isAllocated() const {
+ return mAllocated;
+ }
+
+ /**
+ * Allocates this render buffer's storage if needed.
+ * This method doesn't do anything if isAllocated() returns true.
+ */
+ void allocate() {
+ if (!mAllocated) {
+ glRenderbufferStorage(GL_RENDERBUFFER, mFormat, mWidth, mHeight);
+ mAllocated = true;
+ }
+ }
+
+ /**
+ * Resizes this render buffer. If the buffer was previously allocated,
+ * the storage is re-allocated wit the new specified dimensions. If the
+ * buffer wasn't previously allocated, the buffer remains unallocated.
+ */
+ void resize(uint32_t width, uint32_t height) {
+ if (isAllocated() && (width != mWidth || height != mHeight)) {
+ glRenderbufferStorage(GL_RENDERBUFFER, mFormat, width, height);
+ }
+
+ mWidth = width;
+ mHeight = height;
+ }
+
+ /**
+ * Returns the width of the render buffer in pixels.
+ */
+ uint32_t getWidth() const {
+ return mWidth;
+ }
+
+ /**
+ * Returns the height of the render buffer in pixels.
+ */
+ uint32_t getHeight() const {
+ return mHeight;
+ }
+
+ /**
+ * Returns the size of this render buffer in bytes.
+ */
+ uint32_t getSize() const {
+ // Round to the nearest byte
+ return (uint32_t) ((mWidth * mHeight * formatSize(mFormat)) / 8.0f + 0.5f);
+ }
+
+ /**
+ * Returns the number of bits per component in the specified format.
+ * The format must be one of the formats allowed by glRenderbufferStorage().
+ */
+ static uint32_t formatSize(GLenum format) {
+ switch (format) {
+ case GL_STENCIL_INDEX8:
+ return 8;
+ case GL_STENCIL_INDEX1_OES:
+ return 1;
+ case GL_STENCIL_INDEX4_OES:
+ return 4;
+ case GL_DEPTH_COMPONENT16:
+ case GL_RGBA4:
+ case GL_RGB565:
+ case GL_RGB5_A1:
+ return 16;
+ }
+ return 0;
+ }
+
+ /**
+ * Indicates whether the specified format represents a stencil buffer.
+ */
+ static bool isStencilBuffer(GLenum format) {
+ switch (format) {
+ case GL_STENCIL_INDEX8:
+ case GL_STENCIL_INDEX1_OES:
+ case GL_STENCIL_INDEX4_OES:
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the name of the specified render buffer format.
+ */
+ static const char* formatName(GLenum format) {
+ switch (format) {
+ case GL_STENCIL_INDEX8:
+ return "STENCIL_8";
+ case GL_STENCIL_INDEX1_OES:
+ return "STENCIL_1";
+ case GL_STENCIL_INDEX4_OES:
+ return "STENCIL_4";
+ case GL_DEPTH_COMPONENT16:
+ return "DEPTH_16";
+ case GL_RGBA4:
+ return "RGBA_4444";
+ case GL_RGB565:
+ return "RGB_565";
+ case GL_RGB5_A1:
+ return "RGBA_5551";
+ }
+ return "Unknown";
+ }
+
+private:
+ GLenum mFormat;
+
+ uint32_t mWidth;
+ uint32_t mHeight;
+
+ bool mAllocated;
+
+ GLuint mName;
+}; // struct RenderBuffer
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_RENDER_BUFFER_H
diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp
new file mode 100644
index 000000000000..830a13ab28a7
--- /dev/null
+++ b/libs/hwui/RenderBufferCache.cpp
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <utils/Log.h>
+
+#include "Debug.h"
+#include "Properties.h"
+#include "RenderBufferCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#if DEBUG_RENDER_BUFFERS
+ #define RENDER_BUFFER_LOGD(...) ALOGD(__VA_ARGS__)
+#else
+ #define RENDER_BUFFER_LOGD(...)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// 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, NULL) > 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() {
+ clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t RenderBufferCache::getSize() {
+ return mSize;
+}
+
+uint32_t RenderBufferCache::getMaxSize() {
+ return mMaxSize;
+}
+
+void RenderBufferCache::setMaxSize(uint32_t maxSize) {
+ clear();
+ mMaxSize = maxSize;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+int RenderBufferCache::RenderBufferEntry::compare(
+ const RenderBufferCache::RenderBufferEntry& lhs,
+ const RenderBufferCache::RenderBufferEntry& rhs) {
+ int deltaInt = int(lhs.mWidth) - int(rhs.mWidth);
+ if (deltaInt != 0) return deltaInt;
+
+ deltaInt = int(lhs.mHeight) - int(rhs.mHeight);
+ if (deltaInt != 0) return deltaInt;
+
+ return int(lhs.mFormat) - int(rhs.mFormat);
+}
+
+void RenderBufferCache::deleteBuffer(RenderBuffer* buffer) {
+ if (buffer) {
+ RENDER_BUFFER_LOGD("Deleted %s render buffer (%dx%d)",
+ RenderBuffer::formatName(buffer->getFormat()),
+ buffer->getWidth(), buffer->getHeight());
+
+ mSize -= buffer->getSize();
+ delete buffer;
+ }
+}
+
+void RenderBufferCache::clear() {
+ size_t count = mCache.size();
+ for (size_t i = 0; i < count; i++) {
+ deleteBuffer(mCache.itemAt(i).mBuffer);
+ }
+ mCache.clear();
+}
+
+RenderBuffer* RenderBufferCache::get(GLenum format, const uint32_t width, const uint32_t height) {
+ RenderBuffer* buffer = NULL;
+
+ RenderBufferEntry entry(format, width, height);
+ ssize_t index = mCache.indexOf(entry);
+
+ if (index >= 0) {
+ entry = mCache.itemAt(index);
+ mCache.removeAt(index);
+
+ buffer = entry.mBuffer;
+ mSize -= buffer->getSize();
+
+ RENDER_BUFFER_LOGD("Found %s render buffer (%dx%d)",
+ RenderBuffer::formatName(format), width, height);
+ } else {
+ buffer = new RenderBuffer(format, width, height);
+
+ RENDER_BUFFER_LOGD("Created new %s render buffer (%dx%d)",
+ RenderBuffer::formatName(format), width, height);
+ }
+
+ buffer->bind();
+ buffer->allocate();
+
+ return buffer;
+}
+
+bool RenderBufferCache::put(RenderBuffer* buffer) {
+ if (!buffer) return false;
+
+ const uint32_t size = buffer->getSize();
+ if (size < mMaxSize) {
+ while (mSize + size > mMaxSize) {
+ size_t position = 0;
+
+ RenderBuffer* victim = mCache.itemAt(position).mBuffer;
+ deleteBuffer(victim);
+ mCache.removeAt(position);
+ }
+
+ RenderBufferEntry entry(buffer);
+
+ mCache.add(entry);
+ mSize += size;
+
+ RENDER_BUFFER_LOGD("Added %s render buffer (%dx%d)",
+ RenderBuffer::formatName(buffer->getFormat()),
+ buffer->getWidth(), buffer->getHeight());
+
+ return true;
+ }
+ return false;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/RenderBufferCache.h b/libs/hwui/RenderBufferCache.h
new file mode 100644
index 000000000000..af8060fe2d21
--- /dev/null
+++ b/libs/hwui/RenderBufferCache.h
@@ -0,0 +1,130 @@
+/*
+ * 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_RENDER_BUFFER_CACHE_H
+#define ANDROID_HWUI_RENDER_BUFFER_CACHE_H
+
+#include <GLES2/gl2.h>
+
+#include "RenderBuffer.h"
+#include "utils/SortedList.h"
+
+namespace android {
+namespace uirenderer {
+
+class RenderBufferCache {
+public:
+ RenderBufferCache();
+ ~RenderBufferCache();
+
+ /**
+ * Returns a buffer with the exact specified dimensions. If no suitable
+ * buffer can be found, a new one is created and returned. If creating a
+ * new buffer fails, NULL is returned.
+ *
+ * When a buffer is obtained from the cache, it is removed and the total
+ * size of the cache goes down.
+ *
+ * The returned buffer is always allocated and bound
+ * (see RenderBuffer::isAllocated()).
+ *
+ * @param format The desired render buffer format
+ * @param width The desired width of the buffer
+ * @param height The desired height of the buffer
+ */
+ RenderBuffer* get(GLenum format, const uint32_t width, const uint32_t height);
+
+ /**
+ * Adds the buffer to the cache. The buffer will not be added if there is
+ * not enough space available. Adding a buffer can cause other buffer to
+ * be removed from the cache.
+ *
+ * @param buffer The render buffer to add to the cache
+ *
+ * @return True if the buffer was added, false otherwise.
+ */
+ bool put(RenderBuffer* buffer);
+ /**
+ * Clears the cache. This causes all layers 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.
+ */
+ uint32_t getMaxSize();
+ /**
+ * Returns the current size of the cache in bytes.
+ */
+ uint32_t getSize();
+
+private:
+ struct RenderBufferEntry {
+ RenderBufferEntry():
+ mBuffer(NULL), mWidth(0), mHeight(0) {
+ }
+
+ RenderBufferEntry(GLenum format, const uint32_t width, const uint32_t height):
+ mBuffer(NULL), mFormat(format), mWidth(width), mHeight(height) {
+ }
+
+ RenderBufferEntry(RenderBuffer* buffer):
+ mBuffer(buffer), mFormat(buffer->getFormat()),
+ mWidth(buffer->getWidth()), mHeight(buffer->getHeight()) {
+ }
+
+ static int compare(const RenderBufferEntry& lhs, const RenderBufferEntry& rhs);
+
+ bool operator==(const RenderBufferEntry& other) const {
+ return compare(*this, other) == 0;
+ }
+
+ bool operator!=(const RenderBufferEntry& other) const {
+ 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);
+ }
+
+ RenderBuffer* mBuffer;
+ GLenum mFormat;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ }; // struct RenderBufferEntry
+
+ void deleteBuffer(RenderBuffer* buffer);
+
+ SortedList<RenderBufferEntry> mCache;
+
+ uint32_t mSize;
+ uint32_t mMaxSize;
+}; // class RenderBufferCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_RENDER_BUFFER_CACHE_H
diff --git a/libs/hwui/ShapeCache.cpp b/libs/hwui/ShapeCache.cpp
deleted file mode 100644
index 5a23235f7241..000000000000
--- a/libs/hwui/ShapeCache.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2011 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 "OpenGLRenderer"
-
-#include "ShapeCache.h"
-
-namespace android {
-namespace uirenderer {
-
-///////////////////////////////////////////////////////////////////////////////
-// Rounded rects
-///////////////////////////////////////////////////////////////////////////////
-
-RoundRectShapeCache::RoundRectShapeCache(): ShapeCache<RoundRectShapeCacheEntry>(
- "round rect", PROPERTY_SHAPE_CACHE_SIZE, DEFAULT_SHAPE_CACHE_SIZE) {
-}
-
-PathTexture* RoundRectShapeCache::getRoundRect(float width, float height,
- float rx, float ry, SkPaint* paint) {
- RoundRectShapeCacheEntry entry(width, height, rx, ry, paint);
- PathTexture* texture = get(entry);
-
- if (!texture) {
- SkPath path;
- SkRect r;
- r.set(0.0f, 0.0f, width, height);
- path.addRoundRect(r, rx, ry, SkPath::kCW_Direction);
-
- texture = addTexture(entry, &path, paint);
- }
-
- return texture;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Circles
-///////////////////////////////////////////////////////////////////////////////
-
-CircleShapeCache::CircleShapeCache(): ShapeCache<CircleShapeCacheEntry>(
- "circle", PROPERTY_SHAPE_CACHE_SIZE, DEFAULT_SHAPE_CACHE_SIZE) {
-}
-
-PathTexture* CircleShapeCache::getCircle(float radius, SkPaint* paint) {
- CircleShapeCacheEntry entry(radius, paint);
- PathTexture* texture = get(entry);
-
- if (!texture) {
- SkPath path;
- path.addCircle(radius, radius, radius, SkPath::kCW_Direction);
-
- texture = addTexture(entry, &path, paint);
- }
-
- return texture;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Ovals
-///////////////////////////////////////////////////////////////////////////////
-
-OvalShapeCache::OvalShapeCache(): ShapeCache<OvalShapeCacheEntry>(
- "oval", PROPERTY_SHAPE_CACHE_SIZE, DEFAULT_SHAPE_CACHE_SIZE) {
-}
-
-PathTexture* OvalShapeCache::getOval(float width, float height, SkPaint* paint) {
- OvalShapeCacheEntry entry(width, height, paint);
- PathTexture* texture = get(entry);
-
- if (!texture) {
- SkPath path;
- SkRect r;
- r.set(0.0f, 0.0f, width, height);
- path.addOval(r, SkPath::kCW_Direction);
-
- texture = addTexture(entry, &path, paint);
- }
-
- return texture;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Rects
-///////////////////////////////////////////////////////////////////////////////
-
-RectShapeCache::RectShapeCache(): ShapeCache<RectShapeCacheEntry>(
- "rect", PROPERTY_SHAPE_CACHE_SIZE, DEFAULT_SHAPE_CACHE_SIZE) {
-}
-
-PathTexture* RectShapeCache::getRect(float width, float height, SkPaint* paint) {
- RectShapeCacheEntry entry(width, height, paint);
- PathTexture* texture = get(entry);
-
- if (!texture) {
- SkRect bounds;
- bounds.set(0.0f, 0.0f, width, height);
-
- float left, top, offset;
- uint32_t rectWidth, rectHeight;
- computeBounds(bounds, paint, left, top, offset, rectWidth, rectHeight);
-
- if (!checkTextureSize(rectWidth, rectHeight)) return NULL;
-
- purgeCache(rectWidth, rectHeight);
-
- SkBitmap bitmap;
- initBitmap(bitmap, rectWidth, rectHeight);
-
- SkPaint pathPaint(*paint);
- initPaint(pathPaint);
-
- SkCanvas canvas(bitmap);
- canvas.translate(-left + offset, -top + offset);
- canvas.drawRect(bounds, pathPaint);
-
- texture = createTexture(0, 0, offset, rectWidth, rectHeight, 0);
- addTexture(entry, &bitmap, texture);
- }
-
- return texture;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Arcs
-///////////////////////////////////////////////////////////////////////////////
-
-ArcShapeCache::ArcShapeCache(): ShapeCache<ArcShapeCacheEntry>(
- "arc", PROPERTY_SHAPE_CACHE_SIZE, DEFAULT_SHAPE_CACHE_SIZE) {
-}
-
-PathTexture* ArcShapeCache::getArc(float width, float height,
- float startAngle, float sweepAngle, bool useCenter, SkPaint* paint) {
- ArcShapeCacheEntry entry(width, height, startAngle, sweepAngle, useCenter, paint);
- PathTexture* texture = get(entry);
-
- if (!texture) {
- SkPath path;
- SkRect r;
- r.set(0.0f, 0.0f, width, height);
- if (useCenter) {
- path.moveTo(r.centerX(), r.centerY());
- }
- path.arcTo(r, startAngle, sweepAngle, !useCenter);
- if (useCenter) {
- path.close();
- }
-
- texture = addTexture(entry, &path, paint);
- }
-
- return texture;
-}
-
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/ShapeCache.h b/libs/hwui/ShapeCache.h
deleted file mode 100644
index 3a95b99cdc5c..000000000000
--- a/libs/hwui/ShapeCache.h
+++ /dev/null
@@ -1,639 +0,0 @@
-/*
- * Copyright (C) 2011 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_SHAPE_CACHE_H
-#define ANDROID_HWUI_SHAPE_CACHE_H
-
-#include <GLES2/gl2.h>
-
-#include <SkBitmap.h>
-#include <SkCanvas.h>
-#include <SkPaint.h>
-#include <SkPath.h>
-#include <SkRect.h>
-
-#include "Debug.h"
-#include "Properties.h"
-#include "Texture.h"
-#include "utils/Compare.h"
-#include "utils/GenerationCache.h"
-
-namespace android {
-namespace uirenderer {
-
-///////////////////////////////////////////////////////////////////////////////
-// Defines
-///////////////////////////////////////////////////////////////////////////////
-
-// Debug
-#if DEBUG_SHAPES
- #define SHAPE_LOGD(...) ALOGD(__VA_ARGS__)
-#else
- #define SHAPE_LOGD(...)
-#endif
-
-///////////////////////////////////////////////////////////////////////////////
-// Classes
-///////////////////////////////////////////////////////////////////////////////
-
-/**
- * Alpha texture used to represent a path.
- */
-struct PathTexture: public Texture {
- PathTexture(): Texture() {
- }
-
- /**
- * Left coordinate of the path bounds.
- */
- float left;
- /**
- * Top coordinate of the path bounds.
- */
- float top;
- /**
- * Offset to draw the path at the correct origin.
- */
- float offset;
-}; // struct PathTexture
-
-/**
- * Describe a shape in the shape cache.
- */
-struct ShapeCacheEntry {
- enum ShapeType {
- kShapeNone,
- kShapeRect,
- kShapeRoundRect,
- kShapeCircle,
- kShapeOval,
- kShapeArc,
- kShapePath
- };
-
- ShapeCacheEntry() {
- shapeType = kShapeNone;
- join = SkPaint::kDefault_Join;
- cap = SkPaint::kDefault_Cap;
- style = SkPaint::kFill_Style;
- float v = 4.0f;
- miter = *(uint32_t*) &v;
- v = 1.0f;
- strokeWidth = *(uint32_t*) &v;
- pathEffect = NULL;
- }
-
- ShapeCacheEntry(ShapeType type, SkPaint* paint) {
- shapeType = type;
- join = paint->getStrokeJoin();
- cap = paint->getStrokeCap();
- float v = paint->getStrokeMiter();
- miter = *(uint32_t*) &v;
- v = paint->getStrokeWidth();
- strokeWidth = *(uint32_t*) &v;
- style = paint->getStyle();
- pathEffect = paint->getPathEffect();
- }
-
- virtual ~ShapeCacheEntry() {
- }
-
- ShapeType shapeType;
- SkPaint::Join join;
- SkPaint::Cap cap;
- SkPaint::Style style;
- uint32_t miter;
- uint32_t strokeWidth;
- SkPathEffect* pathEffect;
-
- bool operator<(const ShapeCacheEntry& rhs) const {
- LTE_INT(shapeType) {
- LTE_INT(join) {
- LTE_INT(cap) {
- LTE_INT(style) {
- LTE_INT(miter) {
- LTE_INT(strokeWidth) {
- LTE_INT(pathEffect) {
- return lessThan(rhs);
- }
- }
- }
- }
- }
- }
- }
- return false;
- }
-
-protected:
- virtual bool lessThan(const ShapeCacheEntry& rhs) const {
- return false;
- }
-}; // struct ShapeCacheEntry
-
-
-struct RoundRectShapeCacheEntry: public ShapeCacheEntry {
- RoundRectShapeCacheEntry(float width, float height, float rx, float ry, SkPaint* paint):
- ShapeCacheEntry(ShapeCacheEntry::kShapeRoundRect, paint) {
- mWidth = *(uint32_t*) &width;
- mHeight = *(uint32_t*) &height;
- mRx = *(uint32_t*) &rx;
- mRy = *(uint32_t*) &ry;
- }
-
- RoundRectShapeCacheEntry(): ShapeCacheEntry() {
- mWidth = 0;
- mHeight = 0;
- mRx = 0;
- mRy = 0;
- }
-
- bool lessThan(const ShapeCacheEntry& r) const {
- const RoundRectShapeCacheEntry& rhs = (const RoundRectShapeCacheEntry&) r;
- LTE_INT(mWidth) {
- LTE_INT(mHeight) {
- LTE_INT(mRx) {
- LTE_INT(mRy) {
- return false;
- }
- }
- }
- }
- return false;
- }
-
-private:
- uint32_t mWidth;
- uint32_t mHeight;
- uint32_t mRx;
- uint32_t mRy;
-}; // RoundRectShapeCacheEntry
-
-struct CircleShapeCacheEntry: public ShapeCacheEntry {
- CircleShapeCacheEntry(float radius, SkPaint* paint):
- ShapeCacheEntry(ShapeCacheEntry::kShapeCircle, paint) {
- mRadius = *(uint32_t*) &radius;
- }
-
- CircleShapeCacheEntry(): ShapeCacheEntry() {
- mRadius = 0;
- }
-
- bool lessThan(const ShapeCacheEntry& r) const {
- const CircleShapeCacheEntry& rhs = (const CircleShapeCacheEntry&) r;
- LTE_INT(mRadius) {
- return false;
- }
- return false;
- }
-
-private:
- uint32_t mRadius;
-}; // CircleShapeCacheEntry
-
-struct OvalShapeCacheEntry: public ShapeCacheEntry {
- OvalShapeCacheEntry(float width, float height, SkPaint* paint):
- ShapeCacheEntry(ShapeCacheEntry::kShapeOval, paint) {
- mWidth = *(uint32_t*) &width;
- mHeight = *(uint32_t*) &height;
- }
-
- OvalShapeCacheEntry(): ShapeCacheEntry() {
- mWidth = mHeight = 0;
- }
-
- bool lessThan(const ShapeCacheEntry& r) const {
- const OvalShapeCacheEntry& rhs = (const OvalShapeCacheEntry&) r;
- LTE_INT(mWidth) {
- LTE_INT(mHeight) {
- return false;
- }
- }
- return false;
- }
-
-private:
- uint32_t mWidth;
- uint32_t mHeight;
-}; // OvalShapeCacheEntry
-
-struct RectShapeCacheEntry: public ShapeCacheEntry {
- RectShapeCacheEntry(float width, float height, SkPaint* paint):
- ShapeCacheEntry(ShapeCacheEntry::kShapeRect, paint) {
- mWidth = *(uint32_t*) &width;
- mHeight = *(uint32_t*) &height;
- }
-
- RectShapeCacheEntry(): ShapeCacheEntry() {
- mWidth = mHeight = 0;
- }
-
- bool lessThan(const ShapeCacheEntry& r) const {
- const RectShapeCacheEntry& rhs = (const RectShapeCacheEntry&) r;
- LTE_INT(mWidth) {
- LTE_INT(mHeight) {
- return false;
- }
- }
- return false;
- }
-
-private:
- uint32_t mWidth;
- uint32_t mHeight;
-}; // RectShapeCacheEntry
-
-struct ArcShapeCacheEntry: public ShapeCacheEntry {
- ArcShapeCacheEntry(float width, float height, float startAngle, float sweepAngle,
- bool useCenter, SkPaint* paint):
- ShapeCacheEntry(ShapeCacheEntry::kShapeArc, paint) {
- mWidth = *(uint32_t*) &width;
- mHeight = *(uint32_t*) &height;
- mStartAngle = *(uint32_t*) &startAngle;
- mSweepAngle = *(uint32_t*) &sweepAngle;
- mUseCenter = useCenter ? 1 : 0;
- }
-
- ArcShapeCacheEntry(): ShapeCacheEntry() {
- mWidth = 0;
- mHeight = 0;
- mStartAngle = 0;
- mSweepAngle = 0;
- mUseCenter = 0;
- }
-
- bool lessThan(const ShapeCacheEntry& r) const {
- const ArcShapeCacheEntry& rhs = (const ArcShapeCacheEntry&) r;
- LTE_INT(mWidth) {
- LTE_INT(mHeight) {
- LTE_INT(mStartAngle) {
- LTE_INT(mSweepAngle) {
- LTE_INT(mUseCenter) {
- return false;
- }
- }
- }
- }
- }
- return false;
- }
-
-private:
- uint32_t mWidth;
- uint32_t mHeight;
- uint32_t mStartAngle;
- uint32_t mSweepAngle;
- uint32_t mUseCenter;
-}; // ArcShapeCacheEntry
-
-/**
- * A simple LRU shape cache. The cache has a maximum size expressed in bytes.
- * Any texture added to the cache causing the cache to grow beyond the maximum
- * allowed size will also cause the oldest texture to be kicked out.
- */
-template<typename Entry>
-class ShapeCache: public OnEntryRemoved<Entry, PathTexture*> {
-public:
- ShapeCache(const char* name, const char* propertyName, float defaultSize);
- ~ShapeCache();
-
- /**
- * Used as a callback when an entry is removed from the cache.
- * Do not invoke directly.
- */
- void operator()(Entry& path, PathTexture*& texture);
-
- /**
- * Clears the cache. This causes all textures 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.
- */
- uint32_t getMaxSize();
- /**
- * Returns the current size of the cache in bytes.
- */
- uint32_t getSize();
-
-protected:
- PathTexture* addTexture(const Entry& entry, const SkPath *path, const SkPaint* paint);
- PathTexture* addTexture(const Entry& entry, SkBitmap* bitmap);
- void addTexture(const Entry& entry, SkBitmap* bitmap, PathTexture* texture);
-
- /**
- * Ensures there is enough space in the cache for a texture of the specified
- * dimensions.
- */
- void purgeCache(uint32_t width, uint32_t height);
-
- void initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height);
- void initPaint(SkPaint& paint);
-
- bool checkTextureSize(uint32_t width, uint32_t height);
-
- PathTexture* get(Entry entry) {
- return mCache.get(entry);
- }
-
- void removeTexture(PathTexture* texture);
-
- GenerationCache<Entry, PathTexture*> mCache;
- uint32_t mSize;
- uint32_t mMaxSize;
- GLuint mMaxTextureSize;
-
- char* mName;
- bool mDebugEnabled;
-
-private:
- /**
- * Generates the texture from a bitmap into the specified texture structure.
- */
- void generateTexture(SkBitmap& bitmap, Texture* texture);
-
- void init();
-}; // class ShapeCache
-
-class RoundRectShapeCache: public ShapeCache<RoundRectShapeCacheEntry> {
-public:
- RoundRectShapeCache();
-
- PathTexture* getRoundRect(float width, float height, float rx, float ry, SkPaint* paint);
-}; // class RoundRectShapeCache
-
-class CircleShapeCache: public ShapeCache<CircleShapeCacheEntry> {
-public:
- CircleShapeCache();
-
- PathTexture* getCircle(float radius, SkPaint* paint);
-}; // class CircleShapeCache
-
-class OvalShapeCache: public ShapeCache<OvalShapeCacheEntry> {
-public:
- OvalShapeCache();
-
- PathTexture* getOval(float width, float height, SkPaint* paint);
-}; // class OvalShapeCache
-
-class RectShapeCache: public ShapeCache<RectShapeCacheEntry> {
-public:
- RectShapeCache();
-
- PathTexture* getRect(float width, float height, SkPaint* paint);
-}; // class RectShapeCache
-
-class ArcShapeCache: public ShapeCache<ArcShapeCacheEntry> {
-public:
- ArcShapeCache();
-
- PathTexture* getArc(float width, float height, float startAngle, float sweepAngle,
- bool useCenter, SkPaint* paint);
-}; // class ArcShapeCache
-
-///////////////////////////////////////////////////////////////////////////////
-// Constructors/destructor
-///////////////////////////////////////////////////////////////////////////////
-
-template<class Entry>
-ShapeCache<Entry>::ShapeCache(const char* name, const char* propertyName, float defaultSize):
- mCache(GenerationCache<ShapeCacheEntry, PathTexture*>::kUnlimitedCapacity),
- mSize(0), mMaxSize(MB(defaultSize)) {
- char property[PROPERTY_VALUE_MAX];
- if (property_get(propertyName, property, NULL) > 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, defaultSize);
- }
-
- size_t len = strlen(name);
- mName = new char[len + 1];
- strcpy(mName, name);
- mName[len] = '\0';
-
- init();
-}
-
-template<class Entry>
-ShapeCache<Entry>::~ShapeCache() {
- mCache.clear();
- delete[] mName;
-}
-
-template<class Entry>
-void ShapeCache<Entry>::init() {
- mCache.setOnEntryRemovedListener(this);
-
- GLint maxTextureSize;
- glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
- mMaxTextureSize = maxTextureSize;
-
- mDebugEnabled = readDebugLevel() & kDebugCaches;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Size management
-///////////////////////////////////////////////////////////////////////////////
-
-template<class Entry>
-uint32_t ShapeCache<Entry>::getSize() {
- return mSize;
-}
-
-template<class Entry>
-uint32_t ShapeCache<Entry>::getMaxSize() {
- return mMaxSize;
-}
-
-template<class Entry>
-void ShapeCache<Entry>::setMaxSize(uint32_t maxSize) {
- mMaxSize = maxSize;
- while (mSize > mMaxSize) {
- mCache.removeOldest();
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Callbacks
-///////////////////////////////////////////////////////////////////////////////
-
-template<class Entry>
-void ShapeCache<Entry>::operator()(Entry& path, PathTexture*& texture) {
- removeTexture(texture);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Caching
-///////////////////////////////////////////////////////////////////////////////
-
-template<class Entry>
-void ShapeCache<Entry>::removeTexture(PathTexture* texture) {
- if (texture) {
- const uint32_t size = texture->width * texture->height;
- mSize -= size;
-
- SHAPE_LOGD("ShapeCache::callback: delete %s: name, size, mSize = %d, %d, %d",
- mName, texture->id, size, mSize);
- if (mDebugEnabled) {
- ALOGD("Shape %s deleted, size = %d", mName, size);
- }
-
- glDeleteTextures(1, &texture->id);
- delete texture;
- }
-}
-
-void computePathBounds(const SkPath* path, const SkPaint* paint,
- float& left, float& top, float& offset, uint32_t& width, uint32_t& height);
-void computeBounds(const SkRect& bounds, const SkPaint* paint,
- float& left, float& top, float& offset, uint32_t& width, uint32_t& height);
-
-static PathTexture* createTexture(float left, float top, float offset,
- uint32_t width, uint32_t height, uint32_t id) {
- PathTexture* texture = new PathTexture;
- texture->left = left;
- texture->top = top;
- texture->offset = offset;
- texture->width = width;
- texture->height = height;
- texture->generation = id;
- return texture;
-}
-
-template<class Entry>
-void ShapeCache<Entry>::purgeCache(uint32_t width, uint32_t height) {
- const uint32_t size = width * height;
- // Don't even try to cache a bitmap that's bigger than the cache
- if (size < mMaxSize) {
- while (mSize + size > mMaxSize) {
- mCache.removeOldest();
- }
- }
-}
-
-template<class Entry>
-void ShapeCache<Entry>::initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height) {
- bitmap.setConfig(SkBitmap::kA8_Config, width, height);
- bitmap.allocPixels();
- bitmap.eraseColor(0);
-}
-
-template<class Entry>
-void ShapeCache<Entry>::initPaint(SkPaint& paint) {
- // Make sure the paint is opaque, color, alpha, filter, etc.
- // will be applied later when compositing the alpha8 texture
- paint.setColor(0xff000000);
- paint.setAlpha(255);
- paint.setColorFilter(NULL);
- paint.setMaskFilter(NULL);
- paint.setShader(NULL);
- SkXfermode* mode = SkXfermode::Create(SkXfermode::kSrc_Mode);
- SkSafeUnref(paint.setXfermode(mode));
-}
-
-template<class Entry>
-bool ShapeCache<Entry>::checkTextureSize(uint32_t width, uint32_t height) {
- if (width > mMaxTextureSize || height > mMaxTextureSize) {
- ALOGW("Shape %s too large to be rendered into a texture (%dx%d, max=%dx%d)",
- mName, width, height, mMaxTextureSize, mMaxTextureSize);
- return false;
- }
- return true;
-}
-
-template<class Entry>
-PathTexture* ShapeCache<Entry>::addTexture(const Entry& entry, const SkPath *path,
- const SkPaint* paint) {
-
- float left, top, offset;
- uint32_t width, height;
- computePathBounds(path, paint, left, top, offset, width, height);
-
- if (!checkTextureSize(width, height)) return NULL;
-
- purgeCache(width, height);
-
- SkBitmap bitmap;
- initBitmap(bitmap, width, height);
-
- SkPaint pathPaint(*paint);
- initPaint(pathPaint);
-
- SkCanvas canvas(bitmap);
- canvas.translate(-left + offset, -top + offset);
- canvas.drawPath(*path, pathPaint);
-
- PathTexture* texture = createTexture(left, top, offset, width, height, path->getGenerationID());
- addTexture(entry, &bitmap, texture);
-
- return texture;
-}
-
-template<class Entry>
-void ShapeCache<Entry>::addTexture(const Entry& entry, SkBitmap* bitmap, PathTexture* texture) {
- generateTexture(*bitmap, texture);
-
- uint32_t size = texture->width * texture->height;
- if (size < mMaxSize) {
- mSize += size;
- SHAPE_LOGD("ShapeCache::get: create %s: name, size, mSize = %d, %d, %d",
- mName, texture->id, size, mSize);
- if (mDebugEnabled) {
- ALOGD("Shape %s created, size = %d", mName, size);
- }
- mCache.put(entry, texture);
- } else {
- texture->cleanup = true;
- }
-}
-
-template<class Entry>
-void ShapeCache<Entry>::clear() {
- mCache.clear();
-}
-
-template<class Entry>
-void ShapeCache<Entry>::generateTexture(SkBitmap& bitmap, Texture* texture) {
- SkAutoLockPixels alp(bitmap);
- if (!bitmap.readyToDraw()) {
- ALOGE("Cannot generate texture from bitmap");
- return;
- }
-
- glGenTextures(1, &texture->id);
-
- glBindTexture(GL_TEXTURE_2D, 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->setFilter(GL_LINEAR);
- texture->setWrap(GL_CLAMP_TO_EDGE);
-}
-
-}; // namespace uirenderer
-}; // namespace android
-
-#endif // ANDROID_HWUI_SHAPE_CACHE_H
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 9013fd5f9fed..c38eedbcf654 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -411,8 +411,14 @@ void SkiaComposeShader::describe(ProgramDescription& description, const Extensio
void SkiaComposeShader::setupProgram(Program* program, const mat4& modelView,
const Snapshot& snapshot, GLuint* textureUnit) {
- mFirst->setupProgram(program, modelView, snapshot, textureUnit);
- mSecond->setupProgram(program, modelView, snapshot, textureUnit);
+ // Apply this compose shader's local transform and pass it down to
+ // the child shaders. They will in turn apply their local transform
+ // to this matrix.
+ mat4 transform;
+ computeScreenSpaceMatrix(transform, modelView);
+
+ mFirst->setupProgram(program, transform, snapshot, textureUnit);
+ mSecond->setupProgram(program, transform, snapshot, textureUnit);
}
}; // namespace uirenderer
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index 26875928cf98..bc12b0dfed66 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -65,7 +65,7 @@ struct SkiaShader {
virtual void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
GLuint* textureUnit);
- inline SkShader *getSkShader() {
+ inline SkShader* getSkShader() {
return mKey;
}
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index fbc84554dcdf..d26ee3884433 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#define LOG_TAG "OpenGLRenderer"
+
#include "Snapshot.h"
#include <SkCanvas.h>
@@ -31,7 +33,7 @@ Snapshot::Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0),
transform = &mTransformRoot;
clipRect = &mClipRectRoot;
region = NULL;
- clipRegion = NULL;
+ clipRegion = &mClipRegionRoot;
}
/**
@@ -39,12 +41,10 @@ Snapshot::Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0),
* the previous snapshot.
*/
Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags):
- flags(0), previous(s), layer(NULL), fbo(s->fbo),
+ flags(0), previous(s), layer(s->layer), fbo(s->fbo),
invisible(s->invisible), empty(false),
viewport(s->viewport), height(s->height), alpha(s->alpha) {
- clipRegion = NULL;
-
if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
mTransformRoot.load(*s->transform);
transform = &mTransformRoot;
@@ -55,17 +55,13 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags):
if (saveFlags & SkCanvas::kClip_SaveFlag) {
mClipRectRoot.set(*s->clipRect);
clipRect = &mClipRectRoot;
-#if STENCIL_BUFFER_SIZE
- if (s->clipRegion) {
+ if (!s->clipRegion->isEmpty()) {
mClipRegionRoot.op(*s->clipRegion, SkRegion::kUnion_Op);
- clipRegion = &mClipRegionRoot;
}
-#endif
+ clipRegion = &mClipRegionRoot;
} else {
clipRect = s->clipRect;
-#if STENCIL_BUFFER_SIZE
clipRegion = s->clipRegion;
-#endif
}
if (s->flags & Snapshot::kFlagFboTarget) {
@@ -81,41 +77,38 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags):
///////////////////////////////////////////////////////////////////////////////
void Snapshot::ensureClipRegion() {
-#if STENCIL_BUFFER_SIZE
- if (!clipRegion) {
- clipRegion = &mClipRegionRoot;
+ if (clipRegion->isEmpty()) {
clipRegion->setRect(clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
}
-#endif
}
void Snapshot::copyClipRectFromRegion() {
-#if STENCIL_BUFFER_SIZE
if (!clipRegion->isEmpty()) {
const SkIRect& bounds = clipRegion->getBounds();
clipRect->set(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
if (clipRegion->isRect()) {
clipRegion->setEmpty();
- clipRegion = NULL;
}
} else {
clipRect->setEmpty();
- clipRegion = NULL;
}
-#endif
}
bool Snapshot::clipRegionOp(float left, float top, float right, float bottom, SkRegion::Op op) {
-#if STENCIL_BUFFER_SIZE
SkIRect tmp;
tmp.set(left, top, right, bottom);
clipRegion->op(tmp, op);
copyClipRectFromRegion();
return true;
-#else
- return false;
-#endif
+}
+
+bool Snapshot::clipRegionTransformed(const SkRegion& region, SkRegion::Op op) {
+ ensureClipRegion();
+ clipRegion->op(region, op);
+ copyClipRectFromRegion();
+ flags |= Snapshot::kFlagClipSet;
+ return true;
}
bool Snapshot::clip(float left, float top, float right, float bottom, SkRegion::Op op) {
@@ -129,7 +122,8 @@ bool Snapshot::clipTransformed(const Rect& r, SkRegion::Op op) {
switch (op) {
case SkRegion::kIntersect_Op: {
- if (CC_UNLIKELY(clipRegion)) {
+ if (CC_UNLIKELY(!clipRegion->isEmpty())) {
+ ensureClipRegion();
clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, SkRegion::kIntersect_Op);
} else {
clipped = clipRect->intersect(r);
@@ -140,14 +134,6 @@ bool Snapshot::clipTransformed(const Rect& r, SkRegion::Op op) {
}
break;
}
- case SkRegion::kUnion_Op: {
- if (CC_UNLIKELY(clipRegion)) {
- clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, SkRegion::kUnion_Op);
- } else {
- clipped = clipRect->unionWith(r);
- }
- break;
- }
case SkRegion::kReplace_Op: {
setClip(r.left, r.top, r.right, r.bottom);
clipped = true;
@@ -169,12 +155,9 @@ bool Snapshot::clipTransformed(const Rect& r, SkRegion::Op op) {
void Snapshot::setClip(float left, float top, float right, float bottom) {
clipRect->set(left, top, right, bottom);
-#if STENCIL_BUFFER_SIZE
- if (clipRegion) {
+ if (!clipRegion->isEmpty()) {
clipRegion->setEmpty();
- clipRegion = NULL;
}
-#endif
flags |= Snapshot::kFlagClipSet;
}
@@ -193,7 +176,11 @@ const Rect& Snapshot::getLocalClip() {
}
void Snapshot::resetClip(float left, float top, float right, float bottom) {
+ // TODO: This is incorrect, when we start rendering into a new layer,
+ // we may have to modify the previous snapshot's clip rect and clip
+ // region if the previous restore() call did not restore the clip
clipRect = &mClipRectRoot;
+ clipRegion = &mClipRegionRoot;
setClip(left, top, right, bottom);
}
@@ -214,5 +201,14 @@ bool Snapshot::isIgnored() const {
return invisible || empty;
}
+void Snapshot::dump() const {
+ ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d",
+ this, flags, previous.get(), height, isIgnored(), clipRegion && !clipRegion->isEmpty());
+ ALOGD(" ClipRect (at %p) %.1f %.1f %.1f %.1f",
+ clipRect, clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
+ ALOGD(" Transform (at %p):", transform);
+ transform->dump();
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 9c612ffb86f1..cc6d0cda003e 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -94,6 +94,12 @@ public:
bool 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);
+
+ /**
* Sets the current clip.
*/
void setClip(float left, float top, float right, float bottom);
@@ -136,7 +142,7 @@ public:
sp<Snapshot> previous;
/**
- * Only set when the flag kFlagIsLayer is set.
+ * A pointer to the currently active layer.
*
* This snapshot does not own the layer, this pointer must not be freed.
*/
@@ -200,8 +206,6 @@ public:
*
* This is a reference to a region owned by this snapshot or another
* snapshot. This pointer must not be freed. See ::mClipRegionRoot.
- *
- * This field is used only if STENCIL_BUFFER_SIZE is > 0.
*/
SkRegion* clipRegion;
@@ -224,6 +228,8 @@ public:
*/
float alpha;
+ void dump() const;
+
private:
void ensureClipRegion();
void copyClipRectFromRegion();
@@ -234,9 +240,7 @@ private:
Rect mClipRectRoot;
Rect mLocalClip;
-#if STENCIL_BUFFER_SIZE
SkRegion mClipRegionRoot;
-#endif
}; // class Snapshot
diff --git a/libs/hwui/Stencil.cpp b/libs/hwui/Stencil.cpp
index 84df82b7b08f..ba2e6f2670e3 100644
--- a/libs/hwui/Stencil.cpp
+++ b/libs/hwui/Stencil.cpp
@@ -14,14 +14,23 @@
* limitations under the License.
*/
-#include <GLES2/gl2.h>
-
+#include "Extensions.h"
#include "Properties.h"
#include "Stencil.h"
+#include <GLES2/gl2ext.h>
+
namespace android {
namespace uirenderer {
+#if DEBUG_STENCIL
+#define STENCIL_WRITE_VALUE 0xff
+#define STENCIL_MASK_VALUE 0xff
+#else
+#define STENCIL_WRITE_VALUE 0x1
+#define STENCIL_MASK_VALUE 0x1
+#endif
+
Stencil::Stencil(): mState(kDisabled) {
}
@@ -29,6 +38,18 @@ uint32_t Stencil::getStencilSize() {
return STENCIL_BUFFER_SIZE;
}
+GLenum Stencil::getSmallestStencilFormat() {
+#if !DEBUG_STENCIL
+ const Extensions& extensions = Extensions::getInstance();
+ if (extensions.has1BitStencil()) {
+ return GL_STENCIL_INDEX1_OES;
+ } else if (extensions.has4BitStencil()) {
+ return GL_STENCIL_INDEX4_OES;
+ }
+#endif
+ return GL_STENCIL_INDEX8;
+}
+
void Stencil::clear() {
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
@@ -37,7 +58,7 @@ void Stencil::clear() {
void Stencil::enableTest() {
if (mState != kTest) {
enable();
- glStencilFunc(GL_EQUAL, 0x1, 0x1);
+ glStencilFunc(GL_EQUAL, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
// We only want to test, let's keep everything
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
@@ -48,7 +69,7 @@ void Stencil::enableTest() {
void Stencil::enableWrite() {
if (mState != kWrite) {
enable();
- glStencilFunc(GL_ALWAYS, 0x1, 0x1);
+ glStencilFunc(GL_ALWAYS, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
// The test always passes so the first two values are meaningless
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
diff --git a/libs/hwui/Stencil.h b/libs/hwui/Stencil.h
index 2f8a66a54693..83ad6681a7f8 100644
--- a/libs/hwui/Stencil.h
+++ b/libs/hwui/Stencil.h
@@ -21,6 +21,8 @@
#define LOG_TAG "OpenGLRenderer"
#endif
+#include <GLES2/gl2.h>
+
#include <cutils/compiler.h>
namespace android {
@@ -41,6 +43,11 @@ public:
ANDROID_API static uint32_t getStencilSize();
/**
+ * Returns the smallest stencil format accepted by render buffers.
+ */
+ static GLenum getSmallestStencilFormat();
+
+ /**
* Clears the stencil buffer.
*/
void clear();
@@ -80,6 +87,13 @@ public:
return mState != kDisabled;
}
+ /**
+ * Indicates whether testing only is enabled.
+ */
+ bool isTestEnabled() {
+ return mState == kTest;
+ }
+
private:
void enable();
diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp
index 8426f5869189..f1f35bd1711c 100644
--- a/libs/hwui/TextDropShadowCache.cpp
+++ b/libs/hwui/TextDropShadowCache.cpp
@@ -16,6 +16,8 @@
#define LOG_TAG "OpenGLRenderer"
+#include <utils/JenkinsHash.h>
+
#include "Debug.h"
#include "TextDropShadowCache.h"
#include "Properties.h"
@@ -24,11 +26,75 @@ namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
+// Cache support
+///////////////////////////////////////////////////////////////////////////////
+
+hash_t ShadowText::hash() const {
+ uint32_t charCount = len / sizeof(char16_t);
+ uint32_t hash = JenkinsHashMix(0, len);
+ 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) {
+ hash = JenkinsHashMixShorts(hash, text, charCount);
+ }
+ if (positions) {
+ for (uint32_t i = 0; i < charCount * 2; i++) {
+ hash = JenkinsHashMix(hash, android::hash_type(positions[i]));
+ }
+ }
+ return JenkinsHashWhiten(hash);
+}
+
+int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) {
+ int deltaInt = int(lhs.len) - int(rhs.len);
+ if (deltaInt != 0) return deltaInt;
+
+ deltaInt = lhs.flags - rhs.flags;
+ if (deltaInt != 0) return deltaInt;
+
+ if (lhs.radius < rhs.radius) return -1;
+ if (lhs.radius > rhs.radius) return +1;
+
+ if (lhs.typeface < rhs.typeface) return -1;
+ if (lhs.typeface > rhs.typeface) return +1;
+
+ if (lhs.textSize < rhs.textSize) return -1;
+ if (lhs.textSize > rhs.textSize) return +1;
+
+ if (lhs.italicStyle < rhs.italicStyle) return -1;
+ if (lhs.italicStyle > rhs.italicStyle) return +1;
+
+ 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;
+
+ deltaInt = memcmp(lhs.text, rhs.text, lhs.len);
+ if (deltaInt != 0) return deltaInt;
+ }
+
+ if (lhs.positions != rhs.positions) {
+ if (!lhs.positions) return -1;
+ if (!rhs.positions) return +1;
+
+ return memcmp(lhs.positions, rhs.positions, lhs.len << 2);
+ }
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
TextDropShadowCache::TextDropShadowCache():
- mCache(GenerationCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity),
+ 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, NULL) > 0) {
@@ -43,7 +109,7 @@ TextDropShadowCache::TextDropShadowCache():
}
TextDropShadowCache::TextDropShadowCache(uint32_t maxByteSize):
- mCache(GenerationCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity),
+ mCache(LruCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity),
mSize(0), mMaxSize(maxByteSize) {
init();
}
@@ -102,7 +168,7 @@ void TextDropShadowCache::clear() {
}
ShadowTexture* TextDropShadowCache::get(SkPaint* paint, const char* text, uint32_t len,
- int numGlyphs, uint32_t radius, const float* positions) {
+ int numGlyphs, float radius, const float* positions) {
ShadowText entry(paint, radius, len, text, positions);
ShadowTexture* texture = mCache.get(entry);
@@ -156,7 +222,7 @@ ShadowTexture* TextDropShadowCache::get(SkPaint* paint, const char* text, uint32
}
// Cleanup shadow
- delete[] shadow.image;
+ free(shadow.image);
}
return texture;
diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h
index bae0c49349f6..0bed72b6b03f 100644
--- a/libs/hwui/TextDropShadowCache.h
+++ b/libs/hwui/TextDropShadowCache.h
@@ -21,10 +21,9 @@
#include <SkPaint.h>
+#include <utils/LruCache.h>
#include <utils/String16.h>
-#include "utils/Compare.h"
-#include "utils/GenerationCache.h"
#include "FontRenderer.h"
#include "Texture.h"
@@ -32,12 +31,14 @@ namespace android {
namespace uirenderer {
struct ShadowText {
- ShadowText(): radius(0), len(0), textSize(0.0f), typeface(NULL) {
+ ShadowText(): len(0), radius(0.0f), textSize(0.0f), typeface(NULL),
+ flags(0), italicStyle(0.0f), scaleX(0), text(NULL), positions(NULL) {
}
- ShadowText(SkPaint* paint, uint32_t radius, uint32_t len, const char* srcText,
+ // len is the number of bytes in text
+ ShadowText(SkPaint* paint, float radius, uint32_t len, const char* srcText,
const float* positions):
- radius(radius), len(len), positions(positions) {
+ len(len), radius(radius), positions(positions) {
// TODO: Propagate this through the API, we should not cast here
text = (const char16_t*) srcText;
@@ -49,63 +50,66 @@ struct ShadowText {
flags |= Font::kFakeBold;
}
- const float skewX = paint->getTextSkewX();
- italicStyle = *(uint32_t*) &skewX;
-
- const float scaleXFloat = paint->getTextScaleX();
- scaleX = *(uint32_t*) &scaleXFloat;
+ italicStyle = paint->getTextSkewX();
+ scaleX = paint->getTextScaleX();
}
~ShadowText() {
}
- uint32_t radius;
- uint32_t len;
- float textSize;
- SkTypeface* typeface;
- uint32_t flags;
- uint32_t italicStyle;
- uint32_t scaleX;
- const char16_t* text;
- const float* positions;
- String16 str;
- Vector<float> positionsCopy;
+ hash_t hash() const;
+
+ static int compare(const ShadowText& lhs, const ShadowText& rhs);
+
+ bool operator==(const ShadowText& other) const {
+ return compare(*this, other) == 0;
+ }
+
+ bool operator!=(const ShadowText& other) const {
+ return compare(*this, other) != 0;
+ }
void copyTextLocally() {
- str.setTo((const char16_t*) text, len >> 1);
+ uint32_t charCount = len / sizeof(char16_t);
+ str.setTo((const char16_t*) text, charCount);
text = str.string();
if (positions != NULL) {
positionsCopy.clear();
- positionsCopy.appendArray(positions, len);
+ positionsCopy.appendArray(positions, charCount * 2);
positions = positionsCopy.array();
}
}
- bool operator<(const ShadowText& rhs) const {
- LTE_INT(len) {
- LTE_INT(radius) {
- LTE_FLOAT(textSize) {
- LTE_INT(typeface) {
- LTE_INT(flags) {
- LTE_INT(italicStyle) {
- LTE_INT(scaleX) {
- int cmp = memcmp(text, rhs.text, len);
- if (cmp < 0) return true;
- if (cmp == 0 && rhs.positions != NULL) {
- if (positions == NULL) return true;
- return memcmp(positions, rhs.positions, len << 2) < 0;
- }
- }
- }
- }
- }
- }
- }
- }
- return false;
- }
+ uint32_t len;
+ float radius;
+ float textSize;
+ SkTypeface* typeface;
+ uint32_t flags;
+ float italicStyle;
+ float scaleX;
+ const char16_t* text;
+ const float* positions;
+
+ // Not directly used to compute the cache key
+ String16 str;
+ Vector<float> positionsCopy;
+
}; // struct ShadowText
+// Caching support
+
+inline int strictly_order_type(const ShadowText& lhs, const ShadowText& rhs) {
+ return ShadowText::compare(lhs, rhs) < 0;
+}
+
+inline int compare_type(const ShadowText& lhs, const ShadowText& rhs) {
+ return ShadowText::compare(lhs, rhs);
+}
+
+inline hash_t hash_type(const ShadowText& entry) {
+ return entry.hash();
+}
+
/**
* Alpha texture used to represent a shadow.
*/
@@ -130,7 +134,7 @@ public:
void operator()(ShadowText& text, ShadowTexture*& texture);
ShadowTexture* get(SkPaint* paint, const char* text, uint32_t len,
- int numGlyphs, uint32_t radius, const float* positions);
+ int numGlyphs, float radius, const float* positions);
/**
* Clears the cache. This causes all textures to be deleted.
@@ -157,7 +161,7 @@ public:
private:
void init();
- GenerationCache<ShadowText, ShadowTexture*> mCache;
+ LruCache<ShadowText, ShadowTexture*> mCache;
uint32_t mSize;
uint32_t mMaxSize;
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 10d112a1e068..2378eb52632d 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -20,7 +20,7 @@
#include <SkCanvas.h>
-#include <utils/threads.h>
+#include <utils/Mutex.h>
#include "Caches.h"
#include "TextureCache.h"
@@ -34,7 +34,7 @@ namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
TextureCache::TextureCache():
- mCache(GenerationCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
+ mCache(LruCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
mSize(0), mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE)),
mFlushRate(DEFAULT_TEXTURE_CACHE_FLUSH_RATE) {
char property[PROPERTY_VALUE_MAX];
@@ -58,7 +58,7 @@ TextureCache::TextureCache():
}
TextureCache::TextureCache(uint32_t maxByteSize):
- mCache(GenerationCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
+ mCache(LruCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
mSize(0), mMaxSize(maxByteSize) {
init();
}
@@ -219,7 +219,7 @@ void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool rege
// 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();
+ const bool canMipMap = Extensions::getInstance().hasNPot();
// If the texture had mipmap enabled but not anymore,
// force a glTexImage2D to discard the mipmap levels
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 31a2e3d7e81f..80bb22e27f09 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -19,12 +19,12 @@
#include <SkBitmap.h>
+#include <utils/LruCache.h>
#include <utils/Mutex.h>
#include <utils/Vector.h>
#include "Debug.h"
#include "Texture.h"
-#include "utils/GenerationCache.h"
namespace android {
namespace uirenderer {
@@ -130,7 +130,7 @@ private:
void init();
- GenerationCache<SkBitmap*, Texture*> mCache;
+ LruCache<SkBitmap*, Texture*> mCache;
uint32_t mSize;
uint32_t mMaxSize;
diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h
index 38455dc93274..523120e358ba 100644
--- a/libs/hwui/Vertex.h
+++ b/libs/hwui/Vertex.h
@@ -33,7 +33,7 @@ struct Vertex {
}; // struct Vertex
/**
- * Simple structure to describe a vertex with a position and a texture.
+ * Simple structure to describe a vertex with a position and texture UV.
*/
struct TextureVertex {
float position[2];
@@ -53,37 +53,36 @@ struct TextureVertex {
}; // struct TextureVertex
/**
- * Simple structure to describe a vertex with a position and an alpha value.
+ * Simple structure to describe a vertex with a position, texture UV and ARGB color.
*/
-struct AlphaVertex : Vertex {
- float alpha;
+struct ColorTextureVertex : TextureVertex {
+ float color[4];
- static inline void set(AlphaVertex* vertex, float x, float y, float alpha) {
- Vertex::set(vertex, x, y);
- vertex[0].alpha = alpha;
- }
+ static inline void set(ColorTextureVertex* vertex, float x, float y,
+ float u, float v, int color) {
+ TextureVertex::set(vertex, x, y, u, v);
- static inline void setColor(AlphaVertex* vertex, float alpha) {
- vertex[0].alpha = alpha;
+ const float a = ((color >> 24) & 0xff) / 255.0f;
+ vertex[0].color[0] = a * ((color >> 16) & 0xff) / 255.0f;
+ vertex[0].color[1] = a * ((color >> 8) & 0xff) / 255.0f;
+ vertex[0].color[2] = a * ((color ) & 0xff) / 255.0f;
+ vertex[0].color[3] = a;
}
-}; // struct AlphaVertex
+}; // struct ColorTextureVertex
/**
* Simple structure to describe a vertex with a position and an alpha value.
*/
-struct AAVertex : Vertex {
- float width;
- float length;
+struct AlphaVertex : Vertex {
+ float alpha;
- static inline void set(AAVertex* vertex, float x, float y, float width, float length) {
+ static inline void set(AlphaVertex* vertex, float x, float y, float alpha) {
Vertex::set(vertex, x, y);
- vertex[0].width = width;
- vertex[0].length = length;
+ vertex[0].alpha = alpha;
}
- static inline void setColor(AAVertex* vertex, float width, float length) {
- vertex[0].width = width;
- vertex[0].length = length;
+ static inline void setColor(AlphaVertex* vertex, float alpha) {
+ vertex[0].alpha = alpha;
}
}; // struct AlphaVertex
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index f65359217768..577f46389fdb 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-#include <utils/Log.h>
+#include <SkGlyph.h>
-#include "Debug.h"
#include "CacheTexture.h"
+#include "../Debug.h"
namespace android {
namespace uirenderer {
@@ -105,6 +105,84 @@ CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove)
// CacheTexture
///////////////////////////////////////////////////////////////////////////////
+CacheTexture::CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount) :
+ mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height),
+ mLinearFiltering(false), mDirty(false), mNumGlyphs(0),
+ mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount) {
+ mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
+ mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
+}
+
+CacheTexture::~CacheTexture() {
+ releaseMesh();
+ releaseTexture();
+ reset();
+}
+
+void CacheTexture::reset() {
+ // Delete existing cache blocks
+ while (mCacheBlocks != NULL) {
+ CacheBlock* tmpBlock = mCacheBlocks;
+ mCacheBlocks = mCacheBlocks->mNext;
+ delete tmpBlock;
+ }
+ mNumGlyphs = 0;
+ mCurrentQuad = 0;
+}
+
+void CacheTexture::init() {
+ // reset, then create a new remainder space to start again
+ reset();
+ mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
+ mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
+}
+
+void CacheTexture::releaseMesh() {
+ delete[] mMesh;
+}
+
+void CacheTexture::releaseTexture() {
+ if (mTexture) {
+ delete[] mTexture;
+ mTexture = NULL;
+ }
+ if (mTextureId) {
+ glDeleteTextures(1, &mTextureId);
+ mTextureId = 0;
+ }
+ mDirty = false;
+ mCurrentQuad = 0;
+}
+
+void CacheTexture::allocateMesh() {
+ if (!mMesh) {
+ mMesh = new TextureVertex[mMaxQuadCount * 4];
+ }
+}
+
+void CacheTexture::allocateTexture() {
+ if (!mTexture) {
+ mTexture = new uint8_t[mWidth * mHeight];
+ }
+
+ if (!mTextureId) {
+ glGenTextures(1, &mTextureId);
+
+ glBindTexture(GL_TEXTURE_2D, mTextureId);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ // Initialize texture dimensions
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mWidth, mHeight, 0,
+ GL_ALPHA, GL_UNSIGNED_BYTE, 0);
+
+ 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);
+ }
+}
+
bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) {
return false;
diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h
index fdd16232bc41..e7fb4746492a 100644
--- a/libs/hwui/font/CacheTexture.h
+++ b/libs/hwui/font/CacheTexture.h
@@ -17,14 +17,15 @@
#ifndef ANDROID_HWUI_CACHE_TEXTURE_H
#define ANDROID_HWUI_CACHE_TEXTURE_H
-#include <GLES2/gl2.h>
+#include <GLES3/gl3.h>
#include <SkScalerContext.h>
#include <utils/Log.h>
#include "FontUtil.h"
-#include "Rect.h"
+#include "../Rect.h"
+#include "../Vertex.h"
namespace android {
namespace uirenderer {
@@ -55,14 +56,14 @@ struct CacheBlock {
}
static CacheBlock* insertBlock(CacheBlock* head, CacheBlock* newBlock);
-
static CacheBlock* removeBlock(CacheBlock* head, CacheBlock* blockToRemove);
void output() {
CacheBlock* currBlock = this;
while (currBlock) {
ALOGD("Block: this, x, y, w, h = %p, %d, %d, %d, %d",
- currBlock, currBlock->mX, currBlock->mY, currBlock->mWidth, currBlock->mHeight);
+ currBlock, currBlock->mX, currBlock->mY,
+ currBlock->mWidth, currBlock->mHeight);
currBlock = currBlock->mNext;
}
}
@@ -70,72 +71,17 @@ struct CacheBlock {
class CacheTexture {
public:
- CacheTexture(uint16_t width, uint16_t height) :
- mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height),
- mLinearFiltering(false), mDirty(false), mNumGlyphs(0) {
- mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
- mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
- }
+ CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount);
+ ~CacheTexture();
- ~CacheTexture() {
- releaseTexture();
- reset();
- }
-
- void reset() {
- // Delete existing cache blocks
- while (mCacheBlocks != NULL) {
- CacheBlock* tmpBlock = mCacheBlocks;
- mCacheBlocks = mCacheBlocks->mNext;
- delete tmpBlock;
- }
- mNumGlyphs = 0;
- }
+ void reset();
+ void init();
- void init() {
- // reset, then create a new remainder space to start again
- reset();
- mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
- mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
- }
+ void releaseMesh();
+ void releaseTexture();
- void releaseTexture() {
- if (mTexture) {
- delete[] mTexture;
- mTexture = NULL;
- }
- if (mTextureId) {
- glDeleteTextures(1, &mTextureId);
- mTextureId = 0;
- }
- mDirty = false;
- }
-
- /**
- * This method assumes that the proper texture unit is active.
- */
- void allocateTexture() {
- if (!mTexture) {
- mTexture = new uint8_t[mWidth * mHeight];
- }
-
- if (!mTextureId) {
- glGenTextures(1, &mTextureId);
-
- glBindTexture(GL_TEXTURE_2D, mTextureId);
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
- // Initialize texture dimensions
- glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mWidth, mHeight, 0,
- GL_ALPHA, GL_UNSIGNED_BYTE, 0);
-
- 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);
- }
- }
+ void allocateTexture();
+ void allocateMesh();
bool fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY);
@@ -193,6 +139,42 @@ public:
return mNumGlyphs;
}
+ TextureVertex* mesh() const {
+ return mMesh;
+ }
+
+ uint32_t meshElementCount() const {
+ return mCurrentQuad * 6;
+ }
+
+ uint16_t* indices() const {
+ return (uint16_t*) 0;
+ }
+
+ void resetMesh() {
+ mCurrentQuad = 0;
+ }
+
+ inline void addQuad(float x1, float y1, float u1, float v1,
+ float x2, float y2, float u2, float v2,
+ float x3, float y3, float u3, float v3,
+ float x4, float y4, float u4, float v4) {
+ TextureVertex* mesh = mMesh + mCurrentQuad * 4;
+ TextureVertex::set(mesh++, x1, y1, u1, v1);
+ TextureVertex::set(mesh++, x2, y2, u2, v2);
+ TextureVertex::set(mesh++, x3, y3, u3, v3);
+ TextureVertex::set(mesh++, x4, y4, u4, v4);
+ mCurrentQuad++;
+ }
+
+ bool canDraw() const {
+ return mCurrentQuad > 0;
+ }
+
+ bool endOfMesh() const {
+ return mCurrentQuad == mMaxQuadCount;
+ }
+
private:
uint8_t* mTexture;
GLuint mTextureId;
@@ -201,6 +183,9 @@ private:
bool mLinearFiltering;
bool mDirty;
uint16_t mNumGlyphs;
+ TextureVertex* mMesh;
+ uint32_t mCurrentQuad;
+ uint32_t mMaxQuadCount;
CacheBlock* mCacheBlocks;
Rect mDirtyRect;
};
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index 7bfa63d850c1..02c1aa191e5c 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -15,9 +15,14 @@
*/
#define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
#include <cutils/compiler.h>
+#include <utils/JenkinsHash.h>
+#include <utils/Trace.h>
+
+#include <SkGlyph.h>
#include <SkUtils.h>
#include "Debug.h"
@@ -33,14 +38,29 @@ namespace uirenderer {
// Font
///////////////////////////////////////////////////////////////////////////////
-Font::Font(FontRenderer* state, uint32_t fontId, float fontSize,
- int flags, uint32_t italicStyle, uint32_t scaleX,
- SkPaint::Style style, uint32_t strokeWidth) :
- mState(state), mFontId(fontId), mFontSize(fontSize),
- mFlags(flags), mItalicStyle(italicStyle), mScaleX(scaleX),
- mStyle(style), mStrokeWidth(mStrokeWidth) {
+Font::Font(FontRenderer* state, const Font::FontDescription& desc) :
+ mState(state), mDescription(desc) {
}
+Font::FontDescription::FontDescription(const SkPaint* paint, const mat4& matrix) {
+ mFontId = SkTypeface::UniqueID(paint->getTypeface());
+ mFontSize = paint->getTextSize();
+ mFlags = 0;
+ if (paint->isFakeBoldText()) {
+ mFlags |= Font::kFakeBold;
+ }
+ mItalicStyle = paint->getTextSkewX();
+ mScaleX = paint->getTextScaleX();
+ mStyle = paint->getStyle();
+ mStrokeWidth = paint->getStrokeWidth();
+ mAntiAliasing = paint->isAntiAlias();
+ mLookupTransform.reset();
+ mLookupTransform[SkMatrix::kMScaleX] = roundf(fmaxf(1.0f, matrix[mat4::kScaleX]));
+ mLookupTransform[SkMatrix::kMScaleY] = roundf(fmaxf(1.0f, matrix[mat4::kScaleY]));
+ if (!mLookupTransform.invert(&mInverseLookupTransform)) {
+ ALOGW("Could not query the inverse lookup transform for this font");
+ }
+}
Font::~Font() {
mState->removeFont(this);
@@ -50,6 +70,59 @@ Font::~Font() {
}
}
+hash_t Font::FontDescription::hash() const {
+ uint32_t hash = JenkinsHashMix(0, mFontId);
+ hash = JenkinsHashMix(hash, android::hash_type(mFontSize));
+ hash = JenkinsHashMix(hash, android::hash_type(mFlags));
+ hash = JenkinsHashMix(hash, android::hash_type(mItalicStyle));
+ hash = JenkinsHashMix(hash, android::hash_type(mScaleX));
+ hash = JenkinsHashMix(hash, android::hash_type(mStyle));
+ hash = JenkinsHashMix(hash, android::hash_type(mStrokeWidth));
+ hash = JenkinsHashMix(hash, int(mAntiAliasing));
+ hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleX]));
+ hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleY]));
+ return JenkinsHashWhiten(hash);
+}
+
+int Font::FontDescription::compare(const Font::FontDescription& lhs,
+ const Font::FontDescription& rhs) {
+ int deltaInt = int(lhs.mFontId) - int(rhs.mFontId);
+ if (deltaInt != 0) return deltaInt;
+
+ if (lhs.mFontSize < rhs.mFontSize) return -1;
+ if (lhs.mFontSize > rhs.mFontSize) return +1;
+
+ if (lhs.mItalicStyle < rhs.mItalicStyle) return -1;
+ if (lhs.mItalicStyle > rhs.mItalicStyle) return +1;
+
+ deltaInt = int(lhs.mFlags) - int(rhs.mFlags);
+ if (deltaInt != 0) return deltaInt;
+
+ if (lhs.mScaleX < rhs.mScaleX) return -1;
+ if (lhs.mScaleX > rhs.mScaleX) return +1;
+
+ deltaInt = int(lhs.mStyle) - int(rhs.mStyle);
+ if (deltaInt != 0) return deltaInt;
+
+ if (lhs.mStrokeWidth < rhs.mStrokeWidth) return -1;
+ if (lhs.mStrokeWidth > rhs.mStrokeWidth) return +1;
+
+ deltaInt = int(lhs.mAntiAliasing) - int(rhs.mAntiAliasing);
+ if (deltaInt != 0) return deltaInt;
+
+ if (lhs.mLookupTransform[SkMatrix::kMScaleX] <
+ rhs.mLookupTransform[SkMatrix::kMScaleX]) return -1;
+ if (lhs.mLookupTransform[SkMatrix::kMScaleX] >
+ rhs.mLookupTransform[SkMatrix::kMScaleX]) return +1;
+
+ if (lhs.mLookupTransform[SkMatrix::kMScaleY] <
+ rhs.mLookupTransform[SkMatrix::kMScaleY]) return -1;
+ if (lhs.mLookupTransform[SkMatrix::kMScaleY] >
+ rhs.mLookupTransform[SkMatrix::kMScaleY]) return +1;
+
+ return 0;
+}
+
void Font::invalidateTextureCache(CacheTexture* cacheTexture) {
for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueAt(i);
@@ -83,23 +156,50 @@ void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
- int nPenX = x + glyph->mBitmapLeft;
- int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
+ float nPenX = x + glyph->mBitmapLeft;
+ float nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
+
+ float width = (float) glyph->mBitmapWidth;
+ float height = (float) glyph->mBitmapHeight;
float u1 = glyph->mBitmapMinU;
float u2 = glyph->mBitmapMaxU;
float v1 = glyph->mBitmapMinV;
float v2 = glyph->mBitmapMaxV;
- int width = (int) glyph->mBitmapWidth;
- int height = (int) glyph->mBitmapHeight;
-
mState->appendMeshQuad(nPenX, nPenY, u1, v2,
nPenX + width, nPenY, u2, v2,
nPenX + width, nPenY - height, u2, v1,
nPenX, nPenY - height, u1, v1, glyph->mCacheTexture);
}
+void Font::drawCachedGlyphTransformed(CachedGlyphInfo* glyph, int x, int y,
+ uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
+ SkPoint p[4];
+ p[0].iset(glyph->mBitmapLeft, glyph->mBitmapTop + glyph->mBitmapHeight);
+ p[1].iset(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop + glyph->mBitmapHeight);
+ p[2].iset(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop);
+ p[3].iset(glyph->mBitmapLeft, glyph->mBitmapTop);
+
+ mDescription.mInverseLookupTransform.mapPoints(p, 4);
+
+ p[0].offset(x, y);
+ p[1].offset(x, y);
+ p[2].offset(x, y);
+ p[3].offset(x, y);
+
+ float u1 = glyph->mBitmapMinU;
+ float u2 = glyph->mBitmapMaxU;
+ float v1 = glyph->mBitmapMinV;
+ float v2 = glyph->mBitmapMaxV;
+
+ mState->appendRotatedMeshQuad(
+ p[0].x(), p[0].y(), u1, v2,
+ p[1].x(), p[1].y(), u2, v2,
+ p[2].x(), p[2].y(), u2, v1,
+ p[3].x(), p[3].y(), u1, v1, glyph->mCacheTexture);
+}
+
void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
int nPenX = x + glyph->mBitmapLeft;
@@ -116,12 +216,6 @@ void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
int32_t bX = 0, bY = 0;
for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
-#if DEBUG_FONT_RENDERER
- if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) {
- ALOGE("Skipping invalid index");
- continue;
- }
-#endif
uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
bitmap[bY * bitmapW + bX] = tempCol;
}
@@ -136,7 +230,10 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float
vOffset += glyph->mBitmapTop + height;
SkPoint destination[4];
- measure.getPosTan(x + hOffset + glyph->mBitmapLeft + halfWidth, position, tangent);
+ bool ok = measure.getPosTan(x + hOffset + glyph->mBitmapLeft + halfWidth, position, tangent);
+ if (!ok) {
+ ALOGW("The path for drawTextOnPath is empty or null");
+ }
// Move along the tangent and offset by the normal
destination[0].set(-tangent->fX * halfWidth - tangent->fY * vOffset,
@@ -154,46 +251,33 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float
const float v2 = glyph->mBitmapMaxV;
mState->appendRotatedMeshQuad(
- position->fX + destination[0].fX,
- position->fY + destination[0].fY, u1, v2,
- position->fX + destination[1].fX,
- position->fY + destination[1].fY, u2, v2,
- position->fX + destination[2].fX,
- position->fY + destination[2].fY, u2, v1,
- position->fX + destination[3].fX,
- position->fY + destination[3].fY, u1, v1,
+ position->x() + destination[0].x(),
+ position->y() + destination[0].y(), u1, v2,
+ position->x() + destination[1].x(),
+ position->y() + destination[1].y(), u2, v2,
+ position->x() + destination[2].x(),
+ position->y() + destination[2].y(), u2, v1,
+ position->x() + destination[3].x(),
+ position->y() + destination[3].y(), u1, v1,
glyph->mCacheTexture);
}
CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching) {
- CachedGlyphInfo* cachedGlyph = NULL;
- ssize_t index = mCachedGlyphs.indexOfKey(textUnit);
- if (index >= 0) {
- cachedGlyph = mCachedGlyphs.valueAt(index);
+ CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(textUnit);
+ if (cachedGlyph) {
+ // Is the glyph still in texture cache?
+ if (!cachedGlyph->mIsValid) {
+ const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit,
+ &mDescription.mLookupTransform);
+ updateGlyphCache(paint, skiaGlyph, cachedGlyph, precaching);
+ }
} else {
cachedGlyph = cacheGlyph(paint, textUnit, precaching);
}
- // Is the glyph still in texture cache?
- if (!cachedGlyph->mIsValid) {
- const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit);
- updateGlyphCache(paint, skiaGlyph, cachedGlyph, precaching);
- }
-
return cachedGlyph;
}
-void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
- int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
- if (bitmap != NULL && bitmapW > 0 && bitmapH > 0) {
- render(paint, text, start, len, numGlyphs, x, y, BITMAP, bitmap,
- bitmapW, bitmapH, NULL, NULL);
- } else {
- render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL,
- 0, 0, NULL, NULL);
- }
-}
-
void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
int numGlyphs, int x, int y, const float* positions) {
render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL,
@@ -240,7 +324,7 @@ void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len
penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
prevRsbDelta = cachedGlyph->mRsbDelta;
- if (cachedGlyph->mIsValid) {
+ if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
drawCachedGlyph(cachedGlyph, penX, hOffset, vOffset, measure, &position, &tangent);
}
@@ -261,12 +345,13 @@ void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t le
}
void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
+ ATRACE_NAME("precacheText");
if (numGlyphs == 0 || text == NULL) {
return;
}
- int glyphsCount = 0;
+ int glyphsCount = 0;
while (glyphsCount < numGlyphs) {
glyph_t glyph = GET_GLYPH(text);
@@ -276,7 +361,6 @@ void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
}
CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph, true);
-
glyphsCount++;
}
}
@@ -290,79 +374,40 @@ void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len
static RenderGlyph gRenderGlyph[] = {
&android::uirenderer::Font::drawCachedGlyph,
+ &android::uirenderer::Font::drawCachedGlyphTransformed,
&android::uirenderer::Font::drawCachedGlyphBitmap,
+ &android::uirenderer::Font::drawCachedGlyphBitmap,
+ &android::uirenderer::Font::measureCachedGlyph,
&android::uirenderer::Font::measureCachedGlyph
};
- RenderGlyph render = gRenderGlyph[mode];
+ RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform];
text += start;
int glyphsCount = 0;
- if (CC_LIKELY(positions == NULL)) {
- SkFixed prevRsbDelta = 0;
-
- float penX = x + 0.5f;
- int penY = y;
+ const SkPaint::Align align = paint->getTextAlign();
- while (glyphsCount < numGlyphs) {
- glyph_t glyph = GET_GLYPH(text);
-
- // Reached the end of the string
- if (IS_END_OF_STRING(glyph)) {
- break;
- }
+ while (glyphsCount < numGlyphs) {
+ glyph_t glyph = GET_GLYPH(text);
- CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
- penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
- prevRsbDelta = cachedGlyph->mRsbDelta;
+ // Reached the end of the string
+ if (IS_END_OF_STRING(glyph)) {
+ break;
+ }
- // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
- if (cachedGlyph->mIsValid) {
- (*this.*render)(cachedGlyph, (int) floorf(penX), penY,
- bitmap, bitmapW, bitmapH, bounds, positions);
- }
+ CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
- penX += SkFixedToFloat(cachedGlyph->mAdvanceX);
+ // If it's still not valid, we couldn't cache it, so we shouldn't
+ // draw garbage; also skip empty glyphs (spaces)
+ if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
+ float penX = x + positions[(glyphsCount << 1)];
+ float penY = y + positions[(glyphsCount << 1) + 1];
- glyphsCount++;
- }
- } else {
- const SkPaint::Align align = paint->getTextAlign();
-
- // This is for renderPosText()
- while (glyphsCount < numGlyphs) {
- glyph_t glyph = GET_GLYPH(text);
-
- // Reached the end of the string
- if (IS_END_OF_STRING(glyph)) {
- break;
- }
-
- CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
-
- // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
- if (cachedGlyph->mIsValid) {
- int penX = x + positions[(glyphsCount << 1)];
- int penY = y + positions[(glyphsCount << 1) + 1];
-
- switch (align) {
- case SkPaint::kRight_Align:
- penX -= SkFixedToFloat(cachedGlyph->mAdvanceX);
- penY -= SkFixedToFloat(cachedGlyph->mAdvanceY);
- break;
- case SkPaint::kCenter_Align:
- penX -= SkFixedToFloat(cachedGlyph->mAdvanceX >> 1);
- penY -= SkFixedToFloat(cachedGlyph->mAdvanceY >> 1);
- default:
- break;
- }
-
- (*this.*render)(cachedGlyph, penX, penY,
- bitmap, bitmapW, bitmapH, bounds, positions);
- }
-
- glyphsCount++;
+ (*this.*render)(cachedGlyph, roundf(penX), roundf(penY),
+ bitmap, bitmapW, bitmapH, bounds, positions);
}
+
+ glyphsCount++;
}
}
@@ -379,7 +424,9 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp
uint32_t startY = 0;
// Get the bitmap for the glyph
- paint->findImage(skiaGlyph);
+ if (!skiaGlyph.fImage) {
+ paint->findImage(skiaGlyph, &mDescription.mLookupTransform);
+ }
mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY, precaching);
if (!glyph->mIsValid) {
@@ -394,49 +441,44 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp
glyph->mBitmapWidth = skiaGlyph.fWidth;
glyph->mBitmapHeight = skiaGlyph.fHeight;
- uint32_t cacheWidth = glyph->mCacheTexture->getWidth();
- uint32_t cacheHeight = glyph->mCacheTexture->getHeight();
+ bool empty = skiaGlyph.fWidth == 0 || skiaGlyph.fHeight == 0;
+ if (!empty) {
+ uint32_t cacheWidth = glyph->mCacheTexture->getWidth();
+ uint32_t cacheHeight = glyph->mCacheTexture->getHeight();
- glyph->mBitmapMinU = startX / (float) cacheWidth;
- glyph->mBitmapMinV = startY / (float) cacheHeight;
- glyph->mBitmapMaxU = endX / (float) cacheWidth;
- glyph->mBitmapMaxV = endY / (float) cacheHeight;
+ glyph->mBitmapMinU = startX / (float) cacheWidth;
+ glyph->mBitmapMinV = startY / (float) cacheHeight;
+ glyph->mBitmapMaxU = endX / (float) cacheWidth;
+ glyph->mBitmapMaxV = endY / (float) cacheHeight;
- mState->setTextureDirty();
+ mState->setTextureDirty();
+ }
}
CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching) {
CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
mCachedGlyphs.add(glyph, newGlyph);
- const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph);
- newGlyph->mGlyphIndex = skiaGlyph.fID;
+ const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph, &mDescription.mLookupTransform);
newGlyph->mIsValid = false;
+ newGlyph->mGlyphIndex = skiaGlyph.fID;
updateGlyphCache(paint, skiaGlyph, newGlyph, precaching);
return newGlyph;
}
-Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize,
- int flags, uint32_t italicStyle, uint32_t scaleX,
- SkPaint::Style style, uint32_t strokeWidth) {
- Vector<Font*> &activeFonts = state->mActiveFonts;
-
- for (uint32_t i = 0; i < activeFonts.size(); i++) {
- Font* font = activeFonts[i];
- if (font->mFontId == fontId && font->mFontSize == fontSize &&
- font->mFlags == flags && font->mItalicStyle == italicStyle &&
- font->mScaleX == scaleX && font->mStyle == style &&
- (style == SkPaint::kFill_Style || font->mStrokeWidth == strokeWidth)) {
- return font;
- }
+Font* Font::create(FontRenderer* state, const SkPaint* paint, const mat4& matrix) {
+ FontDescription description(paint, matrix);
+ Font* font = state->mActiveFonts.get(description);
+
+ if (!font) {
+ font = new Font(state, description);
+ state->mActiveFonts.put(description, font);
}
+ font->mIdentityTransform = matrix.isIdentity();
- Font* newFont = new Font(state, fontId, fontSize, flags, italicStyle,
- scaleX, style, strokeWidth);
- activeFonts.push(newFont);
- return newFont;
+ return font;
}
}; // namespace uirenderer
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index 7cab31e8a2ab..52cca1c370f3 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -25,6 +25,7 @@
#include "CachedGlyphInfo.h"
#include "../Rect.h"
+#include "../Matrix.h"
namespace android {
namespace uirenderer {
@@ -45,31 +46,55 @@ public:
kFakeBold = 1
};
- ~Font();
+ struct FontDescription {
+ FontDescription(const SkPaint* paint, const mat4& matrix);
- /**
- * Renders the specified string of text.
- * If bitmap is specified, it will be used as the render target
- */
- void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
- int numGlyphs, int x, int y, uint8_t *bitmap = NULL,
- uint32_t bitmapW = 0, uint32_t bitmapH = 0);
+ static int compare(const FontDescription& lhs, const FontDescription& rhs);
- void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+ hash_t hash() const;
+
+ bool operator==(const FontDescription& other) const {
+ return compare(*this, other) == 0;
+ }
+
+ bool operator!=(const FontDescription& other) const {
+ return compare(*this, other) != 0;
+ }
+
+ SkFontID mFontId;
+ float mFontSize;
+ int mFlags;
+ float mItalicStyle;
+ float mScaleX;
+ uint8_t mStyle;
+ float mStrokeWidth;
+ bool mAntiAliasing;
+ SkMatrix mLookupTransform;
+ SkMatrix mInverseLookupTransform;
+ };
+
+ ~Font();
+
+ void render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
int numGlyphs, int x, int y, const float* positions);
- void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+ void render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
int numGlyphs, SkPath* path, float hOffset, float vOffset);
+ const Font::FontDescription& getDescription() const {
+ return mDescription;
+ }
+
/**
* Creates a new font associated with the specified font state.
*/
- static Font* create(FontRenderer* state, uint32_t fontId, float fontSize,
- int flags, uint32_t italicStyle, uint32_t scaleX, SkPaint::Style style,
- uint32_t strokeWidth);
+ static Font* create(FontRenderer* state, const SkPaint* paint, const mat4& matrix);
private:
friend class FontRenderer;
+
+ Font(FontRenderer* state, const Font::FontDescription& desc);
+
typedef void (Font::*RenderGlyph)(CachedGlyphInfo*, int, int, uint8_t*,
uint32_t, uint32_t, Rect*, const float*);
@@ -88,12 +113,6 @@ private:
void measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
int numGlyphs, Rect *bounds, const float* positions);
- Font(FontRenderer* state, uint32_t fontId, float fontSize, int flags, uint32_t italicStyle,
- uint32_t scaleX, SkPaint::Style style, uint32_t strokeWidth);
-
- // Cache of glyphs
- DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs;
-
void invalidateTextureCache(CacheTexture* cacheTexture = NULL);
CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching);
@@ -106,6 +125,9 @@ private:
void drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
Rect* bounds, const float* pos);
+ void drawCachedGlyphTransformed(CachedGlyphInfo* glyph, int x, int y,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
+ Rect* bounds, const float* pos);
void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
Rect* bounds, const float* pos);
@@ -115,15 +137,27 @@ private:
CachedGlyphInfo* getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching = false);
FontRenderer* mState;
- uint32_t mFontId;
- float mFontSize;
- int mFlags;
- uint32_t mItalicStyle;
- uint32_t mScaleX;
- SkPaint::Style mStyle;
- uint32_t mStrokeWidth;
+ FontDescription mDescription;
+
+ // Cache of glyphs
+ DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs;
+
+ bool mIdentityTransform;
};
+inline int strictly_order_type(const Font::FontDescription& lhs,
+ const Font::FontDescription& rhs) {
+ return Font::FontDescription::compare(lhs, rhs) < 0;
+}
+
+inline int compare_type(const Font::FontDescription& lhs, const Font::FontDescription& rhs) {
+ return Font::FontDescription::compare(lhs, rhs);
+}
+
+inline hash_t hash_type(const Font::FontDescription& entry) {
+ return entry.hash();
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/font/FontUtil.h b/libs/hwui/font/FontUtil.h
index 12247baa876a..f758666c1fc8 100644
--- a/libs/hwui/font/FontUtil.h
+++ b/libs/hwui/font/FontUtil.h
@@ -26,7 +26,7 @@
///////////////////////////////////////////////////////////////////////////////
#define DEFAULT_TEXT_SMALL_CACHE_WIDTH 1024
-#define DEFAULT_TEXT_SMALL_CACHE_HEIGHT 256
+#define DEFAULT_TEXT_SMALL_CACHE_HEIGHT 512
#define DEFAULT_TEXT_LARGE_CACHE_WIDTH 2048
#define DEFAULT_TEXT_LARGE_CACHE_HEIGHT 512
@@ -37,7 +37,7 @@
#if RENDER_TEXT_AS_GLYPHS
typedef uint16_t glyph_t;
#define TO_GLYPH(g) g
- #define GET_METRICS(paint, glyph) paint->getGlyphMetrics(glyph)
+ #define GET_METRICS(paint, glyph, matrix) paint->getGlyphMetrics(glyph, matrix)
#define GET_GLYPH(text) nextGlyph((const uint16_t**) &text)
#define IS_END_OF_STRING(glyph) false
@@ -50,7 +50,7 @@
#else
typedef SkUnichar glyph_t;
#define TO_GLYPH(g) ((SkUnichar) g)
- #define GET_METRICS(paint, glyph) paint->getUnicharMetrics(glyph)
+ #define GET_METRICS(paint, glyph, matrix) paint->getUnicharMetrics(glyph, matrix)
#define GET_GLYPH(text) SkUTF16_NextUnichar((const uint16_t**) &text)
#define IS_END_OF_STRING(glyph) glyph < 0
#endif
diff --git a/libs/hwui/thread/Barrier.h b/libs/hwui/thread/Barrier.h
new file mode 100644
index 000000000000..6cb23e54943b
--- /dev/null
+++ b/libs/hwui/thread/Barrier.h
@@ -0,0 +1,58 @@
+/*
+ * 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_BARRIER_H
+#define ANDROID_HWUI_BARRIER_H
+
+#include <utils/Condition.h>
+
+namespace android {
+namespace uirenderer {
+
+class Barrier {
+public:
+ Barrier(Condition::WakeUpType type = Condition::WAKE_UP_ALL) : mType(type), mOpened(false) { }
+ ~Barrier() { }
+
+ void open() {
+ Mutex::Autolock l(mLock);
+ mOpened = true;
+ mCondition.signal(mType);
+ }
+
+ void close() {
+ Mutex::Autolock l(mLock);
+ mOpened = false;
+ }
+
+ void wait() const {
+ Mutex::Autolock l(mLock);
+ while (!mOpened) {
+ mCondition.wait(mLock);
+ }
+ }
+
+private:
+ Condition::WakeUpType mType;
+ volatile bool mOpened;
+ mutable Mutex mLock;
+ mutable Condition mCondition;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_BARRIER_H
diff --git a/libs/hwui/thread/Future.h b/libs/hwui/thread/Future.h
new file mode 100644
index 000000000000..a3ff3bcc3698
--- /dev/null
+++ b/libs/hwui/thread/Future.h
@@ -0,0 +1,58 @@
+/*
+ * 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_FUTURE_H
+#define ANDROID_HWUI_FUTURE_H
+
+#include <utils/RefBase.h>
+
+#include "Barrier.h"
+
+namespace android {
+namespace uirenderer {
+
+template<typename T>
+class Future: public LightRefBase<Future<T> > {
+public:
+ Future(Condition::WakeUpType type = Condition::WAKE_UP_ONE): mBarrier(type), mResult() { }
+ ~Future() { }
+
+ /**
+ * Returns the result of this future, blocking if
+ * the result is not available yet.
+ */
+ T get() const {
+ mBarrier.wait();
+ return mResult;
+ }
+
+ /**
+ * This method must be called only once.
+ */
+ void produce(T result) {
+ mResult = result;
+ mBarrier.open();
+ }
+
+private:
+ Barrier mBarrier;
+ T mResult;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_FUTURE_H
diff --git a/libs/hwui/thread/Signal.h b/libs/hwui/thread/Signal.h
new file mode 100644
index 000000000000..dcf54498b9f5
--- /dev/null
+++ b/libs/hwui/thread/Signal.h
@@ -0,0 +1,56 @@
+/*
+ * 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_SIGNAL_H
+#define ANDROID_HWUI_SIGNAL_H
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <utils/threads.h>
+
+namespace android {
+namespace uirenderer {
+
+class Signal {
+public:
+ Signal(Condition::WakeUpType type = Condition::WAKE_UP_ALL) : mType(type), mSignaled(false) { }
+ ~Signal() { }
+
+ void signal() {
+ Mutex::Autolock l(mLock);
+ mSignaled = true;
+ mCondition.signal(mType);
+ }
+
+ void wait() {
+ Mutex::Autolock l(mLock);
+ while (!mSignaled) {
+ mCondition.wait(mLock);
+ }
+ mSignaled = false;
+ }
+
+private:
+ Condition::WakeUpType mType;
+ volatile bool mSignaled;
+ mutable Mutex mLock;
+ mutable Condition mCondition;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_SIGNAL_H
diff --git a/libs/hwui/thread/Task.h b/libs/hwui/thread/Task.h
new file mode 100644
index 000000000000..9a211a238bbc
--- /dev/null
+++ b/libs/hwui/thread/Task.h
@@ -0,0 +1,63 @@
+/*
+ * 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_TASK_H
+#define ANDROID_HWUI_TASK_H
+
+#define ATRACE_TAG ATRACE_TAG_VIEW
+
+#include <utils/RefBase.h>
+#include <utils/Trace.h>
+
+#include "Future.h"
+
+namespace android {
+namespace uirenderer {
+
+class TaskBase: public RefBase {
+public:
+ TaskBase() { }
+ virtual ~TaskBase() { }
+};
+
+template<typename T>
+class Task: public TaskBase {
+public:
+ Task(): mFuture(new Future<T>()) { }
+ virtual ~Task() { }
+
+ T getResult() const {
+ ATRACE_NAME("waitForTask");
+ return mFuture->get();
+ }
+
+ void setResult(T result) {
+ mFuture->produce(result);
+ }
+
+protected:
+ const sp<Future<T> >& future() const {
+ return mFuture;
+ }
+
+private:
+ sp<Future<T> > mFuture;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TASK_H
diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp
new file mode 100644
index 000000000000..c8bfd9c88165
--- /dev/null
+++ b/libs/hwui/thread/TaskManager.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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 <sys/sysinfo.h>
+
+#include "Task.h"
+#include "TaskProcessor.h"
+#include "TaskManager.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Manager
+///////////////////////////////////////////////////////////////////////////////
+
+TaskManager::TaskManager() {
+ // Get the number of available CPUs. This value does not change over time.
+ int cpuCount = sysconf(_SC_NPROCESSORS_ONLN);
+
+ for (int i = 0; i < cpuCount / 2; i++) {
+ String8 name;
+ name.appendFormat("hwuiTask%d", i + 1);
+ mThreads.add(new WorkerThread(name));
+ }
+}
+
+TaskManager::~TaskManager() {
+ for (size_t i = 0; i < mThreads.size(); i++) {
+ mThreads[i]->exit();
+ }
+}
+
+bool TaskManager::canRunTasks() const {
+ return mThreads.size() > 0;
+}
+
+void TaskManager::stop() {
+ for (size_t i = 0; i < mThreads.size(); i++) {
+ mThreads[i]->exit();
+ }
+}
+
+bool TaskManager::addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor) {
+ if (mThreads.size() > 0) {
+ TaskWrapper wrapper(task, processor);
+
+ size_t minQueueSize = INT_MAX;
+ sp<WorkerThread> thread;
+
+ for (size_t i = 0; i < mThreads.size(); i++) {
+ if (mThreads[i]->getTaskCount() < minQueueSize) {
+ thread = mThreads[i];
+ minQueueSize = mThreads[i]->getTaskCount();
+ }
+ }
+
+ return thread->addTask(wrapper);
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Thread
+///////////////////////////////////////////////////////////////////////////////
+
+bool TaskManager::WorkerThread::threadLoop() {
+ mSignal.wait();
+ Vector<TaskWrapper> tasks;
+ {
+ Mutex::Autolock l(mLock);
+ tasks = mTasks;
+ mTasks.clear();
+ }
+
+ for (size_t i = 0; i < tasks.size(); i++) {
+ const TaskWrapper& task = tasks.itemAt(i);
+ task.mProcessor->process(task.mTask);
+ }
+
+ return true;
+}
+
+bool TaskManager::WorkerThread::addTask(TaskWrapper task) {
+ if (!isRunning()) {
+ run(mName.string(), PRIORITY_DEFAULT);
+ }
+
+ Mutex::Autolock l(mLock);
+ ssize_t index = mTasks.add(task);
+ mSignal.signal();
+
+ return index >= 0;
+}
+
+size_t TaskManager::WorkerThread::getTaskCount() const {
+ Mutex::Autolock l(mLock);
+ return mTasks.size();
+}
+
+void TaskManager::WorkerThread::exit() {
+ {
+ Mutex::Autolock l(mLock);
+ mTasks.clear();
+ }
+ requestExit();
+ mSignal.signal();
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h
new file mode 100644
index 000000000000..477314bbbb00
--- /dev/null
+++ b/libs/hwui/thread/TaskManager.h
@@ -0,0 +1,106 @@
+/*
+ * 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_TASK_MANAGER_H
+#define ANDROID_HWUI_TASK_MANAGER_H
+
+#include <utils/Mutex.h>
+#include <utils/String8.h>
+#include <utils/Thread.h>
+#include <utils/Vector.h>
+
+#include "Signal.h"
+
+namespace android {
+namespace uirenderer {
+
+template <typename T>
+class Task;
+class TaskBase;
+
+template <typename T>
+class TaskProcessor;
+class TaskProcessorBase;
+
+class TaskManager {
+public:
+ TaskManager();
+ ~TaskManager();
+
+ /**
+ * Returns true if this task manager can run tasks,
+ * false otherwise. This method will typically return
+ * true on a single CPU core device.
+ */
+ bool canRunTasks() const;
+
+ /**
+ * Stops all allocated threads. Adding tasks will start
+ * the threads again as necessary.
+ */
+ void stop();
+
+private:
+ template <typename T>
+ friend class TaskProcessor;
+
+ template<typename T>
+ bool addTask(const sp<Task<T> >& task, const sp<TaskProcessor<T> >& processor) {
+ return addTaskBase(sp<TaskBase>(task), sp<TaskProcessorBase>(processor));
+ }
+
+ bool addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor);
+
+ struct TaskWrapper {
+ TaskWrapper(): mTask(), mProcessor() { }
+
+ TaskWrapper(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor):
+ mTask(task), mProcessor(processor) {
+ }
+
+ sp<TaskBase> mTask;
+ sp<TaskProcessorBase> mProcessor;
+ };
+
+ class WorkerThread: public Thread {
+ public:
+ WorkerThread(const String8 name): mSignal(Condition::WAKE_UP_ONE), mName(name) { }
+
+ bool addTask(TaskWrapper task);
+ size_t getTaskCount() const;
+ void exit();
+
+ private:
+ virtual bool threadLoop();
+
+ // Lock for the list of tasks
+ mutable Mutex mLock;
+ Vector<TaskWrapper> mTasks;
+
+ // Signal used to wake up the thread when a new
+ // task is available in the list
+ mutable Signal mSignal;
+
+ const String8 mName;
+ };
+
+ Vector<sp<WorkerThread> > mThreads;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TASK_MANAGER_H
diff --git a/libs/hwui/thread/TaskProcessor.h b/libs/hwui/thread/TaskProcessor.h
new file mode 100644
index 000000000000..d1269f0bd55a
--- /dev/null
+++ b/libs/hwui/thread/TaskProcessor.h
@@ -0,0 +1,72 @@
+/*
+ * 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_TASK_PROCESSOR_H
+#define ANDROID_HWUI_TASK_PROCESSOR_H
+
+#include <utils/RefBase.h>
+
+#include "Task.h"
+#include "TaskManager.h"
+
+namespace android {
+namespace uirenderer {
+
+class TaskProcessorBase: public RefBase {
+public:
+ TaskProcessorBase() { }
+ virtual ~TaskProcessorBase() { };
+
+private:
+ friend class TaskManager;
+
+ virtual void process(const sp<TaskBase>& task) = 0;
+};
+
+template<typename T>
+class TaskProcessor: public TaskProcessorBase {
+public:
+ TaskProcessor(TaskManager* manager): mManager(manager) { }
+ virtual ~TaskProcessor() { }
+
+ bool add(const sp<Task<T> >& task);
+
+ virtual void onProcess(const sp<Task<T> >& task) = 0;
+
+private:
+ virtual void process(const sp<TaskBase>& task) {
+ sp<Task<T> > realTask = static_cast<Task<T>* >(task.get());
+ // This is the right way to do it but sp<> doesn't play nice
+ // sp<Task<T> > realTask = static_cast<sp<Task<T> > >(task);
+ onProcess(realTask);
+ }
+
+ TaskManager* mManager;
+};
+
+template<typename T>
+bool TaskProcessor<T>::add(const sp<Task<T> >& task) {
+ if (mManager) {
+ sp<TaskProcessor<T> > self(this);
+ return mManager->addTask(task, self);
+ }
+ return false;
+}
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TASK_PROCESSOR_H
diff --git a/libs/hwui/utils/Blur.cpp b/libs/hwui/utils/Blur.cpp
new file mode 100644
index 000000000000..85d90d030b53
--- /dev/null
+++ b/libs/hwui/utils/Blur.cpp
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <math.h>
+
+#include "Blur.h"
+
+namespace android {
+namespace uirenderer {
+
+void Blur::generateGaussianWeights(float* weights, int32_t radius) {
+ // Compute gaussian weights for the blur
+ // e is the euler's number
+ static float e = 2.718281828459045f;
+ static float pi = 3.1415926535897932f;
+ // 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.
+ // Based on some experimental radius values and sigma's
+ // we approximately fit sigma = f(radius) as
+ // sigma = radius * 0.3 + 0.6
+ // The larger the radius gets, the more our gaussian blur
+ // will resemble a box blur since with large sigma
+ // the gaussian curve begins to lose its shape
+ float sigma = 0.3f * (float) radius + 0.6f;
+
+ // Now compute the coefficints
+ // We will store some redundant values to save some math during
+ // the blur calculations
+ // precompute some values
+ float coeff1 = 1.0f / (sqrt(2.0f * pi) * sigma);
+ float coeff2 = - 1.0f / (2.0f * sigma * sigma);
+
+ float normalizeFactor = 0.0f;
+ for (int32_t r = -radius; r <= radius; r ++) {
+ float floatR = (float) r;
+ weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
+ normalizeFactor += weights[r + radius];
+ }
+
+ //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;
+ }
+}
+
+void Blur::horizontal(float* weights, int32_t radius,
+ const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
+ float blurredPixel = 0.0f;
+ float currentPixel = 0.0f;
+
+ for (int32_t y = 0; y < height; y ++) {
+
+ const uint8_t* input = source + y * width;
+ uint8_t* output = dest + y * width;
+
+ for (int32_t x = 0; x < width; x ++) {
+ blurredPixel = 0.0f;
+ const float* gPtr = weights;
+ // Optimization for non-border pixels
+ if (x > radius && x < (width - radius)) {
+ const uint8_t *i = input + (x - radius);
+ for (int r = -radius; r <= radius; r ++) {
+ currentPixel = (float) (*i);
+ blurredPixel += currentPixel * gPtr[0];
+ gPtr++;
+ i++;
+ }
+ } else {
+ for (int32_t r = -radius; r <= radius; r ++) {
+ // Stepping left and right away from the pixel
+ int validW = x + r;
+ if (validW < 0) {
+ validW = 0;
+ }
+ if (validW > width - 1) {
+ validW = width - 1;
+ }
+
+ currentPixel = (float) input[validW];
+ blurredPixel += currentPixel * gPtr[0];
+ gPtr++;
+ }
+ }
+ *output = (uint8_t)blurredPixel;
+ output ++;
+ }
+ }
+}
+
+void Blur::vertical(float* weights, int32_t radius,
+ const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
+ float blurredPixel = 0.0f;
+ float currentPixel = 0.0f;
+
+ for (int32_t y = 0; y < height; y ++) {
+ uint8_t* output = dest + y * width;
+
+ for (int32_t x = 0; x < width; x ++) {
+ blurredPixel = 0.0f;
+ const float* gPtr = weights;
+ const uint8_t* input = source + x;
+ // Optimization for non-border pixels
+ if (y > radius && y < (height - radius)) {
+ const uint8_t *i = input + ((y - radius) * width);
+ for (int32_t r = -radius; r <= radius; r ++) {
+ currentPixel = (float) (*i);
+ blurredPixel += currentPixel * gPtr[0];
+ gPtr++;
+ i += width;
+ }
+ } else {
+ for (int32_t r = -radius; r <= radius; r ++) {
+ int validH = y + r;
+ // Clamp to zero and width
+ if (validH < 0) {
+ validH = 0;
+ }
+ if (validH > height - 1) {
+ validH = height - 1;
+ }
+
+ const uint8_t *i = input + validH * width;
+ currentPixel = (float) (*i);
+ blurredPixel += currentPixel * gPtr[0];
+ gPtr++;
+ }
+ }
+ *output = (uint8_t) blurredPixel;
+ output++;
+ }
+ }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/utils/Blur.h b/libs/hwui/utils/Blur.h
new file mode 100644
index 000000000000..6c176e95e33c
--- /dev/null
+++ b/libs/hwui/utils/Blur.h
@@ -0,0 +1,37 @@
+/*
+ * 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_BLUR_H
+#define ANDROID_HWUI_BLUR_H
+
+#include <stdint.h>
+
+namespace android {
+namespace uirenderer {
+
+class Blur {
+public:
+ static void generateGaussianWeights(float* weights, int32_t 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,
+ uint8_t* dest, int32_t width, int32_t height);
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_BLUR_H
diff --git a/libs/hwui/utils/Compare.h b/libs/hwui/utils/Compare.h
deleted file mode 100644
index fdd9acf7a6e3..000000000000
--- a/libs/hwui/utils/Compare.h
+++ /dev/null
@@ -1,36 +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_COMPARE_H
-#define ANDROID_HWUI_COMPARE_H
-
-#include <cmath>
-
-/**
- * Compare floats.
- */
-#define LTE_FLOAT(a) \
- if (a < rhs.a) return true; \
- if (a == rhs.a)
-
-/**
- * Compare integers.
- */
-#define LTE_INT(a) \
- if (a < rhs.a) return true; \
- if (a == rhs.a)
-
-#endif // ANDROID_HWUI_COMPARE_H
diff --git a/libs/hwui/utils/Pair.h b/libs/hwui/utils/Pair.h
new file mode 100644
index 000000000000..172606a4d586
--- /dev/null
+++ b/libs/hwui/utils/Pair.h
@@ -0,0 +1,58 @@
+/*
+ * 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_PAIR_H
+#define ANDROID_HWUI_PAIR_H
+
+namespace android {
+namespace uirenderer {
+
+template <typename F, typename S>
+struct Pair {
+ F first;
+ S second;
+
+ Pair() { }
+ Pair(const Pair& o) : first(o.first), second(o.second) { }
+ Pair(const F& f, const S& s) : first(f), second(s) { }
+
+ inline const F& getFirst() const {
+ return first;
+ }
+
+ inline const S& getSecond() const {
+ return second;
+ }
+};
+
+}; // namespace uirenderer
+
+template <typename F, typename S>
+struct trait_trivial_ctor< uirenderer::Pair<F, S> >
+{ enum { value = aggregate_traits<F, S>::has_trivial_ctor }; };
+template <typename F, typename S>
+struct trait_trivial_dtor< uirenderer::Pair<F, S> >
+{ enum { value = aggregate_traits<F, S>::has_trivial_dtor }; };
+template <typename F, typename S>
+struct trait_trivial_copy< uirenderer::Pair<F, S> >
+{ enum { value = aggregate_traits<F, S>::has_trivial_copy }; };
+template <typename F, typename S>
+struct trait_trivial_move< uirenderer::Pair<F, S> >
+{ enum { value = aggregate_traits<F, S>::has_trivial_move }; };
+
+}; // namespace android
+
+#endif // ANDROID_HWUI_PAIR_H
diff --git a/libs/hwui/utils/Timing.h b/libs/hwui/utils/Timing.h
new file mode 100644
index 000000000000..eced987a0084
--- /dev/null
+++ b/libs/hwui/utils/Timing.h
@@ -0,0 +1,42 @@
+/*
+ * 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_TIMING_H
+#define ANDROID_HWUI_TIMING_H
+
+#include <sys/time.h>
+
+#define TIME_METHOD() MethodTimer __method_timer(__func__)
+class MethodTimer {
+public:
+ MethodTimer(const char* name)
+ : mMethodName(name) {
+ gettimeofday(&mStart, NULL);
+ }
+
+ ~MethodTimer() {
+ struct timeval stop;
+ gettimeofday(&stop, NULL);
+ long long elapsed = (stop.tv_sec * 1000000) - (mStart.tv_sec * 1000000)
+ + (stop.tv_usec - mStart.tv_usec);
+ ALOGD("%s took %.2fms", mMethodName, elapsed / 1000.0);
+ }
+private:
+ const char* mMethodName;
+ struct timeval mStart;
+};
+
+#endif