diff options
Diffstat (limited to 'libs/hwui')
53 files changed, 1883 insertions, 903 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index a630ea1..5a30472 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -10,6 +10,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) thread/TaskManager.cpp \ font/CacheTexture.cpp \ font/Font.cpp \ + AssetAtlas.cpp \ FontRenderer.cpp \ GammaFontRenderer.cpp \ Caches.cpp \ @@ -21,6 +22,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) Extensions.cpp \ FboCache.cpp \ GradientCache.cpp \ + Image.cpp \ Layer.cpp \ LayerCache.cpp \ LayerRenderer.cpp \ @@ -39,6 +41,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) SkiaShader.cpp \ Snapshot.cpp \ Stencil.cpp \ + Texture.cpp \ TextureCache.cpp \ TextDropShadowCache.cpp @@ -52,17 +55,23 @@ ifeq ($(USE_OPENGL_RENDERER),true) external/skia/include/images \ external/skia/src/core \ external/skia/src/ports \ - external/skia/include/utils \ - $(intermediates) \ - frameworks/rs/cpp \ - frameworks/rs + external/skia/include/utils - LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DGL_GLEXT_PROTOTYPES + LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES LOCAL_MODULE_CLASS := SHARED_LIBRARIES - LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libGLESv2 libskia libui libRS libRScpp + LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libEGL libGLESv2 libskia libui LOCAL_MODULE := libhwui LOCAL_MODULE_TAGS := optional + ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT)) + LOCAL_CFLAGS += -DANDROID_ENABLE_RENDERSCRIPT + LOCAL_SHARED_LIBRARIES += libRS libRScpp + LOCAL_C_INCLUDES += \ + $(intermediates) \ + frameworks/rs/cpp \ + frameworks/rs + endif + ifndef HWUI_COMPILE_SYMBOLS LOCAL_CFLAGS += -fvisibility=hidden endif diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp new file mode 100644 index 0000000..782c052 --- /dev/null +++ b/libs/hwui/AssetAtlas.cpp @@ -0,0 +1,137 @@ +/* + * 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 "AssetAtlas.h" +#include "Caches.h" + +#include <GLES2/gl2ext.h> + +namespace android { +namespace uirenderer { + +/////////////////////////////////////////////////////////////////////////////// +// Lifecycle +/////////////////////////////////////////////////////////////////////////////// + +void AssetAtlas::init(sp<GraphicBuffer> buffer, int* map, int count) { + if (mImage) { + return; + } + + mImage = new Image(buffer); + + if (mImage->getTexture()) { + Caches& caches = Caches::getInstance(); + + mTexture = new Texture(caches); + mTexture->id = mImage->getTexture(); + mTexture->width = buffer->getWidth(); + mTexture->height = buffer->getHeight(); + + createEntries(caches, map, count); + } else { + ALOGW("Could not create atlas image"); + + delete mImage; + mImage = NULL; + mTexture = NULL; + } +} + +void AssetAtlas::terminate() { + if (mImage) { + delete mImage; + mImage = NULL; + + delete mTexture; + mTexture = NULL; + + for (size_t i = 0; i < mEntries.size(); i++) { + delete mEntries.valueAt(i); + } + mEntries.clear(); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Entries +/////////////////////////////////////////////////////////////////////////////// + +AssetAtlas::Entry* AssetAtlas::getEntry(SkBitmap* const bitmap) const { + ssize_t index = mEntries.indexOfKey(bitmap); + return index >= 0 ? mEntries.valueAt(index) : NULL; +} + +Texture* AssetAtlas::getEntryTexture(SkBitmap* const bitmap) const { + ssize_t index = mEntries.indexOfKey(bitmap); + return index >= 0 ? mEntries.valueAt(index)->texture : NULL; +} + +/** + * Delegates changes to wrapping and filtering to the base atlas texture + * instead of applying the changes to the virtual textures. + */ +struct DelegateTexture: public Texture { + DelegateTexture(Caches& caches, Texture* delegate): Texture(caches), mDelegate(delegate) { } + + virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false, + bool force = false, GLenum renderTarget = GL_TEXTURE_2D) { + mDelegate->setWrapST(wrapS, wrapT, bindTexture, force, renderTarget); + } + + virtual void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false, + bool force = false, GLenum renderTarget = GL_TEXTURE_2D) { + mDelegate->setFilterMinMag(min, mag, bindTexture, force, renderTarget); + } +private: + Texture* const mDelegate; +}; // struct DelegateTexture + +/** + * TODO: This method does not take the rotation flag into account + */ +void AssetAtlas::createEntries(Caches& caches, int* map, int count) { + const float width = float(mTexture->width); + const float height = float(mTexture->height); + + for (int i = 0; i < count; ) { + SkBitmap* bitmap = (SkBitmap*) map[i++]; + int x = map[i++]; + int y = map[i++]; + bool rotated = map[i++] > 0; + + // Bitmaps should never be null, we're just extra paranoid + if (!bitmap) continue; + + const UvMapper mapper( + x / width, (x + bitmap->width()) / width, + y / height, (y + bitmap->height()) / height); + + Texture* texture = new DelegateTexture(caches, mTexture); + Entry* entry = new Entry(bitmap, x, y, rotated, texture, mapper, *this); + + texture->id = mTexture->id; + texture->blend = !bitmap->isOpaque(); + texture->width = bitmap->width(); + texture->height = bitmap->height(); + texture->uvMapper = &entry->uvMapper; + + mEntries.add(entry->bitmap, entry); + } +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h new file mode 100644 index 0000000..2624907 --- /dev/null +++ b/libs/hwui/AssetAtlas.h @@ -0,0 +1,173 @@ +/* + * 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_ASSET_ATLAS_H +#define ANDROID_HWUI_ASSET_ATLAS_H + +#include <GLES2/gl2.h> + +#include <ui/GraphicBuffer.h> + +#include <utils/KeyedVector.h> + +#include <cutils/compiler.h> + +#include <SkBitmap.h> + +#include "Image.h" +#include "Texture.h" +#include "UvMapper.h" + +namespace android { +namespace uirenderer { + +class Caches; + +/** + * An asset atlas holds a collection of framework bitmaps in a single OpenGL + * texture. Each bitmap is associated with a location, defined in pixels, + * inside the atlas. The atlas is generated by the framework and bound as + * an external texture using the EGLImageKHR extension. + */ +class AssetAtlas { +public: + /** + * Entry representing the position and rotation of a + * bitmap inside the atlas. + */ + struct Entry { + /** + * The bitmap that generated this atlas entry. + */ + SkBitmap* bitmap; + + /** + * Location of the bitmap inside the atlas, in pixels. + */ + int x; + int y; + + /** + * If set, the bitmap is rotated 90 degrees (clockwise) + * inside the atlas. + */ + bool rotated; + + /* + * A "virtual texture" object that represents the texture + * this entry belongs to. This texture should never be + * modified. + */ + Texture* texture; + + /** + * Maps texture coordinates in the [0..1] range into the + * correct range to sample this entry from the atlas. + */ + const UvMapper uvMapper; + + /** + * Atlas this entry belongs to. + */ + const AssetAtlas& atlas; + + private: + Entry(SkBitmap* bitmap, int x, int y, bool rotated, + Texture* texture, const UvMapper& mapper, const AssetAtlas& atlas): + bitmap(bitmap), x(x), y(y), rotated(rotated), + texture(texture), uvMapper(mapper), atlas(atlas) { } + + ~Entry() { + delete texture; + } + + friend class AssetAtlas; + }; + + AssetAtlas(): mTexture(NULL), mImage(NULL) { } + ~AssetAtlas() { terminate(); } + + /** + * Initializes the atlas with the specified buffer and + * map. The buffer is a gralloc'd texture that will be + * used as an EGLImage. The map is a list of SkBitmap* + * and their (x, y) positions as well as their rotation + * flags. + * + * This method returns immediately if the atlas is already + * initialized. To re-initialize the atlas, you must + * first call terminate(). + */ + ANDROID_API void init(sp<GraphicBuffer> buffer, int* map, int count); + + /** + * Destroys the atlas texture. This object can be + * re-initialized after calling this method. + * + * After calling this method, the width, height + * and texture are set to 0. + */ + ANDROID_API void terminate(); + + /** + * Returns the width of this atlas in pixels. + * Can return 0 if the atlas is not initialized. + */ + uint32_t getWidth() const { + return mTexture ? mTexture->width : 0; + } + + /** + * Returns the height of this atlas in pixels. + * Can return 0 if the atlas is not initialized. + */ + uint32_t getHeight() const { + return mTexture ? mTexture->height : 0; + } + + /** + * Returns the OpenGL name of the texture backing this atlas. + * Can return 0 if the atlas is not initialized. + */ + GLuint getTexture() const { + return mTexture ? mTexture->id : 0; + } + + /** + * Returns the entry in the atlas associated with the specified + * bitmap. If the bitmap is not in the atlas, return NULL. + */ + Entry* getEntry(SkBitmap* const bitmap) const; + + /** + * Returns the texture for the atlas entry associated with the + * specified bitmap. If the bitmap is not in the atlas, return NULL. + */ + Texture* getEntryTexture(SkBitmap* const bitmap) const; + +private: + void createEntries(Caches& caches, int* map, int count); + + Texture* mTexture; + Image* mImage; + + KeyedVector<SkBitmap*, Entry*> mEntries; +}; // class AssetAtlas + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_ASSET_ATLAS_H diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index a381a68..74aeddb 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -47,19 +47,21 @@ namespace uirenderer { // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -Caches::Caches(): Singleton<Caches>(), mExtensions(Extensions::getInstance()), mInitialized(false) { +Caches::Caches(): Singleton<Caches>(), + mExtensions(Extensions::getInstance()), mInitialized(false) { init(); initFont(); initConstraints(); initProperties(); + initStaticProperties(); initExtensions(); mDebugLevel = readDebugLevel(); ALOGD("Enabling debug mode %d", mDebugLevel); } -void Caches::init() { - if (mInitialized) return; +bool Caches::init() { + if (mInitialized) return false; glGenBuffers(1, &meshBuffer); glBindBuffer(GL_ARRAY_BUFFER, meshBuffer); @@ -82,6 +84,7 @@ void Caches::init() { mTextureUnit = 0; mRegionMesh = NULL; + mMeshIndices = 0; blend = false; lastSrcMode = GL_ZERO; @@ -94,7 +97,13 @@ void Caches::init() { debugOverdraw = false; debugStencilClip = kStencilHide; + patchCache.init(*this); + mInitialized = true; + + resetBoundTextures(); + + return true; } void Caches::initFont() { @@ -132,6 +141,18 @@ void Caches::initConstraints() { glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); } +void Caches::initStaticProperties() { + gpuPixelBuffersEnabled = false; + + // OpenGL ES 3.0+ specific features + if (mExtensions.getMajorGlVersion() >= 3) { + char property[PROPERTY_VALUE_MAX]; + if (property_get(PROPERTY_ENABLE_GPU_PIXEL_BUFFERS, property, "true") > 0) { + gpuPixelBuffersEnabled = !strcmp(property, "true"); + } + } +} + bool Caches::initProperties() { bool prevDebugLayersUpdates = debugLayersUpdates; bool prevDebugOverdraw = debugOverdraw; @@ -147,7 +168,7 @@ bool Caches::initProperties() { if (property_get(PROPERTY_DEBUG_OVERDRAW, property, NULL) > 0) { INIT_LOGD(" Overdraw debug enabled: %s", property); - debugOverdraw = !strcmp(property, "true"); + debugOverdraw = !strcmp(property, "show"); } else { debugOverdraw = false; } @@ -191,8 +212,9 @@ void Caches::terminate() { glDeleteBuffers(1, &meshBuffer); mCurrentBuffer = 0; - glDeleteBuffers(1, &mRegionMeshIndices); + glDeleteBuffers(1, &mMeshIndices); delete[] mRegionMesh; + mMeshIndices = 0; mRegionMesh = NULL; fboCache.clear(); @@ -200,6 +222,10 @@ void Caches::terminate() { programCache.clear(); currentProgram = NULL; + assetAtlas.terminate(); + + patchCache.clear(); + mInitialized = false; } @@ -227,6 +253,8 @@ void Caches::dumpMemoryUsage(String8 &log) { pathCache.getSize(), pathCache.getMaxSize()); log.appendFormat(" TextDropShadowCache %8d / %8d\n", dropShadowCache.getSize(), dropShadowCache.getMaxSize()); + log.appendFormat(" PatchCache %8d / %8d\n", + patchCache.getSize(), patchCache.getMaxSize()); for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) { const uint32_t size = fontRenderer->getFontRendererSize(i); log.appendFormat(" FontRenderer %d %8d / %8d\n", i, size, size); @@ -234,8 +262,6 @@ void Caches::dumpMemoryUsage(String8 &log) { log.appendFormat("Other:\n"); log.appendFormat(" FboCache %8d / %8d\n", fboCache.getSize(), fboCache.getMaxSize()); - log.appendFormat(" PatchCache %8d / %8d\n", - patchCache.getSize(), patchCache.getMaxSize()); uint32_t total = 0; total += textureCache.getSize(); @@ -244,6 +270,7 @@ void Caches::dumpMemoryUsage(String8 &log) { total += gradientCache.getSize(); total += pathCache.getSize(); total += dropShadowCache.getSize(); + total += patchCache.getSize(); for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) { total += fontRenderer->getFontRendererSize(i); } @@ -357,6 +384,32 @@ bool Caches::bindIndicesBuffer(const GLuint buffer) { return false; } +bool Caches::bindIndicesBuffer() { + if (!mMeshIndices) { + uint16_t* regionIndices = new uint16_t[REGION_MESH_QUAD_COUNT * 6]; + for (int i = 0; i < REGION_MESH_QUAD_COUNT; i++) { + uint16_t quad = i * 4; + int index = i * 6; + regionIndices[index ] = quad; // top-left + regionIndices[index + 1] = quad + 1; // top-right + regionIndices[index + 2] = quad + 2; // bottom-left + regionIndices[index + 3] = quad + 2; // bottom-left + regionIndices[index + 4] = quad + 1; // top-right + regionIndices[index + 5] = quad + 3; // bottom-right + } + + glGenBuffers(1, &mMeshIndices); + bool force = bindIndicesBuffer(mMeshIndices); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, REGION_MESH_QUAD_COUNT * 6 * sizeof(uint16_t), + regionIndices, GL_STATIC_DRAW); + + delete[] regionIndices; + return force; + } + + return bindIndicesBuffer(mMeshIndices); +} + bool Caches::unbindIndicesBuffer() { if (mCurrentIndicesBuffer) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -441,6 +494,46 @@ void Caches::activeTexture(GLuint textureUnit) { } } +void Caches::bindTexture(GLuint texture) { + if (mBoundTextures[mTextureUnit] != texture) { + glBindTexture(GL_TEXTURE_2D, texture); + mBoundTextures[mTextureUnit] = texture; + } +} + +void Caches::bindTexture(GLenum target, GLuint texture) { + if (mBoundTextures[mTextureUnit] != texture) { + glBindTexture(target, texture); + mBoundTextures[mTextureUnit] = texture; + } +} + +void Caches::deleteTexture(GLuint texture) { + // When glDeleteTextures() is called on a currently bound texture, + // OpenGL ES specifies that the texture is then considered unbound + // Consider the following series of calls: + // + // glGenTextures -> creates texture name 2 + // glBindTexture(2) + // glDeleteTextures(2) -> 2 is now unbound + // glGenTextures -> can return 2 again + // + // If we don't call glBindTexture(2) after the second glGenTextures + // call, any texture operation will be performed on the default + // texture (name=0) + + for (int i = 0; i < REQUIRED_TEXTURE_UNITS_COUNT; i++) { + if (mBoundTextures[i] == texture) { + mBoundTextures[i] = 0; + } + } + glDeleteTextures(1, &texture); +} + +void Caches::resetBoundTextures() { + memset(mBoundTextures, 0, REQUIRED_TEXTURE_UNITS_COUNT * sizeof(GLuint)); +} + /////////////////////////////////////////////////////////////////////////////// // Scissor /////////////////////////////////////////////////////////////////////////////// @@ -546,27 +639,6 @@ TextureVertex* Caches::getRegionMesh() { // Create the mesh, 2 triangles and 4 vertices per rectangle in the region if (!mRegionMesh) { mRegionMesh = new TextureVertex[REGION_MESH_QUAD_COUNT * 4]; - - uint16_t* regionIndices = new uint16_t[REGION_MESH_QUAD_COUNT * 6]; - for (int i = 0; i < REGION_MESH_QUAD_COUNT; i++) { - uint16_t quad = i * 4; - int index = i * 6; - regionIndices[index ] = quad; // top-left - regionIndices[index + 1] = quad + 1; // top-right - regionIndices[index + 2] = quad + 2; // bottom-left - regionIndices[index + 3] = quad + 2; // bottom-left - regionIndices[index + 4] = quad + 1; // top-right - regionIndices[index + 5] = quad + 3; // bottom-right - } - - glGenBuffers(1, &mRegionMeshIndices); - bindIndicesBuffer(mRegionMeshIndices); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, REGION_MESH_QUAD_COUNT * 6 * sizeof(uint16_t), - regionIndices, GL_STATIC_DRAW); - - delete[] regionIndices; - } else { - bindIndicesBuffer(mRegionMeshIndices); } return mRegionMesh; diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 91b938b..bdde8fb 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -21,13 +21,18 @@ #define LOG_TAG "OpenGLRenderer" #endif +#include <GLES3/gl3.h> + +#include <utils/KeyedVector.h> #include <utils/Singleton.h> +#include <utils/Vector.h> #include <cutils/compiler.h> #include "thread/TaskProcessor.h" #include "thread/TaskManager.h" +#include "AssetAtlas.h" #include "FontRenderer.h" #include "GammaFontRenderer.h" #include "TextureCache.h" @@ -50,6 +55,7 @@ namespace uirenderer { // Globals /////////////////////////////////////////////////////////////////////////////// +// GL ES 2.0 defines that at least 16 texture units must be supported #define REQUIRED_TEXTURE_UNITS_COUNT 3 #define REGION_MESH_QUAD_COUNT 512 @@ -74,6 +80,7 @@ static const GLsizei gVertexAAWidthOffset = 2 * sizeof(float); static const GLsizei gVertexAALengthOffset = 3 * sizeof(float); static const GLsizei gMeshCount = 4; +// Must define as many texture units as specified by REQUIRED_TEXTURE_UNITS_COUNT static const GLenum gTextureUnits[] = { GL_TEXTURE0, GL_TEXTURE1, @@ -113,7 +120,7 @@ public: /** * Initialize caches. */ - void init(); + bool init(); /** * Initialize global system properties. @@ -172,6 +179,11 @@ public: */ bool unbindMeshBuffer(); + /** + * Binds a global indices buffer that can draw up to + * REGION_MESH_QUAD_COUNT quads. + */ + bool bindIndicesBuffer(); bool bindIndicesBuffer(const GLuint buffer); bool unbindIndicesBuffer(); @@ -213,6 +225,33 @@ public: void activeTexture(GLuint textureUnit); /** + * Binds the specified texture as a GL_TEXTURE_2D texture. + * All texture bindings must be performed with this method or + * bindTexture(GLenum, GLuint). + */ + void bindTexture(GLuint texture); + + /** + * Binds the specified texture with the specified render target. + * All texture bindings must be performed with this method or + * bindTexture(GLuint). + */ + void bindTexture(GLenum target, GLuint texture); + + /** + * Deletes the specified texture and clears it from the cache + * of bound textures. + * All textures must be deleted using this method. + */ + void deleteTexture(GLuint texture); + + /** + * Signals that the cache of bound textures should be cleared. + * Other users of the context may have altered which textures are bound. + */ + void resetBoundTextures(); + + /** * Sets the scissor for the current surface. */ bool setScissor(GLint x, GLint y, GLint width, GLint height); @@ -290,6 +329,10 @@ public: Dither dither; Stencil stencil; + AssetAtlas assetAtlas; + + bool gpuPixelBuffersEnabled; + // Debug methods PFNGLINSERTEVENTMARKEREXTPROC eventMark; PFNGLPUSHGROUPMARKEREXTPROC startMark; @@ -302,6 +345,7 @@ private: void initFont(); void initExtensions(); void initConstraints(); + void initStaticProperties(); static void eventMarkNull(GLsizei length, const GLchar* marker) { } static void startMarkNull(GLsizei length, const GLchar* marker) { } @@ -336,7 +380,9 @@ private: // Used to render layers TextureVertex* mRegionMesh; - GLuint mRegionMeshIndices; + + // Global index buffer + GLuint mMeshIndices; mutable Mutex mGarbageLock; Vector<Layer*> mLayerGarbage; @@ -346,6 +392,8 @@ private: bool mInitialized; uint32_t mFunctorsCount; + + GLuint mBoundTextures[REQUIRED_TEXTURE_UNITS_COUNT]; }; // class Caches }; // namespace uirenderer diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h index 790c4f4..786f12a 100644 --- a/libs/hwui/Debug.h +++ b/libs/hwui/Debug.h @@ -53,8 +53,6 @@ // Turn on to display debug info about 9patch objects #define DEBUG_PATCHES 0 -// Turn on to "explode" 9patch objects -#define DEBUG_EXPLODE_PATCHES 0 // Turn on to display vertex and tex coords data about 9patch objects // This flag requires DEBUG_PATCHES to be turned on #define DEBUG_PATCHES_VERTICES 0 diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp index 9323a3a..512d3b1 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -85,8 +85,8 @@ public: } virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) { - DEFER_LOGD("%d replaying DrawingBatch %p, with %d ops (batch id %x, merge id %p)", - index, this, mOps.size(), mOps[0]->getBatchId(), mOps[0]->getMergeId()); + DEFER_LOGD("%d replaying DrawBatch %p, with %d ops (batch id %x, merge id %p)", + index, this, mOps.size(), getBatchId(), getMergeId()); status_t status = DrawGlInfo::kStatusDone; DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance(); @@ -145,7 +145,10 @@ public: * dropped, so we make simplifying qualifications on the ops that can merge, per op type. */ bool canMergeWith(DrawOp* op) { - if (!op->state.mMatrix.isPureTranslate()) return false; + if (getBatchId() == DeferredDisplayList::kOpBatch_Bitmap) { + // Bitmap batches can handle translate and scaling + if (!op->state.mMatrix.isSimple()) return false; + } else if (!op->state.mMatrix.isPureTranslate()) return false; bool isTextBatch = getBatchId() == DeferredDisplayList::kOpBatch_Text || getBatchId() == DeferredDisplayList::kOpBatch_ColorText; @@ -193,10 +196,10 @@ public: } virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) { - DEFER_LOGD("%d replaying DrawingBatch %p, with %d ops (batch id %x, merge id %p)", + DEFER_LOGD("%d replaying MergingDrawBatch %p, with %d ops (batch id %x, merge id %p)", index, this, mOps.size(), getBatchId(), getMergeId()); if (mOps.size() == 1) { - return DrawBatch::replay(renderer, dirty, false); + return DrawBatch::replay(renderer, dirty, 0); } DrawOp* op = mOps[0]; diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index 1cbd531..b2d9915 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -514,6 +514,10 @@ void DisplayList::iterate(OpenGLRenderer& renderer, T& handler, const int level) for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) { DisplayListOp *op = mDisplayListData->displayListOps[i]; +#if DEBUG_DISPLAY_LIST + op->output(level + 1); +#endif + logBuffer.writeCommand(level, op->name()); handler(op, saveCount, mClipToBounds); } diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index 5f84329..97c34dc 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -129,7 +129,12 @@ public: void setName(const char* name) { if (name) { - mName.setTo(name); + char* lastPeriod = strrchr(name, '.'); + if (lastPeriod) { + mName.setTo(lastPeriod + 1); + } else { + mName.setTo(name); + } } } diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index a0290e3..028decd 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -26,8 +26,10 @@ #include <private/hwui/DrawGlInfo.h> #include "OpenGLRenderer.h" +#include "AssetAtlas.h" #include "DeferredDisplayList.h" #include "DisplayListRenderer.h" +#include "UvMapper.h" #include "utils/LinearAllocator.h" #define CRASH() do { \ @@ -218,6 +220,9 @@ public: DrawBoundedOp(float left, float top, float right, float bottom, SkPaint* paint) : DrawOp(paint), mLocalBounds(left, top, right, bottom) {} + DrawBoundedOp(const Rect& localBounds, SkPaint* paint) + : DrawOp(paint), mLocalBounds(localBounds) {} + // 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) @@ -721,7 +726,6 @@ private: int mSetBits; }; - /////////////////////////////////////////////////////////////////////////////// // DRAW OPERATIONS - these are operations that can draw to the canvas's device /////////////////////////////////////////////////////////////////////////////// @@ -729,9 +733,16 @@ private: 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) {} + : DrawBoundedOp(left, top, left + bitmap->width(), top + bitmap->height(), paint), + mBitmap(bitmap), mAtlasEntry(NULL) { + } + + DrawBitmapOp(SkBitmap* bitmap, float left, float top, SkPaint* paint, + const AssetAtlas::Entry* entry) + : DrawBoundedOp(left, top, left + bitmap->width(), top + bitmap->height(), paint), + mBitmap(bitmap), mAtlasEntry(entry) { + if (entry) mUvMapper = entry->uvMapper; + } virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawBitmap(mBitmap, mLocalBounds.left, mLocalBounds.top, @@ -749,14 +760,20 @@ public: TextureVertex vertices[6 * ops.size()]; TextureVertex* vertex = &vertices[0]; - // TODO: manually handle rect clip for bitmaps by adjusting texCoords per op, and allowing - // them to be merged in getBatchId() - const Rect texCoords(0, 0, 1, 1); + bool transformed = false; - const float width = mBitmap->width(); - const float height = mBitmap->height(); + // TODO: manually handle rect clip for bitmaps by adjusting texCoords per op, + // and allowing them to be merged in getBatchId() for (unsigned int i = 0; i < ops.size(); i++) { const Rect& opBounds = ops[i]->state.mBounds; + // When we reach multiDraw(), the matrix can be either + // pureTranslate or simple (translate and/or scale). + // If the matrix is not pureTranslate, then we have a scale + if (!ops[i]->state.mMatrix.isPureTranslate()) transformed = true; + + Rect texCoords(0, 0, 1, 1); + ((DrawBitmapOp*) ops[i])->mUvMapper.map(texCoords); + SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, top); SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, top); SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, bottom); @@ -766,7 +783,8 @@ public: SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, bottom); } - return renderer.drawBitmaps(mBitmap, ops.size(), &vertices[0], bounds, mPaint); + return renderer.drawBitmaps(mBitmap, ops.size(), &vertices[0], + transformed, bounds, mPaint); } virtual void output(int level, uint32_t logFlags) { @@ -775,18 +793,25 @@ public: virtual const char* name() { return "DrawBitmap"; } + bool bitmapMergeAllowed() { + return state.mMatrix.isSimple() && !state.mClipped && + OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; + } + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { *batchId = DeferredDisplayList::kOpBatch_Bitmap; - *mergeId = (mergeid_t)mBitmap; + *mergeId = mAtlasEntry ? (mergeid_t) &mAtlasEntry->atlas : (mergeid_t) mBitmap; // don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in // MergingDrawBatch::canMergeWith - return mergeAllowed() && (mBitmap->getConfig() != SkBitmap::kA8_Config); + return bitmapMergeAllowed() && (mBitmap->getConfig() != SkBitmap::kA8_Config); } const SkBitmap* bitmap() { return mBitmap; } protected: SkBitmap* mBitmap; + const AssetAtlas::Entry* mAtlasEntry; + UvMapper mUvMapper; }; class DrawBitmapMatrixOp : public DrawBoundedOp { @@ -904,20 +929,25 @@ private: 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) + DrawPatchOp(SkBitmap* bitmap, Res_png_9patch* patch, + 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) {}; + mBitmap(bitmap), mPatch(patch), mAlpha(alpha), mMode(mode), + mGenerationId(0), mMesh(NULL) { + mEntry = Caches::getInstance().assetAtlas.getEntry(bitmap); + }; virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { - // NOTE: not calling the virtual method, which takes a paint - return renderer.drawPatch(mBitmap, mxDivs, myDivs, mColors, - mxDivsCount, myDivsCount, mNumColors, - mLocalBounds.left, mLocalBounds.top, + if (!mMesh || renderer.getCaches().patchCache.getGenerationId() != mGenerationId) { + PatchCache& cache = renderer.getCaches().patchCache; + mMesh = cache.get(mEntry, mBitmap->width(), mBitmap->height(), + mLocalBounds.right - mLocalBounds.left, mLocalBounds.bottom - mLocalBounds.top, + mPatch); + mGenerationId = cache.getGenerationId(); + } + // We're not calling the public variant of drawPatch() here + // This method won't perform the quickReject() since we've already done it at this point + return renderer.drawPatch(mBitmap, mMesh, mEntry, mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom, mAlpha, mMode); } @@ -929,20 +959,20 @@ public: virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { *batchId = DeferredDisplayList::kOpBatch_Patch; - *mergeId = (mergeid_t)mBitmap; + *mergeId = (mergeid_t) mBitmap; return true; } private: SkBitmap* mBitmap; - const int32_t* mxDivs; - const int32_t* myDivs; - const uint32_t* mColors; - uint32_t mxDivsCount; - uint32_t myDivsCount; - int8_t mNumColors; + Res_png_9patch* mPatch; + int mAlpha; SkXfermode::Mode mMode; + + uint32_t mGenerationId; + const Patch* mMesh; + AssetAtlas::Entry* mEntry; }; class DrawColorOp : public DrawOp { @@ -1270,23 +1300,10 @@ private: 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); + const float* positions, SkPaint* paint, float totalAdvance, const Rect& bounds) + : DrawBoundedOp(bounds, paint), mText(text), mBytesCount(bytesCount), mCount(count), + mX(x), mY(y), mPositions(positions), mTotalAdvance(totalAdvance) { + mLocalBounds.translate(x,y); memset(&mPrecacheTransform.data[0], 0xff, 16 * sizeof(float)); } @@ -1312,7 +1329,7 @@ public: virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawText(mText, mBytesCount, mCount, mX, mY, - mPositions, getPaint(renderer), mLength); + mPositions, getPaint(renderer), mTotalAdvance, mLocalBounds); } virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty, @@ -1325,7 +1342,8 @@ public: DrawTextOp& op = *((DrawTextOp*)ops[i]); status |= renderer.drawText(op.mText, op.mBytesCount, op.mCount, op.mX, op.mY, - op.mPositions, op.getPaint(renderer), op.mLength, drawOpMode); + op.mPositions, op.getPaint(renderer), op.mTotalAdvance, op.mLocalBounds, + drawOpMode); } return status; } @@ -1343,7 +1361,7 @@ private: float mX; float mY; const float* mPositions; - float mLength; + float mTotalAdvance; mat4 mPrecacheTransform; }; diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 876c38a..6d85a16 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -257,7 +257,8 @@ status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float left, float top bitmap = refBitmap(bitmap); paint = refPaint(paint); - addDrawOp(new (alloc()) DrawBitmapOp(bitmap, left, top, paint)); + const AssetAtlas::Entry* entry = mCaches.assetAtlas.getEntry(bitmap); + addDrawOp(new (alloc()) DrawBitmapOp(bitmap, left, top, paint, entry)); return DrawGlInfo::kStatusDone; } @@ -281,7 +282,8 @@ status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float srcLeft, float (srcBottom - srcTop == dstBottom - dstTop) && (srcRight - srcLeft == dstRight - dstLeft)) { // transform simple rect to rect drawing case into position bitmap ops, since they merge - addDrawOp(new (alloc()) DrawBitmapOp(bitmap, dstLeft, dstTop, paint)); + const AssetAtlas::Entry* entry = mCaches.assetAtlas.getEntry(bitmap); + addDrawOp(new (alloc()) DrawBitmapOp(bitmap, dstLeft, dstTop, paint, entry)); return DrawGlInfo::kStatusDone; } @@ -313,20 +315,15 @@ status_t DisplayListRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, in return DrawGlInfo::kStatusDone; } -status_t DisplayListRenderer::drawPatch(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, SkPaint* paint) { +status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, + float left, float top, float right, float bottom, SkPaint* paint) { int alpha; SkXfermode::Mode mode; OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode); 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)); + addDrawOp(new (alloc()) DrawPatchOp(bitmap, patch, left, top, right, bottom, alpha, mode)); return DrawGlInfo::kStatusDone; } @@ -423,17 +420,16 @@ status_t DisplayListRenderer::drawPosText(const char* text, int bytesCount, int status_t DisplayListRenderer::drawText(const char* text, int bytesCount, int count, float x, float y, const float* positions, SkPaint* paint, - float length, DrawOpMode drawOpMode) { + float totalAdvance, const Rect& bounds, DrawOpMode drawOpMode) { if (!text || count <= 0) return DrawGlInfo::kStatusDone; - if (length < 0.0f) length = paint->measureText(text, bytesCount); - text = refText(text, bytesCount); positions = refBuffer<float>(positions, count * 2); paint = refPaint(paint); - DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count, x, y, positions, paint, length); + DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count, + x, y, positions, paint, totalAdvance, bounds); addDrawOp(op); return DrawGlInfo::kStatusDone; } diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index 75abad6..85d6107 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -103,8 +103,7 @@ public: virtual status_t drawBitmapData(SkBitmap* bitmap, float left, float top, SkPaint* paint); virtual status_t drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHeight, float* vertices, int* colors, SkPaint* paint); - virtual status_t drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, + virtual status_t drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, float left, float top, float right, float bottom, SkPaint* paint); virtual status_t drawColor(int color, SkXfermode::Mode mode); virtual status_t drawRect(float left, float top, float right, float bottom, SkPaint* paint); @@ -122,7 +121,8 @@ public: virtual status_t drawPosText(const char* text, int bytesCount, int count, 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, DrawOpMode drawOpMode); + const float* positions, SkPaint* paint, float totalAdvance, const Rect& bounds, + DrawOpMode drawOpMode); virtual status_t drawRects(const float* rects, int count, SkPaint* paint); diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp index 19b3849..649a7bc 100644 --- a/libs/hwui/Dither.cpp +++ b/libs/hwui/Dither.cpp @@ -24,12 +24,15 @@ namespace uirenderer { // Lifecycle /////////////////////////////////////////////////////////////////////////////// +Dither::Dither(): mCaches(NULL), mInitialized(false), mDitherTexture(0) { +} + void Dither::bindDitherTexture() { if (!mInitialized) { bool useFloatTexture = Extensions::getInstance().getMajorGlVersion() >= 3; glGenTextures(1, &mDitherTexture); - glBindTexture(GL_TEXTURE_2D, mDitherTexture); + mCaches->bindTexture(mDitherTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -68,13 +71,13 @@ void Dither::bindDitherTexture() { mInitialized = true; } else { - glBindTexture(GL_TEXTURE_2D, mDitherTexture); + mCaches->bindTexture(mDitherTexture); } } void Dither::clear() { if (mInitialized) { - glDeleteTextures(1, &mDitherTexture); + mCaches->deleteTexture(mDitherTexture); mInitialized = false; } } @@ -84,8 +87,10 @@ void Dither::clear() { /////////////////////////////////////////////////////////////////////////////// void Dither::setupProgram(Program* program, GLuint* textureUnit) { + if (!mCaches) mCaches = &Caches::getInstance(); + GLuint textureSlot = (*textureUnit)++; - Caches::getInstance().activeTexture(textureSlot); + mCaches->activeTexture(textureSlot); bindDitherTexture(); diff --git a/libs/hwui/Dither.h b/libs/hwui/Dither.h index 4d1f921..546236b 100644 --- a/libs/hwui/Dither.h +++ b/libs/hwui/Dither.h @@ -24,9 +24,7 @@ namespace android { namespace uirenderer { -/////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// +class Caches; // Must be a power of two #define DITHER_KERNEL_SIZE 4 @@ -39,7 +37,7 @@ namespace uirenderer { */ class Dither { public: - Dither(): mInitialized(false), mDitherTexture(0) { } + Dither(); void clear(); void setupProgram(Program* program, GLuint* textureUnit); @@ -47,6 +45,7 @@ public: private: void bindDitherTexture(); + Caches* mCaches; bool mInitialized; GLuint mDitherTexture; }; diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp index 51aec8d..eefdb84 100644 --- a/libs/hwui/Extensions.cpp +++ b/libs/hwui/Extensions.cpp @@ -14,6 +14,16 @@ * limitations under the License. */ +#define LOG_TAG "OpenGLRenderer" + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +#include <utils/Log.h> + #include "Debug.h" #include "Extensions.h" @@ -40,33 +50,22 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// 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); + // Query GL extensions + findExtensions((const char*) glGetString(GL_EXTENSIONS), mGlExtensionList); + mHasNPot = hasGlExtension("GL_OES_texture_npot"); + mHasFramebufferFetch = hasGlExtension("GL_NV_shader_framebuffer_fetch"); + mHasDiscardFramebuffer = hasGlExtension("GL_EXT_discard_framebuffer"); + mHasDebugMarker = hasGlExtension("GL_EXT_debug_marker"); + mHasDebugLabel = hasGlExtension("GL_EXT_debug_label"); + mHasTiledRendering = hasGlExtension("GL_QCOM_tiled_rendering"); + mHas1BitStencil = hasGlExtension("GL_OES_stencil1"); + mHas4BitStencil = hasGlExtension("GL_OES_stencil4"); + + // Query EGL extensions + findExtensions(eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS), mEglExtensionList); + mHasNvSystemTime = hasEglExtension("EGL_NV_system_time"); 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: @@ -88,22 +87,41 @@ Extensions::Extensions(): Singleton<Extensions>() { } Extensions::~Extensions() { - free(mExtensions); - free(mVersion); } /////////////////////////////////////////////////////////////////////////////// // Methods /////////////////////////////////////////////////////////////////////////////// -bool Extensions::hasExtension(const char* extension) const { +bool Extensions::hasGlExtension(const char* extension) const { const String8 s(extension); - return mExtensionList.indexOf(s) >= 0; + return mGlExtensionList.indexOf(s) >= 0; +} + +bool Extensions::hasEglExtension(const char* extension) const { + const String8 s(extension); + return mEglExtensionList.indexOf(s) >= 0; +} + +void Extensions::findExtensions(const char* extensions, SortedVector<String8>& list) const { + const char* current = extensions; + const char* head = current; + EXT_LOGD("Available extensions:"); + do { + head = strchr(current, ' '); + String8 s(current, head ? head - current : strlen(current)); + if (s.length()) { + list.add(s); + EXT_LOGD(" %s", s.string()); + } + current = head + 1; + } while (head); } void Extensions::dump() const { - ALOGD("%s", mVersion); - ALOGD("Supported extensions:\n%s", mExtensions); + ALOGD("%s", (const char*) glGetString(GL_VERSION)); + ALOGD("Supported GL extensions:\n%s", (const char*) glGetString(GL_EXTENSIONS)); + ALOGD("Supported EGL extensions:\n%s", eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS)); } }; // namespace uirenderer diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h index 54a3987..3112244 100644 --- a/libs/hwui/Extensions.h +++ b/libs/hwui/Extensions.h @@ -17,13 +17,12 @@ #ifndef ANDROID_HWUI_EXTENSIONS_H #define ANDROID_HWUI_EXTENSIONS_H +#include <cutils/compiler.h> + #include <utils/Singleton.h> #include <utils/SortedVector.h> #include <utils/String8.h> -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> - namespace android { namespace uirenderer { @@ -31,11 +30,8 @@ namespace uirenderer { // Classes /////////////////////////////////////////////////////////////////////////////// -class Extensions: public Singleton<Extensions> { +class ANDROID_API Extensions: public Singleton<Extensions> { public: - Extensions(); - ~Extensions(); - inline bool hasNPot() const { return mHasNPot; } inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; } inline bool hasDiscardFramebuffer() const { return mHasDiscardFramebuffer; } @@ -44,21 +40,26 @@ public: inline bool hasTiledRendering() const { return mHasTiledRendering; } inline bool has1BitStencil() const { return mHas1BitStencil; } inline bool has4BitStencil() const { return mHas4BitStencil; } + inline bool hasNvSystemTime() const { return mHasNvSystemTime; } inline int getMajorGlVersion() const { return mVersionMajor; } inline int getMinorGlVersion() const { return mVersionMinor; } - bool hasExtension(const char* extension) const; + bool hasGlExtension(const char* extension) const; + bool hasEglExtension(const char* extension) const; void dump() const; private: - friend class Singleton<Extensions>; + Extensions(); + ~Extensions(); - SortedVector<String8> mExtensionList; + void findExtensions(const char* extensions, SortedVector<String8>& list) const; + + friend class Singleton<Extensions>; - char* mExtensions; - char* mVersion; + SortedVector<String8> mGlExtensionList; + SortedVector<String8> mEglExtensionList; bool mHasNPot; bool mHasFramebufferFetch; @@ -68,6 +69,7 @@ private: bool mHasTiledRendering; bool mHas1BitStencil; bool mHas4BitStencil; + bool mHasNvSystemTime; int mVersionMajor; int mVersionMinor; diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 543cfa2..3e3d882 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -24,7 +24,9 @@ #include <utils/Functor.h> #include <utils/Log.h> +#ifdef ANDROID_ENABLE_RENDERSCRIPT #include <RenderScript.h> +#endif #include "utils/Blur.h" #include "utils/Timing.h" @@ -374,7 +376,7 @@ void FontRenderer::checkTextureUpdate() { if (cacheTexture->getTextureId() != lastTextureId) { lastTextureId = cacheTexture->getTextureId(); caches.activeTexture(0); - glBindTexture(GL_TEXTURE_2D, lastTextureId); + caches.bindTexture(lastTextureId); } if (cacheTexture->upload()) { @@ -427,7 +429,7 @@ void FontRenderer::issueDrawCommand() { first = false; } - glBindTexture(GL_TEXTURE_2D, texture->getTextureId()); + caches.bindTexture(texture->getTextureId()); texture->setLinearFiltering(mLinearFiltering, false); TextureVertex* mesh = texture->mesh(); @@ -532,13 +534,18 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const ch return image; } +#ifdef ANDROID_ENABLE_RENDERSCRIPT // 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); +#else + int size = paddedWidth * paddedHeight; + uint8_t* dataBuffer = (uint8_t*) malloc(size); +#endif + memset(dataBuffer, 0, size); int penX = radius - bounds.left; @@ -633,43 +640,46 @@ void FontRenderer::removeFont(const Font* font) { } 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); +#ifdef ANDROID_ENABLE_RENDERSCRIPT + if (width * height * radius >= RS_MIN_INPUT_CUTOFF) { + uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height); + + if (mRs.get() == 0) { + mRs = new RSC::RS(); + if (!mRs->init(true, true)) { + ALOGE("blur RS failed to init"); + } - uint8_t* scratch = new uint8_t[width * height]; - Blur::horizontal(gaussian, radius, *image, scratch, width, height); - Blur::vertical(gaussian, radius, scratch, *image, width, height); + mRsElement = RSC::Element::A_8(mRs); + mRsScript = new RSC::ScriptIntrinsicBlur(mRs, mRsElement); + } - delete[] gaussian; - delete[] scratch; - return; - } + 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); - uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height); + mRsScript->setRadius(radius); + mRsScript->blur(ain, aout); - if (mRs.get() == 0) { - mRs = new RSC::RS(); - if (!mRs->init(true, true)) { - ALOGE("blur RS failed to init"); - } + // replace the original image's pointer, avoiding a copy back to the original buffer + free(*image); + *image = outImage; - mRsElement = RSC::Element::A_8(mRs); - mRsScript = new RSC::ScriptIntrinsicBlur(mRs, mRsElement); + return; } +#endif - 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); + float *gaussian = new float[2 * radius + 1]; + Blur::generateGaussianWeights(gaussian, radius); - mRsScript->setRadius(radius); - mRsScript->blur(ain, aout); + uint8_t* scratch = new uint8_t[width * height]; + Blur::horizontal(gaussian, radius, *image, scratch, width, height); + Blur::vertical(gaussian, radius, scratch, *image, width, height); - // replace the original image's pointer, avoiding a copy back to the original buffer - free(*image); - *image = outImage; + delete[] gaussian; + delete[] scratch; } uint32_t FontRenderer::getCacheSize() const { diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index 307a1d9..cbbd871 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -19,6 +19,7 @@ #include <utils/LruCache.h> #include <utils/Vector.h> +#include <utils/StrongPointer.h> #include <SkPaint.h> @@ -32,11 +33,13 @@ #include "Matrix.h" #include "Properties.h" +#ifdef ANDROID_ENABLE_RENDERSCRIPT namespace RSC { class Element; class RS; class ScriptIntrinsicBlur; } +#endif class Functor; @@ -168,10 +171,12 @@ private: bool mLinearFiltering; +#ifdef ANDROID_ENABLE_RENDERSCRIPT // RS constructs sp<RSC::RS> mRs; sp<const RSC::Element> mRsElement; sp<RSC::ScriptIntrinsicBlur> mRsScript; +#endif static void computeGaussianWeights(float* weights, int32_t radius); static void horizontalBlur(float* weights, int32_t radius, const uint8_t *source, uint8_t *dest, diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index 507ed95..1815bff 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -120,7 +120,7 @@ void GradientCache::operator()(GradientCacheEntry& shader, Texture*& texture) { const uint32_t size = texture->width * texture->height * bytesPerPixel(); mSize -= size; - glDeleteTextures(1, &texture->id); + texture->deleteTexture(); delete texture; } } @@ -173,7 +173,7 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient, GradientInfo info; getGradientInfo(colors, count, info); - Texture* texture = new Texture; + Texture* texture = new Texture(); texture->width = info.width; texture->height = 2; texture->blend = info.hasAlpha; @@ -286,7 +286,7 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, memcpy(pixels + rowBytes, pixels, rowBytes); glGenTextures(1, &texture->id); - glBindTexture(GL_TEXTURE_2D, texture->id); + Caches::getInstance().bindTexture(texture->id); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); if (mUseFloatTexture) { diff --git a/libs/hwui/Image.cpp b/libs/hwui/Image.cpp new file mode 100644 index 0000000..edf3930 --- /dev/null +++ b/libs/hwui/Image.cpp @@ -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. + */ + +#define LOG_TAG "OpenGLRenderer" + +#include <utils/Log.h> + +#include "Caches.h" +#include "Image.h" + +namespace android { +namespace uirenderer { + +Image::Image(sp<GraphicBuffer> buffer) { + // Create the EGLImage object that maps the GraphicBuffer + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer->getNativeBuffer(); + EGLint attrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; + + mImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs); + + if (mImage == EGL_NO_IMAGE_KHR) { + ALOGW("Error creating image (%#x)", eglGetError()); + mTexture = 0; + } else { + // Create a 2D texture to sample from the EGLImage + glGenTextures(1, &mTexture); + Caches::getInstance().bindTexture(mTexture); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, mImage); + + GLenum status = GL_NO_ERROR; + while ((status = glGetError()) != GL_NO_ERROR) { + ALOGW("Error creating image (%#x)", status); + } + } +} + +Image::~Image() { + if (mImage != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), mImage); + mImage = EGL_NO_IMAGE_KHR; + + Caches::getInstance().deleteTexture(mTexture); + mTexture = 0; + } +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/Image.h b/libs/hwui/Image.h new file mode 100644 index 0000000..2514535 --- /dev/null +++ b/libs/hwui/Image.h @@ -0,0 +1,67 @@ +/* + * 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_IMAGE_H +#define ANDROID_HWUI_IMAGE_H + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +#include <ui/GraphicBuffer.h> + +namespace android { +namespace uirenderer { + +/** + * A simple wrapper that creates an EGLImage and a texture for a GraphicBuffer. + */ +class Image { +public: + /** + * Creates a new image from the specified graphic buffer. If the image + * cannot be created, getTexture() will return 0 and getImage() will + * return EGL_NO_IMAGE_KHR. + */ + Image(sp<GraphicBuffer> buffer); + ~Image(); + + /** + * Returns the name of the GL texture that can be used to sample + * from this image. + */ + GLuint getTexture() const { + return mTexture; + } + + /** + * Returns the name of the EGL image represented by this object. + */ + EGLImageKHR getImage() const { + return mImage; + } + +private: + GLuint mTexture; + EGLImageKHR mImage; +}; // class Image + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_IMAGE_H diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index 4adad05..134f452 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -28,7 +28,8 @@ namespace android { namespace uirenderer { -Layer::Layer(const uint32_t layerWidth, const uint32_t layerHeight) { +Layer::Layer(const uint32_t layerWidth, const uint32_t layerHeight): + caches(Caches::getInstance()), texture(caches) { mesh = NULL; meshIndices = NULL; meshElementCount = 0; @@ -47,11 +48,11 @@ Layer::Layer(const uint32_t layerWidth, const uint32_t layerHeight) { debugDrawUpdate = false; hasDrawnSinceUpdate = false; deferredList = NULL; - Caches::getInstance().resourceCache.incrementRefcount(this); + caches.resourceCache.incrementRefcount(this); } Layer::~Layer() { - if (colorFilter) Caches::getInstance().resourceCache.decrementRefcount(colorFilter); + if (colorFilter) caches.resourceCache.decrementRefcount(colorFilter); removeFbo(); deleteTexture(); @@ -76,7 +77,7 @@ bool Layer::resize(const uint32_t width, const uint32_t height) { return true; } - const uint32_t maxTextureSize = Caches::getInstance().maxTextureSize; + const uint32_t maxTextureSize = caches.maxTextureSize; if (desiredWidth > maxTextureSize || desiredHeight > maxTextureSize) { ALOGW("Layer exceeds max. dimensions supported by the GPU (%dx%d, max=%dx%d)", desiredWidth, desiredHeight, maxTextureSize, maxTextureSize); @@ -89,7 +90,7 @@ bool Layer::resize(const uint32_t width, const uint32_t height) { setSize(desiredWidth, desiredHeight); if (fbo) { - Caches::getInstance().activeTexture(0); + caches.activeTexture(0); bindTexture(); allocateTexture(); @@ -120,14 +121,14 @@ void Layer::removeFbo(bool flush) { glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0); if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, previousFbo); - Caches::getInstance().renderBufferCache.put(stencil); + caches.renderBufferCache.put(stencil); stencil = NULL; } if (fbo) { if (flush) LayerRenderer::flushLayer(this); // If put fails the cache will delete the FBO - Caches::getInstance().fboCache.put(fbo); + caches.fboCache.put(fbo); fbo = 0; } } @@ -138,11 +139,51 @@ void Layer::setPaint(SkPaint* paint) { void Layer::setColorFilter(SkiaColorFilter* filter) { if (colorFilter) { - Caches::getInstance().resourceCache.decrementRefcount(colorFilter); + caches.resourceCache.decrementRefcount(colorFilter); } colorFilter = filter; if (colorFilter) { - Caches::getInstance().resourceCache.incrementRefcount(colorFilter); + caches.resourceCache.incrementRefcount(colorFilter); + } +} + +void Layer::bindTexture() const { + if (texture.id) { + caches.bindTexture(renderTarget, texture.id); + } +} + +void Layer::bindStencilRenderBuffer() const { + if (stencil) { + stencil->bind(); + } +} + +void Layer::generateTexture() { + if (!texture.id) { + glGenTextures(1, &texture.id); + } +} + +void Layer::deleteTexture() { + if (texture.id) { + texture.deleteTexture(); + texture.id = 0; + } +} + +void Layer::clearTexture() { + texture.id = 0; +} + +void Layer::allocateTexture() { +#if DEBUG_LAYERS + ALOGD(" Allocate layer: %dx%d", getWidth(), getHeight()); +#endif + if (texture.id) { + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + glTexImage2D(renderTarget, 0, GL_RGBA, getWidth(), getHeight(), 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); } } diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index 7186603..326b25a 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -40,6 +40,7 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// // Forward declarations +class Caches; class OpenGLRenderer; class DisplayList; class DeferredDisplayList; @@ -221,50 +222,19 @@ struct Layer { ANDROID_API void setColorFilter(SkiaColorFilter* filter); - inline void bindTexture() const { - if (texture.id) { - glBindTexture(renderTarget, texture.id); - } - } - - inline void bindStencilRenderBuffer() const { - if (stencil) { - stencil->bind(); - } - } + void bindStencilRenderBuffer() const; - inline void generateTexture() { - if (!texture.id) { - glGenTextures(1, &texture.id); - } - } - - inline void deleteTexture() { - if (texture.id) { - glDeleteTextures(1, &texture.id); - texture.id = 0; - } - } + void bindTexture() const; + void generateTexture(); + void allocateTexture(); + void deleteTexture(); /** * When the caller frees the texture itself, the caller * must call this method to tell this layer that it lost * the texture. */ - void clearTexture() { - texture.id = 0; - } - - inline void allocateTexture() { -#if DEBUG_LAYERS - ALOGD(" Allocate layer: %dx%d", getWidth(), getHeight()); -#endif - if (texture.id) { - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - glTexImage2D(renderTarget, 0, GL_RGBA, getWidth(), getHeight(), 0, - GL_RGBA, GL_UNSIGNED_BYTE, NULL); - } - } + ANDROID_API void clearTexture(); inline mat4& getTexTransform() { return texTransform; @@ -320,6 +290,8 @@ struct Layer { bool hasDrawnSinceUpdate; private: + Caches& caches; + /** * Name of the FBO used to render the layer. If the name is 0 * this layer is not backed by an FBO, but a simple texture. diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index 3e55fff..cfb1e97 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -436,7 +436,7 @@ bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) { if ((error = glGetError()) != GL_NO_ERROR) goto error; caches.activeTexture(0); - glBindTexture(GL_TEXTURE_2D, texture); + caches.bindTexture(texture); glPixelStorei(GL_PACK_ALIGNMENT, bitmap->bytesPerPixel()); @@ -467,7 +467,7 @@ bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) { mat4 texTransform(layer->getTexTransform()); mat4 invert; - invert.translate(0.0f, 1.0f, 0.0f); + invert.translate(0.0f, 1.0f); invert.scale(1.0f, -1.0f, 1.0f); layer->getTexTransform().multiply(invert); @@ -498,7 +498,7 @@ error: glBindFramebuffer(GL_FRAMEBUFFER, previousFbo); layer->setAlpha(alpha, mode); layer->setFbo(previousLayerFbo); - glDeleteTextures(1, &texture); + caches.deleteTexture(texture); caches.fboCache.put(fbo); glViewport(previousViewport[0], previousViewport[1], previousViewport[2], previousViewport[3]); diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h index 75e280c..df744be 100644 --- a/libs/hwui/Matrix.h +++ b/libs/hwui/Matrix.h @@ -128,10 +128,27 @@ public: void multiply(float v); - void translate(float x, float y, float z) { - Matrix4 u; - u.loadTranslate(x, y, z); - multiply(u); + void translate(float x, float y) { + if ((getType() & sGeometryMask) == kTypeTranslate) { + data[kTranslateX] += x; + data[kTranslateY] += y; + } else { + // Doing a translation will only affect the translate bit of the type + // Save the type + uint32_t type = mType; + + Matrix4 u; + u.loadTranslate(x, y, 0.0f); + multiply(u); + + // Restore the type and fix the translate bit + mType = type; + if (data[kTranslateX] != 0.0f || data[kTranslateY] != 0.0f) { + mType |= kTypeTranslate; + } else { + mType &= ~kTypeTranslate; + } + } } void scale(float sx, float sy, float sz) { diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index ddb190e..d95a62c 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -21,7 +21,6 @@ #include <sys/types.h> #include <SkCanvas.h> -#include <SkPathMeasure.h> #include <SkTypeface.h> #include <utils/Log.h> @@ -120,6 +119,7 @@ OpenGLRenderer::OpenGLRenderer(): mFirstSnapshot = new Snapshot; mFrameStarted = false; + mCountOverdraw = false; mScissorOptimizationDisabled = false; } @@ -222,6 +222,7 @@ status_t OpenGLRenderer::prepare(bool 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 @@ -253,7 +254,7 @@ void OpenGLRenderer::discardFramebuffer(float left, float top, float right, floa } status_t OpenGLRenderer::clear(float left, float top, float right, float bottom, bool opaque) { - if (!opaque) { + if (!opaque || mCountOverdraw) { mCaches.enableScissor(); mCaches.setScissor(left, mSnapshot->height - bottom, right - left, bottom - top); glClear(GL_COLOR_BUFFER_BIT); @@ -335,6 +336,10 @@ void OpenGLRenderer::finish() { #endif } + if (mCountOverdraw) { + countOverdraw(); + } + mFrameStarted = false; } @@ -366,6 +371,7 @@ void OpenGLRenderer::resume() { dirtyClip(); mCaches.activeTexture(0); + mCaches.resetBoundTextures(); mCaches.blend = true; glEnable(GL_BLEND); @@ -459,7 +465,7 @@ status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { info.height = getSnapshot()->height; getSnapshot()->transform->copyTo(&info.transform[0]); - status_t result = (*functor)(DrawGlInfo::kModeDraw, &info) | DrawGlInfo::kStatusDrew; + status_t result = (*functor)(DrawGlInfo::kModeDraw, &info); if (result != DrawGlInfo::kStatusDone) { Rect localDirty(info.dirtyLeft, info.dirtyTop, info.dirtyRight, info.dirtyBottom); @@ -471,7 +477,7 @@ status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { } resume(); - return result; + return result | DrawGlInfo::kStatusDrew; } /////////////////////////////////////////////////////////////////////////////// @@ -524,6 +530,21 @@ void OpenGLRenderer::renderOverdraw() { } } +void OpenGLRenderer::countOverdraw() { + size_t count = mWidth * mHeight; + uint32_t* buffer = new uint32_t[count]; + glReadPixels(0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, &buffer[0]); + + size_t total = 0; + for (size_t i = 0; i < count; i++) { + total += buffer[i] & 0xff; + } + + mOverdraw = total / float(count); + + delete[] buffer; +} + /////////////////////////////////////////////////////////////////////////////// // Layers /////////////////////////////////////////////////////////////////////////////// @@ -1339,11 +1360,15 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef // state has bounds initialized in local coordinates if (!state.mBounds.isEmpty()) { currentMatrix.mapRect(state.mBounds); + state.mClipped = !currentClip.contains(state.mBounds); if (!state.mBounds.intersect(currentClip)) { // quick rejected return true; } } else { + // If we don't have bounds, let's assume we're clipped + // to prevent merging + state.mClipped = true; state.mBounds.set(currentClip); } } @@ -1382,7 +1407,7 @@ void OpenGLRenderer::setFullScreenClip() { /////////////////////////////////////////////////////////////////////////////// void OpenGLRenderer::translate(float dx, float dy) { - currentTransform().translate(dx, dy, 0.0f); + currentTransform().translate(dx, dy); } void OpenGLRenderer::rotate(float degrees) { @@ -1656,6 +1681,8 @@ void OpenGLRenderer::setupDraw(bool clear) { mDescription.hasDebugHighlight = !mCaches.debugOverdraw && mCaches.debugStencilClip == Caches::kStencilShowHighlight && mCaches.stencil.isTestEnabled(); + + mDescription.emulateStencil = mCountOverdraw; } void OpenGLRenderer::setupDrawWithTexture(bool isAlpha8) { @@ -1681,11 +1708,6 @@ void OpenGLRenderer::setupDrawAA() { mDescription.isAA = true; } -void OpenGLRenderer::setupDrawPoint(float pointSize) { - mDescription.isPoint = true; - mDescription.pointSize = pointSize; -} - void OpenGLRenderer::setupDrawColor(int color, int alpha) { mColorA = alpha / 255.0f; mColorR = mColorA * ((color >> 16) & 0xFF) / 255.0f; @@ -1800,11 +1822,6 @@ void OpenGLRenderer::setupDrawModelView(float left, float top, float right, floa } } -void OpenGLRenderer::setupDrawPointUniforms() { - int slot = mCaches.currentProgram->getUniform("pointSize"); - glUniform1f(slot, mDescription.pointSize); -} - void OpenGLRenderer::setupDrawColorUniforms() { if ((mColorSet && !mDrawModifiers.mShader) || (mDrawModifiers.mShader && mSetShaderColor)) { mCaches.currentProgram->setColor(mColorR, mColorG, mColorB, mColorA); @@ -1873,7 +1890,7 @@ void OpenGLRenderer::setupDrawTextureTransformUniforms(mat4& transform) { void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint vbo) { bool force = false; - if (!vertices) { + if (!vertices || vbo) { force = mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo); } else { force = mCaches.unbindMeshBuffer(); @@ -1904,8 +1921,18 @@ void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLvoid* mCaches.unbindIndicesBuffer(); } -void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords) { - bool force = mCaches.unbindMeshBuffer(); +void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords, GLuint vbo) { + bool force = false; + // If vbo is != 0 we want to treat the vertices parameter as an offset inside + // a VBO. However, if vertices is set to NULL and vbo == 0 then we want to + // use the default VBO found in Caches + if (!vertices || vbo) { + force = mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo); + } else { + force = mCaches.unbindMeshBuffer(); + } + mCaches.bindIndicesBuffer(); + mCaches.bindPositionVertexPointer(force, vertices); if (mCaches.currentProgram->texCoords >= 0) { mCaches.bindTexCoordsVertexPointer(force, texCoords); @@ -1980,20 +2007,23 @@ void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, Sk texture->setFilter(FILTER(paint), true); } + // No need to check for a UV mapper on the texture object, only ARGB_8888 + // bitmaps get packed in the atlas 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); + paint != NULL, color, alpha, mode, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset, + GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform); } status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureVertex* vertices, - const Rect& bounds, SkPaint* paint) { + bool transformed, const Rect& bounds, SkPaint* paint) { // merged draw operations don't need scissor, but clip should still be valid mCaches.setScissorEnabled(mScissorOptimizationDisabled); mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = getTexture(bitmap); if (!texture) return DrawGlInfo::kStatusDone; + const AutoTexture autoCleanup(texture); int alpha; @@ -2001,7 +2031,7 @@ status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureV getAlphaAndMode(paint, &alpha, &mode); texture->setWrap(GL_CLAMP_TO_EDGE, true); - texture->setFilter(GL_NEAREST, true); // merged ops are always pure-translation for now + texture->setFilter(transformed ? FILTER(paint) : GL_NEAREST, true); const float x = (int) floorf(bounds.left + 0.5f); const float y = (int) floorf(bounds.top + 0.5f); @@ -2030,7 +2060,7 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkP } mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = getTexture(bitmap); if (!texture) return DrawGlInfo::kStatusDone; const AutoTexture autoCleanup(texture); @@ -2053,7 +2083,7 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* } mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = getTexture(bitmap); if (!texture) return DrawGlInfo::kStatusDone; const AutoTexture autoCleanup(texture); @@ -2116,6 +2146,10 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes cleanupColors = true; } + mCaches.activeTexture(0); + Texture* texture = mCaches.assetAtlas.getEntryTexture(bitmap); + const UvMapper& mapper(getMapper(texture)); + for (int32_t y = 0; y < meshHeight; y++) { for (int32_t x = 0; x < meshWidth; x++) { uint32_t i = (y * (meshWidth + 1) + x) * 2; @@ -2125,6 +2159,8 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes float v1 = float(y) / meshHeight; float v2 = float(y + 1) / meshHeight; + mapper.map(u1, v1, u2, v2); + int ax = i + (meshWidth + 1) * 2; int ay = ax + 1; int bx = i; @@ -2154,11 +2190,12 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes return DrawGlInfo::kStatusDone; } - mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); if (!texture) { - if (cleanupColors) delete[] colors; - return DrawGlInfo::kStatusDone; + texture = mCaches.textureCache.get(bitmap); + if (!texture) { + if (cleanupColors) delete[] colors; + return DrawGlInfo::kStatusDone; + } } const AutoTexture autoCleanup(texture); @@ -2211,17 +2248,19 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, } mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = getTexture(bitmap); if (!texture) return DrawGlInfo::kStatusDone; const AutoTexture autoCleanup(texture); const float width = texture->width; const float height = texture->height; - const float u1 = fmax(0.0f, srcLeft / width); - const float v1 = fmax(0.0f, srcTop / height); - const float u2 = fmin(1.0f, srcRight / width); - const float v2 = fmin(1.0f, srcBottom / height); + float u1 = fmax(0.0f, srcLeft / width); + float v1 = fmax(0.0f, srcTop / height); + float u2 = fmin(1.0f, srcRight / width); + float v2 = fmin(1.0f, srcBottom / height); + + getMapper(texture).map(u1, v1, u2, v2); mCaches.unbindMeshBuffer(); resetDrawTextureTexCoords(u1, v1, u2, v2); @@ -2292,34 +2331,37 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, return DrawGlInfo::kStatusDrew; } -status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, +status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, float left, float top, float right, float bottom, SkPaint* paint) { int alpha; SkXfermode::Mode mode; getAlphaAndMode(paint, &alpha, &mode); - return drawPatch(bitmap, xDivs, yDivs, colors, width, height, numColors, - left, top, right, bottom, alpha, mode); -} - -status_t OpenGLRenderer::drawPatch(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) { if (quickReject(left, top, right, bottom)) { return DrawGlInfo::kStatusDone; } - alpha *= mSnapshot->alpha; + AssetAtlas::Entry* entry = mCaches.assetAtlas.getEntry(bitmap); + const Patch* mesh = mCaches.patchCache.get(entry, bitmap->width(), bitmap->height(), + right - left, bottom - top, patch); - const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(), - right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors); + return drawPatch(bitmap, mesh, entry, left, top, right, bottom, alpha, mode); +} + +status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const Patch* mesh, + AssetAtlas::Entry* entry, float left, float top, float right, float bottom, + int alpha, SkXfermode::Mode mode) { + + if (quickReject(left, top, right, bottom)) { + return DrawGlInfo::kStatusDone; + } if (CC_LIKELY(mesh && mesh->verticesCount > 0)) { mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = entry ? entry->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); @@ -2342,19 +2384,23 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const } } + alpha *= mSnapshot->alpha; + if (CC_LIKELY(pureTranslate)) { 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, - GL_TRIANGLES, mesh->verticesCount, false, true, mesh->meshBuffer, - true, !mesh->hasEmptyQuads); + right = x + right - left; + bottom = y + bottom - top; + drawIndexedTextureMesh(x, y, right, bottom, texture->id, alpha / 255.0f, + mode, texture->blend, (GLvoid*) mesh->offset, (GLvoid*) mesh->textureOffset, + GL_TRIANGLES, mesh->indexCount, false, true, + mCaches.patchCache.getMeshBuffer(), true, !mesh->hasEmptyQuads); } else { - drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, - mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset, - GL_TRIANGLES, mesh->verticesCount, false, false, mesh->meshBuffer, - true, !mesh->hasEmptyQuads); + drawIndexedTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, + mode, texture->blend, (GLvoid*) mesh->offset, (GLvoid*) mesh->textureOffset, + GL_TRIANGLES, mesh->indexCount, false, false, + mCaches.patchCache.getMeshBuffer(), true, !mesh->hasEmptyQuads); } } @@ -2363,7 +2409,7 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const status_t OpenGLRenderer::drawVertexBuffer(const VertexBuffer& vertexBuffer, SkPaint* paint, bool useOffset) { - if (!vertexBuffer.getSize()) { + if (!vertexBuffer.getVertexCount()) { // no vertices to draw return DrawGlInfo::kStatusDone; } @@ -2401,7 +2447,7 @@ status_t OpenGLRenderer::drawVertexBuffer(const VertexBuffer& vertexBuffer, SkPa glVertexAttribPointer(alphaSlot, 1, GL_FLOAT, GL_FALSE, gAlphaVertexStride, alphaCoords); } - glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getSize()); + glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getVertexCount()); if (isAA) { glDisableVertexAttribArray(alphaSlot); @@ -2464,65 +2510,22 @@ status_t OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { } status_t OpenGLRenderer::drawPoints(float* points, int count, SkPaint* paint) { - if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone; - - // TODO: The paint's cap style defines whether the points are square or circular - // TODO: Handle AA for round points + if (mSnapshot->isIgnored() || count < 2) return DrawGlInfo::kStatusDone; - // A stroke width of 0 has a special meaning in Skia: - // it draws an unscaled 1px point - float strokeWidth = paint->getStrokeWidth(); - const bool isHairLine = paint->getStrokeWidth() == 0.0f; - if (isHairLine) { - // Now that we know it's hairline, we can set the effective width, to be used later - strokeWidth = 1.0f; - } - const float halfWidth = strokeWidth / 2; + count &= ~0x1; // round down to nearest two - int alpha; - SkXfermode::Mode mode; - getAlphaAndMode(paint, &alpha, &mode); - - int verticesCount = count >> 1; - int generatedVerticesCount = 0; - - TextureVertex pointsData[verticesCount]; - TextureVertex* vertex = &pointsData[0]; - - // TODO: We should optimize this method to not generate vertices for points - // that lie outside of the clip. - mCaches.enableScissor(); - - setupDraw(); - setupDrawNoTexture(); - setupDrawPoint(strokeWidth); - setupDrawColor(paint->getColor(), alpha); - setupDrawColorFilter(); - setupDrawShader(); - setupDrawBlending(mode); - setupDrawProgram(); - setupDrawModelViewIdentity(true); - setupDrawColorUniforms(); - setupDrawColorFilterUniforms(); - setupDrawPointUniforms(); - setupDrawShaderIdentityUniforms(); - setupDrawMesh(vertex); - - for (int i = 0; i < count; i += 2) { - TextureVertex::set(vertex++, points[i], points[i + 1], 0.0f, 0.0f); - generatedVerticesCount++; - - float left = points[i] - halfWidth; - float right = points[i] + halfWidth; - float top = points[i + 1] - halfWidth; - float bottom = points [i + 1] + halfWidth; + VertexBuffer buffer; + SkRect bounds; + PathTessellator::tessellatePoints(points, count, paint, mSnapshot->transform, bounds, buffer); - dirtyLayer(left, top, right, bottom, currentTransform()); + if (quickReject(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom)) { + return DrawGlInfo::kStatusDone; } - glDrawArrays(GL_POINTS, 0, generatedVerticesCount); + 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::drawColor(int color, SkXfermode::Mode mode) { @@ -2853,8 +2856,8 @@ mat4 OpenGLRenderer::findBestFontTransform(const mat4& transform) const { return fontTransform; } -status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, - float x, float y, const float* positions, SkPaint* paint, float length, +status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float x, float y, + const float* positions, SkPaint* paint, float totalAdvance, const Rect& bounds, DrawOpMode drawOpMode) { if (drawOpMode == kDrawOpMode_Immediate && @@ -2862,24 +2865,8 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, return DrawGlInfo::kStatusDone; } - if (length < 0.0f) length = paint->measureText(text, bytesCount); - switch (paint->getTextAlign()) { - case SkPaint::kCenter_Align: - x -= length / 2.0f; - break; - case SkPaint::kRight_Align: - x -= length; - break; - default: - break; - } - - SkPaint::FontMetrics metrics; - paint->getFontMetrics(&metrics, 0.0f); if (drawOpMode == kDrawOpMode_Immediate) { - if (quickReject(x, y + metrics.fTop, x + length, y + metrics.fBottom)) { - return DrawGlInfo::kStatusDone; - } + if (quickReject(bounds)) return DrawGlInfo::kStatusDone; } else { // merged draw operations don't need scissor, but clip should still be valid mCaches.setScissorEnabled(mScissorOptimizationDisabled); @@ -2930,7 +2917,7 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, // 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); + Rect layerBounds(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); @@ -2941,20 +2928,20 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, SkPaint paintCopy(*paint); paintCopy.setTextAlign(SkPaint::kLeft_Align); status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y, - positions, hasActiveLayer ? &bounds : NULL, &functor, forceFinish); + positions, hasActiveLayer ? &layerBounds : NULL, &functor, forceFinish); } else { status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y, - positions, hasActiveLayer ? &bounds : NULL, &functor, forceFinish); + positions, hasActiveLayer ? &layerBounds : NULL, &functor, forceFinish); } if ((status || drawOpMode != kDrawOpMode_Immediate) && hasActiveLayer) { if (!pureTranslate) { - transform.mapRect(bounds); + transform.mapRect(layerBounds); } - dirtyLayerUnchecked(bounds, getRegion()); + dirtyLayerUnchecked(layerBounds, getRegion()); } - drawTextDecorations(text, bytesCount, length, oldX, oldY, paint); + drawTextDecorations(text, bytesCount, totalAdvance, oldX, oldY, paint); return DrawGlInfo::kStatusDrew; } @@ -3128,7 +3115,7 @@ void OpenGLRenderer::resetShader() { void OpenGLRenderer::setupShader(SkiaShader* shader) { mDrawModifiers.mShader = shader; if (mDrawModifiers.mShader) { - mDrawModifiers.mShader->set(&mCaches.textureCache, &mCaches.gradientCache); + mDrawModifiers.mShader->setCaches(mCaches); } } @@ -3196,6 +3183,14 @@ SkPaint* OpenGLRenderer::filterPaint(SkPaint* paint) { // Drawing implementation /////////////////////////////////////////////////////////////////////////////// +Texture* OpenGLRenderer::getTexture(SkBitmap* bitmap) { + Texture* texture = mCaches.assetAtlas.getEntryTexture(bitmap); + if (!texture) { + return mCaches.textureCache.get(bitmap); + } + return texture; +} + void OpenGLRenderer::drawPathTexture(const PathTexture* texture, float x, float y, SkPaint* paint) { if (quickReject(x, y, x + texture->width, y + texture->height)) { @@ -3230,17 +3225,12 @@ void OpenGLRenderer::drawPathTexture(const PathTexture* texture, #define kStdUnderline_Offset (1.0f / 9.0f) #define kStdUnderline_Thickness (1.0f / 18.0f) -void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float length, +void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float underlineWidth, float x, float y, SkPaint* paint) { // Handle underline and strike-through uint32_t flags = paint->getFlags(); if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { SkPaint paintCopy(*paint); - float underlineWidth = length; - // If length is > 0.0f, we already measured the text for the text alignment - if (length <= 0.0f) { - underlineWidth = paintCopy.measureText(text, bytesCount); - } if (CC_LIKELY(underlineWidth > 0.0f)) { const float textSize = paintCopy.getTextSize(); @@ -3389,19 +3379,35 @@ void OpenGLRenderer::drawTextureRect(float left, float top, float right, float b texture->setWrap(GL_CLAMP_TO_EDGE, true); + GLvoid* vertices = (GLvoid*) NULL; + GLvoid* texCoords = (GLvoid*) gMeshTextureOffset; + + if (texture->uvMapper) { + vertices = &mMeshVertices[0].position[0]; + texCoords = &mMeshVertices[0].texture[0]; + + Rect uvs(0.0f, 0.0f, 1.0f, 1.0f); + texture->uvMapper->map(uvs); + + resetDrawTextureTexCoords(uvs.left, uvs.top, uvs.right, uvs.bottom); + } + 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, - alpha / 255.0f, mode, texture->blend, (GLvoid*) NULL, - (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount, false, true); + alpha / 255.0f, mode, texture->blend, vertices, texCoords, + GL_TRIANGLE_STRIP, gMeshCount, false, true); } else { texture->setFilter(FILTER(paint), true); drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, mode, - texture->blend, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset, - GL_TRIANGLE_STRIP, gMeshCount); + texture->blend, vertices, texCoords, GL_TRIANGLE_STRIP, gMeshCount); + } + + if (texture->uvMapper) { + resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f); } } @@ -3438,6 +3444,33 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b finishDrawTexture(); } +void OpenGLRenderer::drawIndexedTextureMesh(float left, float top, float right, float bottom, + GLuint texture, float alpha, SkXfermode::Mode mode, bool blend, + GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount, + bool swapSrcDst, bool ignoreTransform, GLuint vbo, bool ignoreScale, bool dirty) { + + setupDraw(); + setupDrawWithTexture(); + setupDrawColor(alpha, alpha, alpha, alpha); + setupDrawColorFilter(); + setupDrawBlending(blend, mode, swapSrcDst); + setupDrawProgram(); + if (!dirty) setupDrawDirtyRegionsDisabled(); + if (!ignoreScale) { + setupDrawModelView(left, top, right, bottom, ignoreTransform); + } else { + setupDrawModelViewTranslate(left, top, right, bottom, ignoreTransform); + } + setupDrawTexture(texture); + setupDrawPureColorUniforms(); + setupDrawColorFilterUniforms(); + setupDrawMeshIndices(vertices, texCoords, vbo); + + glDrawElements(drawMode, elementsCount, GL_UNSIGNED_SHORT, NULL); + + 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, @@ -3471,6 +3504,19 @@ void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, f void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, ProgramDescription& description, bool swapSrcDst) { + if (mCountOverdraw) { + if (!mCaches.blend) glEnable(GL_BLEND); + if (mCaches.lastSrcMode != GL_ONE || mCaches.lastDstMode != GL_ONE) { + glBlendFunc(GL_ONE, GL_ONE); + } + + mCaches.blend = true; + mCaches.lastSrcMode = GL_ONE; + mCaches.lastDstMode = GL_ONE; + + return; + } + blend = blend || mode != SkXfermode::kSrcOver_Mode; if (blend) { diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index a0ad888..ce4ce42 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -34,6 +34,8 @@ #include <cutils/compiler.h> +#include <androidfw/ResourceTypes.h> + #include "Debug.h" #include "Extensions.h" #include "Matrix.h" @@ -43,6 +45,7 @@ #include "Vertex.h" #include "SkiaShader.h" #include "SkiaColorFilter.h" +#include "UvMapper.h" #include "Caches.h" namespace android { @@ -78,11 +81,13 @@ enum DrawOpMode { }; struct DeferredDisplayState { - Rect mBounds; // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped. + // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped + Rect mBounds; // the below are set and used by the OpenGLRenderer at record and deferred playback bool mClipValid; Rect mClip; + bool mClipped; mat4 mMatrix; DrawModifiers mDrawModifiers; float mAlpha; @@ -188,6 +193,14 @@ public: */ virtual void resume(); + ANDROID_API void setCountOverdrawEnabled(bool enabled) { + mCountOverdraw = enabled; + } + + ANDROID_API float getOverdraw() { + return mCountOverdraw ? mOverdraw : 0.0f; + } + ANDROID_API status_t invokeFunctors(Rect& dirty); ANDROID_API void detachFunctor(Functor* functor); ANDROID_API void attachFunctor(Functor* functor); @@ -229,6 +242,9 @@ public: ANDROID_API const Rect& getClipBounds(); ANDROID_API bool quickReject(float left, float top, float right, float bottom); + bool quickReject(const Rect& bounds) { + return quickReject(bounds.left, bounds.top, bounds.right, bounds.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); @@ -240,7 +256,7 @@ public: virtual status_t drawLayer(Layer* layer, float x, float y); virtual status_t drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint); status_t drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureVertex* vertices, - const Rect& bounds, SkPaint* paint); + bool transformed, const Rect& bounds, SkPaint* paint); virtual status_t drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint); virtual status_t drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, @@ -248,11 +264,9 @@ public: virtual status_t drawBitmapData(SkBitmap* bitmap, float left, float top, SkPaint* paint); virtual status_t drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHeight, float* vertices, int* colors, SkPaint* paint); - virtual status_t drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, + virtual status_t drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, float left, float top, float right, float bottom, SkPaint* paint); - status_t drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, + status_t drawPatch(SkBitmap* bitmap, const Patch* mesh, AssetAtlas::Entry* entry, float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode); virtual status_t drawColor(int color, SkXfermode::Mode mode); virtual status_t drawRect(float left, float top, float right, float bottom, SkPaint* paint); @@ -270,7 +284,7 @@ public: virtual status_t drawPosText(const char* text, int bytesCount, int count, 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, + const float* positions, SkPaint* paint, float totalAdvance, const Rect& bounds, DrawOpMode drawOpMode = kDrawOpMode_Immediate); virtual status_t drawRects(const float* rects, int count, SkPaint* paint); @@ -798,6 +812,12 @@ private: bool swapSrcDst = false, bool ignoreTransform = false, GLuint vbo = 0, bool ignoreScale = false, bool dirty = true); + void drawIndexedTextureMesh(float left, float top, float right, float bottom, GLuint texture, + float alpha, SkXfermode::Mode mode, bool blend, + GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount, + 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, @@ -808,12 +828,12 @@ private: * * @param text The text to decor * @param bytesCount The number of bytes in the text - * @param length The length in pixels of the text, can be <= 0.0f to force a measurement + * @param totalAdvance The total advance in pixels, defines underline/strikethrough length * @param x The x coordinate where the text will be drawn * @param y The y coordinate where the text will be drawn * @param paint The paint to draw the text with */ - void drawTextDecorations(const char* text, int bytesCount, float length, + void drawTextDecorations(const char* text, int bytesCount, float totalAdvance, float x, float y, SkPaint* paint); /** @@ -868,7 +888,7 @@ private: * prior to calling this method. */ inline void bindTexture(GLuint texture) { - glBindTexture(GL_TEXTURE_2D, texture); + mCaches.bindTexture(texture); } /** @@ -876,7 +896,7 @@ private: * prior to calling this method. */ inline void bindExternalTexture(GLuint texture) { - glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture); + mCaches.bindTexture(GL_TEXTURE_EXTERNAL_OES, texture); } /** @@ -911,7 +931,6 @@ private: void setupDrawWithExternalTexture(); void setupDrawNoTexture(); void setupDrawAA(); - void setupDrawPoint(float pointSize); void setupDrawColor(int color, int alpha); void setupDrawColor(float r, float g, float b, float a); void setupDrawAlpha8Color(int color, int alpha); @@ -929,7 +948,6 @@ private: bool ignoreTransform = false, bool ignoreModelView = false); void setupDrawModelViewTranslate(float left, float top, float right, float bottom, bool ignoreTransform = false); - void setupDrawPointUniforms(); void setupDrawColorUniforms(); void setupDrawPureColorUniforms(); void setupDrawShaderIdentityUniforms(); @@ -943,7 +961,7 @@ private: 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 setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords, GLuint vbo = 0); void setupDrawVertices(GLvoid* vertices); void finishDrawTexture(); void accountForClear(SkXfermode::Mode mode); @@ -973,6 +991,7 @@ private: void debugOverdraw(bool enable, bool clear); void renderOverdraw(); + void countOverdraw(); /** * Should be invoked every time the glScissor is modified. @@ -985,6 +1004,17 @@ private: return *mSnapshot->transform; } + inline const UvMapper& getMapper(const Texture* texture) { + return texture && texture->uvMapper ? *texture->uvMapper : mUvMapper; + } + + /** + * Returns a texture object for the specified bitmap. The texture can + * come from the texture cache or an atlas. If this method returns + * NULL, the texture could not be found and/or allocated. + */ + Texture* getTexture(SkBitmap* bitmap); + // Dimensions of the drawing surface int mWidth, mHeight; @@ -1010,6 +1040,9 @@ private: // Used to draw textured quads TextureVertex mMeshVertices[4]; + // Default UV mapper + const UvMapper mUvMapper; + // shader, filters, and shadow DrawModifiers mDrawModifiers; SkPaint mFilteredPaint; @@ -1050,6 +1083,11 @@ private: // No-ops start/endTiling when set bool mSuppressTiling; + // If true, this renderer will setup drawing to emulate + // an increment stencil buffer in the color buffer + bool mCountOverdraw; + float mOverdraw; + // Optional name of the renderer String8 mName; diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp index 45c619e..6b0734a 100644 --- a/libs/hwui/Patch.cpp +++ b/libs/hwui/Patch.cpp @@ -20,9 +20,10 @@ #include <utils/Log.h> -#include "Patch.h" #include "Caches.h" +#include "Patch.h" #include "Properties.h" +#include "UvMapper.h" namespace android { namespace uirenderer { @@ -31,90 +32,61 @@ namespace uirenderer { // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -Patch::Patch(const uint32_t xCount, const uint32_t yCount, const int8_t emptyQuads): - mXCount(xCount), mYCount(yCount), mEmptyQuads(emptyQuads) { - // Initialized with the maximum number of vertices we will need - // 2 triangles per patch, 3 vertices per triangle - uint32_t maxVertices = ((xCount + 1) * (yCount + 1) - emptyQuads) * 2 * 3; - mVertices = new TextureVertex[maxVertices]; - mAllocatedVerticesCount = 0; - - verticesCount = 0; - hasEmptyQuads = emptyQuads > 0; - - mColorKey = 0; - mXDivs = new int32_t[mXCount]; - mYDivs = new int32_t[mYCount]; - - PATCH_LOGD(" patch: xCount = %d, yCount = %d, emptyQuads = %d, max vertices = %d", - xCount, yCount, emptyQuads, maxVertices); - - glGenBuffers(1, &meshBuffer); +Patch::Patch(): verticesCount(0), indexCount(0), hasEmptyQuads(false) { } Patch::~Patch() { - delete[] mVertices; - delete[] mXDivs; - delete[] mYDivs; - glDeleteBuffers(1, &meshBuffer); } /////////////////////////////////////////////////////////////////////////////// -// Patch management +// Vertices management /////////////////////////////////////////////////////////////////////////////// -void Patch::copy(const int32_t* xDivs, const int32_t* yDivs) { - memcpy(mXDivs, xDivs, mXCount * sizeof(int32_t)); - memcpy(mYDivs, yDivs, mYCount * sizeof(int32_t)); +uint32_t Patch::getSize() const { + return verticesCount * sizeof(TextureVertex); } -void Patch::updateColorKey(const uint32_t colorKey) { - mColorKey = colorKey; +TextureVertex* Patch::createMesh(const float bitmapWidth, const float bitmapHeight, + float left, float top, float right, float bottom, const Res_png_9patch* patch) { + UvMapper mapper; + return createMesh(bitmapWidth, bitmapHeight, left, top, right, bottom, mapper, patch); } -bool Patch::matches(const int32_t* xDivs, const int32_t* yDivs, - const uint32_t colorKey, const int8_t emptyQuads) { +TextureVertex* Patch::createMesh(const float bitmapWidth, const float bitmapHeight, + float left, float top, float right, float bottom, + const UvMapper& mapper, const Res_png_9patch* patch) { - bool matches = true; + const uint32_t* colors = &patch->colors[0]; + const int8_t numColors = patch->numColors; - if (mEmptyQuads != emptyQuads) { - mEmptyQuads = emptyQuads; - hasEmptyQuads = emptyQuads > 0; - matches = false; - } - - if (mColorKey != colorKey) { - updateColorKey(colorKey); - matches = false; - } - - if (memcmp(mXDivs, xDivs, mXCount * sizeof(int32_t))) { - memcpy(mXDivs, xDivs, mXCount * sizeof(int32_t)); - matches = false; + mColorKey = 0; + int8_t emptyQuads = 0; + + if (uint8_t(numColors) < sizeof(uint32_t) * 4) { + for (int8_t i = 0; i < numColors; i++) { + if (colors[i] == 0x0) { + emptyQuads++; + mColorKey |= 0x1 << i; + } + } } - if (memcmp(mYDivs, yDivs, mYCount * sizeof(int32_t))) { - memcpy(mYDivs, yDivs, mYCount * sizeof(int32_t)); - matches = false; - } + hasEmptyQuads = emptyQuads > 0; - return matches; -} + uint32_t xCount = patch->numXDivs; + uint32_t yCount = patch->numYDivs; -/////////////////////////////////////////////////////////////////////////////// -// Vertices management -/////////////////////////////////////////////////////////////////////////////// + uint32_t maxVertices = ((xCount + 1) * (yCount + 1) - emptyQuads) * 4; + if (maxVertices == 0) return NULL; -void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, - float left, float top, float right, float bottom) { - if (hasEmptyQuads) quads.clear(); + TextureVertex* vertices = new TextureVertex[maxVertices]; + TextureVertex* vertex = vertices; - // Reset the vertices count here, we will count exactly how many - // vertices we actually need when generating the quads - verticesCount = 0; + const int32_t* xDivs = patch->xDivs; + const int32_t* yDivs = patch->yDivs; - const uint32_t xStretchCount = (mXCount + 1) >> 1; - const uint32_t yStretchCount = (mYCount + 1) >> 1; + const uint32_t xStretchCount = (xCount + 1) >> 1; + const uint32_t yStretchCount = (yCount + 1) >> 1; float stretchX = 0.0f; float stretchY = 0.0f; @@ -124,8 +96,8 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, if (xStretchCount > 0) { uint32_t stretchSize = 0; - for (uint32_t i = 1; i < mXCount; i += 2) { - stretchSize += mXDivs[i] - mXDivs[i - 1]; + for (uint32_t i = 1; i < xCount; i += 2) { + stretchSize += xDivs[i] - xDivs[i - 1]; } const float xStretchTex = stretchSize; const float fixed = bitmapWidth - stretchSize; @@ -136,8 +108,8 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, if (yStretchCount > 0) { uint32_t stretchSize = 0; - for (uint32_t i = 1; i < mYCount; i += 2) { - stretchSize += mYDivs[i] - mYDivs[i - 1]; + for (uint32_t i = 1; i < yCount; i += 2) { + stretchSize += yDivs[i] - yDivs[i - 1]; } const float yStretchTex = stretchSize; const float fixed = bitmapHeight - stretchSize; @@ -146,7 +118,6 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, rescaleY = fixed == 0.0f ? 0.0f : fminf(fmaxf(bottom - top, 0.0f) / fixed, 1.0f); } - TextureVertex* vertex = mVertices; uint32_t quadCount = 0; float previousStepY = 0.0f; @@ -155,8 +126,10 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, float y2 = 0.0f; float v1 = 0.0f; - for (uint32_t i = 0; i < mYCount; i++) { - float stepY = mYDivs[i]; + mUvMapper = mapper; + + for (uint32_t i = 0; i < yCount; i++) { + float stepY = yDivs[i]; const float segment = stepY - previousStepY; if (i & 1) { @@ -170,15 +143,8 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, v1 += vOffset / bitmapHeight; if (stepY > 0.0f) { -#if DEBUG_EXPLODE_PATCHES - y1 += i * EXPLODE_GAP; - y2 += i * EXPLODE_GAP; -#endif - generateRow(vertex, y1, y2, v1, v2, stretchX, rescaleX, right - left, - bitmapWidth, quadCount); -#if DEBUG_EXPLODE_PATCHES - y2 -= i * EXPLODE_GAP; -#endif + generateRow(xDivs, xCount, vertex, y1, y2, v1, v2, stretchX, rescaleX, + right - left, bitmapWidth, quadCount); } y1 = y2; @@ -189,33 +155,16 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, if (previousStepY != bitmapHeight) { y2 = bottom - top; -#if DEBUG_EXPLODE_PATCHES - y1 += mYCount * EXPLODE_GAP; - y2 += mYCount * EXPLODE_GAP; -#endif - generateRow(vertex, y1, y2, v1, 1.0f, stretchX, rescaleX, right - left, - bitmapWidth, quadCount); - } - - if (verticesCount > 0) { - Caches& caches = Caches::getInstance(); - caches.bindMeshBuffer(meshBuffer); - if (mAllocatedVerticesCount < verticesCount) { - glBufferData(GL_ARRAY_BUFFER, sizeof(TextureVertex) * verticesCount, - mVertices, GL_DYNAMIC_DRAW); - mAllocatedVerticesCount = verticesCount; - } else { - glBufferSubData(GL_ARRAY_BUFFER, 0, - sizeof(TextureVertex) * verticesCount, mVertices); - } - caches.resetVertexPointers(); + generateRow(xDivs, xCount, vertex, y1, y2, v1, 1.0f, stretchX, rescaleX, + right - left, bitmapWidth, quadCount); } - PATCH_LOGD(" patch: new vertices count = %d", verticesCount); + return vertices; } -void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, float v2, - float stretchX, float rescaleX, float width, float bitmapWidth, uint32_t& quadCount) { +void Patch::generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex, + float y1, float y2, float v1, float v2, float stretchX, float rescaleX, + float width, float bitmapWidth, uint32_t& quadCount) { float previousStepX = 0.0f; float x1 = 0.0f; @@ -223,8 +172,8 @@ void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, fl float u1 = 0.0f; // Generate the row quad by quad - for (uint32_t i = 0; i < mXCount; i++) { - float stepX = mXDivs[i]; + for (uint32_t i = 0; i < xCount; i++) { + float stepX = xDivs[i]; const float segment = stepX - previousStepX; if (i & 1) { @@ -238,14 +187,7 @@ void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, fl u1 += uOffset / bitmapWidth; if (stepX > 0.0f) { -#if DEBUG_EXPLODE_PATCHES - x1 += i * EXPLODE_GAP; - x2 += i * EXPLODE_GAP; -#endif generateQuad(vertex, x1, y1, x2, y2, u1, v1, u2, v2, quadCount); -#if DEBUG_EXPLODE_PATCHES - x2 -= i * EXPLODE_GAP; -#endif } x1 = x2; @@ -256,10 +198,6 @@ void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, fl if (previousStepX != bitmapWidth) { x2 = width; -#if DEBUG_EXPLODE_PATCHES - x1 += mXCount * EXPLODE_GAP; - x2 += mXCount * EXPLODE_GAP; -#endif generateQuad(vertex, x1, y1, x2, y2, u1, v1, 1.0f, v2, quadCount); } } @@ -290,18 +228,15 @@ void Patch::generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, f quads.add(bounds); } - // Left triangle + mUvMapper.map(u1, v1, u2, v2); + TextureVertex::set(vertex++, x1, y1, u1, v1); TextureVertex::set(vertex++, x2, y1, u2, v1); TextureVertex::set(vertex++, x1, y2, u1, v2); - - // Right triangle - TextureVertex::set(vertex++, x1, y2, u1, v2); - TextureVertex::set(vertex++, x2, y1, u2, v1); TextureVertex::set(vertex++, x2, y2, u2, v2); - // A quad is made of 2 triangles, 6 vertices - verticesCount += 6; + verticesCount += 4; + indexCount += 6; #if DEBUG_PATCHES_VERTICES PATCH_LOGD(" quad %d", oldQuadCount); diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h index ee7bf70..448cf60 100644 --- a/libs/hwui/Patch.h +++ b/libs/hwui/Patch.h @@ -23,62 +23,52 @@ #include <utils/Vector.h> +#include <androidfw/ResourceTypes.h> + #include "Rect.h" +#include "UvMapper.h" #include "Vertex.h" namespace android { namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -#define EXPLODE_GAP 4 - -/////////////////////////////////////////////////////////////////////////////// // 9-patch structures /////////////////////////////////////////////////////////////////////////////// -/** - * An OpenGL patch. This contains an array of vertices and an array of - * indices to render the vertices. - */ struct Patch { - Patch(const uint32_t xCount, const uint32_t yCount, const int8_t emptyQuads); + Patch(); ~Patch(); - void updateVertices(const float bitmapWidth, const float bitmapHeight, - float left, float top, float right, float bottom); - - 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, const int8_t emptyQuads); + /** + * Returns the size of this patch's mesh in bytes. + */ + uint32_t getSize() const; - GLuint meshBuffer; uint32_t verticesCount; + uint32_t indexCount; bool hasEmptyQuads; Vector<Rect> quads; -private: - TextureVertex* mVertices; - uint32_t mAllocatedVerticesCount; - - int32_t* mXDivs; - int32_t* mYDivs; - uint32_t mColorKey; + GLintptr offset; + GLintptr textureOffset; - uint32_t mXCount; - uint32_t mYCount; - int8_t mEmptyQuads; + TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight, + float left, float top, float right, float bottom, + const Res_png_9patch* patch); + TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight, + float left, float top, float right, float bottom, + const UvMapper& mapper, const Res_png_9patch* patch); - void generateRow(TextureVertex*& vertex, float y1, float y2, - float v1, float v2, float stretchX, float rescaleX, +private: + void generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex, + float y1, float y2, float v1, float v2, float stretchX, float rescaleX, float width, float bitmapWidth, uint32_t& quadCount); - void generateQuad(TextureVertex*& vertex, - float x1, float y1, float x2, float y2, - float u1, float v1, float u2, float v2, - uint32_t& quadCount); + void generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, float y2, + float u1, float v1, float u2, float v2, uint32_t& quadCount); + + uint32_t mColorKey; + UvMapper mUvMapper; }; // struct Patch }; // namespace uirenderer diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp index 62e38d3..c23e991 100644 --- a/libs/hwui/PatchCache.cpp +++ b/libs/hwui/PatchCache.cpp @@ -16,8 +16,10 @@ #define LOG_TAG "OpenGLRenderer" +#include <utils/JenkinsHash.h> #include <utils/Log.h> +#include "Caches.h" #include "PatchCache.h" #include "Properties.h" @@ -28,107 +30,123 @@ namespace uirenderer { // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -PatchCache::PatchCache(): mMaxEntries(DEFAULT_PATCH_CACHE_SIZE) { -} - -PatchCache::PatchCache(uint32_t maxEntries): mMaxEntries(maxEntries) { +PatchCache::PatchCache(): + mSize(0), mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity), + mMeshBuffer(0), mGenerationId(0) { + char property[PROPERTY_VALUE_MAX]; + if (property_get(PROPERTY_PATCH_CACHE_SIZE, property, NULL) > 0) { + INIT_LOGD(" Setting patch cache size to %skB", property); + mMaxSize = KB(atoi(property)); + } else { + INIT_LOGD(" Using default patch cache size of %.2fkB", DEFAULT_PATCH_CACHE_SIZE); + mMaxSize = KB(DEFAULT_PATCH_CACHE_SIZE); + } } PatchCache::~PatchCache() { clear(); } -/////////////////////////////////////////////////////////////////////////////// -// 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; +void PatchCache::init(Caches& caches) { + bool created = false; + if (!mMeshBuffer) { + glGenBuffers(1, &mMeshBuffer); + created = true; + } - if (lhs.pixelWidth < rhs.pixelWidth) return -1; - if (lhs.pixelWidth > rhs.pixelWidth) return +1; + caches.bindMeshBuffer(mMeshBuffer); + caches.resetVertexPointers(); - if (lhs.pixelHeight < rhs.pixelHeight) return -1; - if (lhs.pixelHeight > rhs.pixelHeight) return +1; + if (created) { + createVertexBuffer(); + } +} - deltaInt = lhs.xCount - rhs.xCount; - if (deltaInt != 0) return deltaInt; +/////////////////////////////////////////////////////////////////////////////// +// Caching +/////////////////////////////////////////////////////////////////////////////// - deltaInt = lhs.yCount - rhs.yCount; - if (deltaInt != 0) return deltaInt; +hash_t PatchCache::PatchDescription::hash() const { + uint32_t hash = JenkinsHashMix(0, android::hash_type(mPatch)); + hash = JenkinsHashMix(hash, mBitmapWidth); + hash = JenkinsHashMix(hash, mBitmapHeight); + hash = JenkinsHashMix(hash, mPixelWidth); + hash = JenkinsHashMix(hash, mPixelHeight); + return JenkinsHashWhiten(hash); +} - deltaInt = lhs.emptyCount - rhs.emptyCount; - if (deltaInt != 0) return deltaInt; +int PatchCache::PatchDescription::compare(const PatchCache::PatchDescription& lhs, + const PatchCache::PatchDescription& rhs) { + return memcmp(&lhs, &rhs, sizeof(PatchDescription)); +} - deltaInt = lhs.colorKey - rhs.colorKey; - if (deltaInt != 0) return deltaInt; +void PatchCache::clear() { + clearCache(); - return 0; + if (mMeshBuffer) { + Caches::getInstance().unbindMeshBuffer(); + glDeleteBuffers(1, &mMeshBuffer); + mMeshBuffer = 0; + mSize = 0; + } } -void PatchCache::clear() { - size_t count = mCache.size(); - for (size_t i = 0; i < count; i++) { - delete mCache.valueAt(i); +void PatchCache::clearCache() { + LruCache<PatchDescription, Patch*>::Iterator i(mCache); + while (i.next()) { + delete i.value(); } mCache.clear(); } -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) { +void PatchCache::createVertexBuffer() { + glBufferData(GL_ARRAY_BUFFER, mMaxSize, NULL, GL_DYNAMIC_DRAW); + mSize = 0; + mGenerationId++; +} - int8_t transparentQuads = 0; - uint32_t colorKey = 0; +const Patch* PatchCache::get(const AssetAtlas::Entry* entry, + const uint32_t bitmapWidth, const uint32_t bitmapHeight, + const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) { - if (uint8_t(numColors) < sizeof(uint32_t) * 4) { - for (int8_t i = 0; i < numColors; i++) { - if (colors[i] == 0x0) { - transparentQuads++; - colorKey |= 0x1 << i; - } + const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch); + const Patch* mesh = mCache.get(description); + + if (!mesh) { + Patch* newMesh = new Patch(); + TextureVertex* vertices; + + if (entry) { + vertices = newMesh->createMesh(bitmapWidth, bitmapHeight, + 0.0f, 0.0f, pixelWidth, pixelHeight, entry->uvMapper, patch); + } else { + vertices = newMesh->createMesh(bitmapWidth, bitmapHeight, + 0.0f, 0.0f, pixelWidth, pixelHeight, patch); } - } - // If the 9patch is made of only transparent quads - if (transparentQuads == int8_t((width + 1) * (height + 1))) { - return NULL; - } + if (vertices) { + // This call ensures the VBO exists and that it is bound + init(Caches::getInstance()); - const PatchDescription description(bitmapWidth, bitmapHeight, - pixelWidth, pixelHeight, width, height, transparentQuads, colorKey); + // TODO: Simply remove the oldest items until we have enough room + // This will require to keep a list of free blocks in the VBO + uint32_t size = newMesh->getSize(); + if (mSize + size > mMaxSize) { + clearCache(); + createVertexBuffer(); + } - ssize_t index = mCache.indexOfKey(description); - Patch* mesh = NULL; - if (index >= 0) { - mesh = mCache.valueAt(index); - } + newMesh->offset = (GLintptr) mSize; + newMesh->textureOffset = newMesh->offset + gMeshTextureOffset; + mSize += size; - if (!mesh) { - PATCH_LOGD("New patch mesh " - "xCount=%d yCount=%d, w=%.2f h=%.2f, bw=%.2f bh=%.2f", - width, height, pixelWidth, pixelHeight, bitmapWidth, bitmapHeight); - - mesh = new Patch(width, height, transparentQuads); - mesh->updateColorKey(colorKey); - mesh->copy(xDivs, yDivs); - mesh->updateVertices(bitmapWidth, bitmapHeight, 0.0f, 0.0f, pixelWidth, pixelHeight); - - if (mCache.size() >= mMaxEntries) { - delete mCache.valueAt(mCache.size() - 1); - mCache.removeItemsAt(mCache.size() - 1, 1); + glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices); + + delete[] vertices; } - mCache.add(description, mesh); - } 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); + mCache.put(description, newMesh); + return newMesh; } return mesh; diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h index 0822cba..1829b89 100644 --- a/libs/hwui/PatchCache.h +++ b/libs/hwui/PatchCache.h @@ -17,8 +17,13 @@ #ifndef ANDROID_HWUI_PATCH_CACHE_H #define ANDROID_HWUI_PATCH_CACHE_H -#include <utils/KeyedVector.h> +#include <GLES2/gl2.h> +#include <utils/LruCache.h> + +#include <androidfw/ResourceTypes.h> + +#include "AssetAtlas.h" #include "Debug.h" #include "Patch.h" @@ -40,45 +45,52 @@ namespace uirenderer { // Cache /////////////////////////////////////////////////////////////////////////////// +class Caches; + class PatchCache { public: PatchCache(); - PatchCache(uint32_t maxCapacity); ~PatchCache(); + void init(Caches& caches); - 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); + const Patch* get(const AssetAtlas::Entry* entry, + const uint32_t bitmapWidth, const uint32_t bitmapHeight, + const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch); void clear(); uint32_t getSize() const { - return mCache.size(); + return mSize; } uint32_t getMaxSize() const { - return mMaxEntries; + return mMaxSize; + } + + GLuint getMeshBuffer() const { + return mMeshBuffer; + } + + uint32_t getGenerationId() const { + return mGenerationId; } private: - /** - * Description of a patch. - */ + void clearCache(); + void createVertexBuffer(); + struct PatchDescription { - PatchDescription(): bitmapWidth(0), bitmapHeight(0), pixelWidth(0), pixelHeight(0), - xCount(0), yCount(0), emptyCount(0), colorKey(0) { + PatchDescription(): mPatch(NULL), mBitmapWidth(0), mBitmapHeight(0), + mPixelWidth(0), mPixelHeight(0) { } 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): - bitmapWidth(bitmapWidth), bitmapHeight(bitmapHeight), - pixelWidth(pixelWidth), pixelHeight(pixelHeight), - xCount(xCount), yCount(yCount), - emptyCount(emptyCount), colorKey(colorKey) { + const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch): + mPatch(patch), mBitmapWidth(bitmapWidth), mBitmapHeight(bitmapHeight), + mPixelWidth(pixelWidth), mPixelHeight(pixelHeight) { } + hash_t hash() const; + static int compare(const PatchDescription& lhs, const PatchDescription& rhs); bool operator==(const PatchDescription& other) const { @@ -99,21 +111,27 @@ private: return PatchDescription::compare(lhs, rhs); } + friend inline hash_t hash_type(const PatchDescription& entry) { + return entry.hash(); + } + private: - uint32_t bitmapWidth; - uint32_t bitmapHeight; - float pixelWidth; - float pixelHeight; - uint32_t xCount; - uint32_t yCount; - int8_t emptyCount; - uint32_t colorKey; + const Res_png_9patch* mPatch; + uint32_t mBitmapWidth; + uint32_t mBitmapHeight; + float mPixelWidth; + float mPixelHeight; }; // struct PatchDescription - uint32_t mMaxEntries; - KeyedVector<PatchDescription, Patch*> mCache; + uint32_t mMaxSize; + uint32_t mSize; + + LruCache<PatchDescription, Patch*> mCache; + + GLuint mMeshBuffer; + uint32_t mGenerationId; }; // class PatchCache }; // namespace uirenderer diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index fdb10e2..3ab40da 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -139,7 +139,7 @@ static void drawPath(const SkPath *path, const SkPaint* paint, SkBitmap& bitmap, static PathTexture* createTexture(float left, float top, float offset, uint32_t width, uint32_t height, uint32_t id) { - PathTexture* texture = new PathTexture(); + PathTexture* texture = new PathTexture(Caches::getInstance()); texture->left = left; texture->top = top; texture->offset = offset; @@ -223,7 +223,7 @@ void PathCache::removeTexture(PathTexture* texture) { } if (texture->id) { - glDeleteTextures(1, &texture->id); + Caches::getInstance().deleteTexture(texture->id); } delete texture; } @@ -300,7 +300,7 @@ void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) { glGenTextures(1, &texture->id); - glBindTexture(GL_TEXTURE_2D, texture->id); + Caches::getInstance().bindTexture(texture->id); // Textures are Alpha8 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h index dd1f996..a191f0e 100644 --- a/libs/hwui/PathCache.h +++ b/libs/hwui/PathCache.h @@ -58,7 +58,7 @@ class Caches; * Alpha texture used to represent a path. */ struct PathTexture: public Texture { - PathTexture(): Texture() { + PathTexture(Caches& caches): Texture(caches) { } ~PathTexture() { diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp index 0879b1b..3970913 100644 --- a/libs/hwui/PathTessellator.cpp +++ b/libs/hwui/PathTessellator.cpp @@ -66,11 +66,11 @@ void PathTessellator::expandBoundsForStroke(SkRect& bounds, const SkPaint* paint } } -inline void copyVertex(Vertex* destPtr, const Vertex* srcPtr) { +inline static void copyVertex(Vertex* destPtr, const Vertex* srcPtr) { Vertex::set(destPtr, srcPtr->position[0], srcPtr->position[1]); } -inline void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) { +inline static void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) { AlphaVertex::set(destPtr, srcPtr->position[0], srcPtr->position[1], srcPtr->alpha); } @@ -84,7 +84,7 @@ inline void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) { * * NOTE: assumes angles between normals 90 degrees or less */ -inline vec2 totalOffsetFromNormals(const vec2& normalA, const vec2& normalB) { +inline static vec2 totalOffsetFromNormals(const vec2& normalA, const vec2& normalB) { return (normalA + normalB) / (1 + fabs(normalA.dot(normalB))); } @@ -224,6 +224,20 @@ void getStrokeVerticesFromPerimeter(const PaintInfo& paintInfo, const Vector<Ver DEBUG_DUMP_BUFFER(); } +static inline void storeBeginEnd(const PaintInfo& paintInfo, const Vertex& center, + const vec2& normal, Vertex* buffer, int& currentIndex, bool begin) { + vec2 strokeOffset = normal; + paintInfo.scaleOffsetForStrokeWidth(strokeOffset); + + vec2 referencePoint(center.position[0], center.position[1]); + if (paintInfo.cap == SkPaint::kSquare_Cap) { + referencePoint += vec2(-strokeOffset.y, strokeOffset.x) * (begin ? -1 : 1); + } + + Vertex::set(&buffer[currentIndex++], referencePoint + strokeOffset); + Vertex::set(&buffer[currentIndex++], referencePoint - strokeOffset); +} + /** * Fills a vertexBuffer with non-alpha vertices similar to getStrokeVerticesFromPerimeter, except: * @@ -235,19 +249,17 @@ 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); + const int lastIndex = vertices.size() - 1; 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]); + - (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]); - + - (vertices[lastIndex].position[0] - vertices[lastIndex - 1].position[0]), + vertices[lastIndex].position[1] - vertices[lastIndex - 1].position[1]); const float dTheta = PI / (extra + 1); const float radialScale = 2.0f / (1 + cos(dTheta)); @@ -270,56 +282,45 @@ void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo, 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); + vertices[lastIndex].position[0] + endRadialOffset.x, + vertices[lastIndex].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* last = &(vertices[0]); + const Vertex* current = &(vertices[1]); + vec2 lastNormal(current->position[1] - last->position[1], + last->position[0] - current->position[0]); + lastNormal.normalize(); + + storeBeginEnd(paintInfo, vertices[0], lastNormal, buffer, currentIndex, true); + + 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; - if (i == 0) { - totalOffset = nextNormal; - } else { - totalOffset = totalOffsetFromNormals(lastNormal, nextNormal); - } - paintInfo.scaleOffsetForStrokeWidth(totalOffset); + vec2 strokeOffset = totalOffsetFromNormals(lastNormal, nextNormal); + paintInfo.scaleOffsetForStrokeWidth(strokeOffset); - 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); + vec2 center(current->position[0], current->position[1]); + Vertex::set(&buffer[currentIndex++], center + strokeOffset); + Vertex::set(&buffer[currentIndex++], center - strokeOffset); 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); + storeBeginEnd(paintInfo, vertices[lastIndex], lastNormal, buffer, currentIndex, false); 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) * @@ -389,7 +390,7 @@ void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Ver * For explanation of constants and general methodoloyg, see comments for * getStrokeVerticesFromUnclosedVerticesAA() below. */ -inline void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>& vertices, +inline static 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; @@ -772,11 +773,67 @@ void PathTessellator::tessellatePath(const SkPath &path, const SkPaint* paint, } } +static void expandRectToCoverVertex(SkRect& rect, float x, float y) { + rect.fLeft = fminf(rect.fLeft, x); + rect.fTop = fminf(rect.fTop, y); + rect.fRight = fmaxf(rect.fRight, x); + rect.fBottom = fmaxf(rect.fBottom, y); +} 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]); + expandRectToCoverVertex(rect, vertex.position[0], vertex.position[1]); +} + +template <class TYPE> +static void instanceVertices(VertexBuffer& srcBuffer, VertexBuffer& dstBuffer, + const float* points, int count, SkRect& bounds) { + bounds.set(points[0], points[1], points[0], points[1]); + + int numPoints = count / 2; + int verticesPerPoint = srcBuffer.getVertexCount(); + dstBuffer.alloc<TYPE>(numPoints * verticesPerPoint + (numPoints - 1) * 2); + + for (int i = 0; i < count; i += 2) { + expandRectToCoverVertex(bounds, points[i + 0], points[i + 1]); + dstBuffer.copyInto<TYPE>(srcBuffer, points[i + 0], points[i + 1]); + } + dstBuffer.createDegenerateSeparators<TYPE>(verticesPerPoint); +} + +void PathTessellator::tessellatePoints(const float* points, int count, SkPaint* paint, + const mat4* transform, SkRect& bounds, VertexBuffer& vertexBuffer) { + const PaintInfo paintInfo(paint, transform); + + // determine point shape + SkPath path; + float radius = paintInfo.halfStrokeWidth; + if (radius == 0.0f) radius = 0.25f; + + if (paintInfo.cap == SkPaint::kRound_Cap) { + path.addCircle(0, 0, radius); + } else { + path.addRect(-radius, -radius, radius, radius); + } + + // calculate outline + Vector<Vertex> outlineVertices; + approximatePathOutlineVertices(path, true, + paintInfo.inverseScaleX * paintInfo.inverseScaleX, + paintInfo.inverseScaleY * paintInfo.inverseScaleY, outlineVertices); + + if (!outlineVertices.size()) return; + + // tessellate, then duplicate outline across points + int numPoints = count / 2; + VertexBuffer tempBuffer; + if (!paintInfo.isAA) { + getFillVerticesFromPerimeter(outlineVertices, tempBuffer); + instanceVertices<Vertex>(tempBuffer, vertexBuffer, points, count, bounds); + } else { + getFillVerticesFromPerimeterAA(paintInfo, outlineVertices, tempBuffer); + instanceVertices<AlphaVertex>(tempBuffer, vertexBuffer, points, count, bounds); + } + + expandBoundsForStroke(bounds, paint, true); // force-expand bounds to incorporate stroke } void PathTessellator::tessellateLines(const float* points, int count, SkPaint* paint, diff --git a/libs/hwui/PathTessellator.h b/libs/hwui/PathTessellator.h index 596d49d..85797fc 100644 --- a/libs/hwui/PathTessellator.h +++ b/libs/hwui/PathTessellator.h @@ -30,7 +30,7 @@ class VertexBuffer { public: VertexBuffer(): mBuffer(0), - mSize(0), + mVertexCount(0), mCleanupMethod(NULL) {} @@ -44,30 +44,42 @@ public: multiple regions within a single VertexBuffer, such as with PathTessellator::tesselateLines() */ template <class TYPE> - TYPE* alloc(int size) { - if (mSize) { + TYPE* alloc(int vertexCount) { + if (mVertexCount) { 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; + mReallocBuffer = reallocBuffer + vertexCount; return reallocBuffer; } - mSize = size; - mReallocBuffer = mBuffer = (void*)new TYPE[size]; + mVertexCount = vertexCount; + mReallocBuffer = mBuffer = (void*)new TYPE[vertexCount]; mCleanupMethod = &(cleanup<TYPE>); return (TYPE*)mBuffer; } - void* getBuffer() const { return mBuffer; } - unsigned int getSize() const { return mSize; } + template <class TYPE> + void copyInto(const VertexBuffer& srcBuffer, float xOffset, float yOffset) { + int verticesToCopy = srcBuffer.getVertexCount(); + + TYPE* dst = alloc<TYPE>(verticesToCopy); + TYPE* src = (TYPE*)srcBuffer.getBuffer(); + + for (int i = 0; i < verticesToCopy; i++) { + TYPE::copyWithOffset(&dst[i], src[i], xOffset, yOffset); + } + } + + void* getBuffer() const { return mBuffer; } // shouldn't be const, since not a const ptr? + unsigned int getVertexCount() const { return mVertexCount; } template <class TYPE> void createDegenerateSeparators(int allocSize) { - TYPE* end = (TYPE*)mBuffer + mSize; + TYPE* end = (TYPE*)mBuffer + mVertexCount; for (TYPE* degen = (TYPE*)mBuffer + allocSize; degen < end; degen += 2 + allocSize) { memcpy(degen, degen - 1, sizeof(TYPE)); memcpy(degen + 1, degen + 2, sizeof(TYPE)); @@ -81,7 +93,7 @@ private: } void* mBuffer; - unsigned int mSize; + unsigned int mVertexCount; void* mReallocBuffer; // used for multi-allocation @@ -95,6 +107,9 @@ public: static void tessellatePath(const SkPath& path, const SkPaint* paint, const mat4 *transform, VertexBuffer& vertexBuffer); + static void tessellatePoints(const float* points, int count, SkPaint* paint, + const mat4* transform, SkRect& bounds, VertexBuffer& vertexBuffer); + static void tessellateLines(const float* points, int count, SkPaint* paint, const mat4* transform, SkRect& bounds, VertexBuffer& vertexBuffer); diff --git a/libs/hwui/PixelBuffer.cpp b/libs/hwui/PixelBuffer.cpp index 8280370..29f8756 100644 --- a/libs/hwui/PixelBuffer.cpp +++ b/libs/hwui/PixelBuffer.cpp @@ -147,14 +147,8 @@ void GpuPixelBuffer::upload(uint32_t x, uint32_t y, uint32_t width, uint32_t hei /////////////////////////////////////////////////////////////////////////////// PixelBuffer* PixelBuffer::create(GLenum format, uint32_t width, uint32_t height, BufferType type) { - bool gpuBuffer = type == kBufferType_Auto && Extensions::getInstance().getMajorGlVersion() >= 3; - if (gpuBuffer) { - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_ENABLE_GPU_PIXEL_BUFFERS, property, "false") > 0) { - if (!strcmp(property, "true")) { - return new GpuPixelBuffer(format, width, height); - } - } + if (type == kBufferType_Auto && Caches::getInstance().gpuPixelBuffersEnabled) { + return new GpuPixelBuffer(format, width, height); } return new CpuPixelBuffer(format, width, height); } diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp index 14a2376..9e4670e 100644 --- a/libs/hwui/Program.cpp +++ b/libs/hwui/Program.cpp @@ -15,6 +15,9 @@ */ #define LOG_TAG "OpenGLRenderer" +#define ATRACE_TAG ATRACE_TAG_VIEW + +#include <utils/Trace.h> #include "Program.h" @@ -25,7 +28,6 @@ namespace uirenderer { // Base program /////////////////////////////////////////////////////////////////////////////// -// TODO: Program instance should be created from a factory method Program::Program(const ProgramDescription& description, const char* vertex, const char* fragment) { mInitialized = false; mHasColorUniform = false; @@ -50,7 +52,9 @@ Program::Program(const ProgramDescription& description, const char* vertex, cons texCoords = -1; } + ATRACE_BEGIN("linkProgram"); glLinkProgram(mProgramId); + ATRACE_END(); GLint status; glGetProgramiv(mProgramId, GL_LINK_STATUS, &status); @@ -87,6 +91,9 @@ Program::Program(const ProgramDescription& description, const char* vertex, cons Program::~Program() { if (mInitialized) { + // This would ideally happen after linking the program + // but Tegra drivers, especially when perfhud is enabled, + // sometimes crash if we do so glDetachShader(mProgramId, mVertexShader); glDetachShader(mProgramId, mFragmentShader); @@ -132,6 +139,8 @@ int Program::getUniform(const char* name) { } GLuint Program::buildShader(const char* source, GLenum type) { + ATRACE_CALL(); + GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &source, 0); glCompileShader(shader); @@ -153,20 +162,24 @@ GLuint Program::buildShader(const char* source, GLenum type) { void Program::set(const mat4& projectionMatrix, const mat4& modelViewMatrix, const mat4& transformMatrix, bool offset) { - mat4 p(projectionMatrix); - if (offset) { - // offset screenspace xy by an amount that compensates for typical precision - // issues in GPU hardware that tends to paint hor/vert lines in pixels shifted - // 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(.065, .065, 0); + if (projectionMatrix != mProjection) { + if (CC_LIKELY(!offset)) { + glUniformMatrix4fv(projection, 1, GL_FALSE, &projectionMatrix.data[0]); + } else { + mat4 p(projectionMatrix); + // offset screenspace xy by an amount that compensates for typical precision + // issues in GPU hardware that tends to paint hor/vert lines in pixels shifted + // 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(.065, .065); + glUniformMatrix4fv(projection, 1, GL_FALSE, &p.data[0]); + } + mProjection = projectionMatrix; } mat4 t(transformMatrix); t.multiply(modelViewMatrix); - - glUniformMatrix4fv(projection, 1, GL_FALSE, &p.data[0]); glUniformMatrix4fv(transform, 1, GL_FALSE, &t.data[0]); } diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h index e8b6d47..4f94afc 100644 --- a/libs/hwui/Program.h +++ b/libs/hwui/Program.h @@ -68,23 +68,22 @@ namespace uirenderer { #define PROGRAM_BITMAP_WRAPS_SHIFT 9 #define PROGRAM_BITMAP_WRAPT_SHIFT 11 -#define PROGRAM_GRADIENT_TYPE_SHIFT 33 +#define PROGRAM_GRADIENT_TYPE_SHIFT 33 // 2 bits for gradient type #define PROGRAM_MODULATE_SHIFT 35 -#define PROGRAM_IS_POINT_SHIFT 36 +#define PROGRAM_HAS_AA_SHIFT 36 -#define PROGRAM_HAS_AA_SHIFT 37 +#define PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT 37 +#define PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT 38 -#define PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT 38 -#define PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT 39 +#define PROGRAM_HAS_GAMMA_CORRECTION 39 -#define PROGRAM_HAS_GAMMA_CORRECTION 40 +#define PROGRAM_IS_SIMPLE_GRADIENT 40 -#define PROGRAM_IS_SIMPLE_GRADIENT 41 +#define PROGRAM_HAS_COLORS 41 -#define PROGRAM_HAS_COLORS 42 - -#define PROGRAM_HAS_DEBUG_HIGHLIGHT 43 +#define PROGRAM_HAS_DEBUG_HIGHLIGHT 42 +#define PROGRAM_EMULATE_STENCIL 43 /////////////////////////////////////////////////////////////////////////////// // Types @@ -156,13 +155,11 @@ struct ProgramDescription { SkXfermode::Mode framebufferMode; bool swapSrcDst; - bool isPoint; - float pointSize; - bool hasGammaCorrection; float gamma; bool hasDebugHighlight; + bool emulateStencil; /** * Resets this description. All fields are reset back to the default @@ -199,9 +196,6 @@ struct ProgramDescription { framebufferMode = SkXfermode::kClear_Mode; swapSrcDst = false; - isPoint = false; - pointSize = 0.0f; - hasGammaCorrection = false; gamma = 2.2f; @@ -267,7 +261,6 @@ struct ProgramDescription { key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT; if (swapSrcDst) key |= PROGRAM_KEY_SWAP_SRC_DST; if (modulate) key |= programid(0x1) << PROGRAM_MODULATE_SHIFT; - if (isPoint) key |= programid(0x1) << PROGRAM_IS_POINT_SHIFT; if (isAA) key |= programid(0x1) << PROGRAM_HAS_AA_SHIFT; if (hasExternalTexture) key |= programid(0x1) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT; if (hasTextureTransform) key |= programid(0x1) << PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT; @@ -275,6 +268,7 @@ struct ProgramDescription { if (isSimpleGradient) key |= programid(0x1) << PROGRAM_IS_SIMPLE_GRADIENT; if (hasColors) key |= programid(0x1) << PROGRAM_HAS_COLORS; if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT; + if (emulateStencil) key |= programid(0x1) << PROGRAM_EMULATE_STENCIL; return key; } @@ -430,10 +424,13 @@ private: bool mUse; bool mInitialized; + // Uniforms caching bool mHasColorUniform; int mColorUniform; bool mHasSampler; + + mat4 mProjection; }; // class Program }; // namespace uirenderer diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index 8eb85e5..a5ce6f6 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -53,8 +53,6 @@ const char* gVS_Header_Uniforms_TextureTransform = const char* gVS_Header_Uniforms = "uniform mat4 projection;\n" \ "uniform mat4 transform;\n"; -const char* gVS_Header_Uniforms_IsPoint = - "uniform mediump float pointSize;\n"; const char* gVS_Header_Uniforms_HasGradient = "uniform mat4 screenSpace;\n"; const char* gVS_Header_Uniforms_HasBitmap = @@ -68,8 +66,6 @@ const char* gVS_Header_Varyings_IsAAVertexShape = "varying float alpha;\n"; const char* gVS_Header_Varyings_HasBitmap = "varying highp vec2 outBitmapTexCoords;\n"; -const char* gVS_Header_Varyings_PointHasBitmap = - "varying highp vec2 outPointBitmapTexCoords;\n"; const char* gVS_Header_Varyings_HasGradient[6] = { // Linear "varying highp vec2 linear;\n" @@ -118,12 +114,8 @@ const char* gVS_Main_OutGradient[6] = { }; const char* gVS_Main_OutBitmapTexCoords = " outBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n"; -const char* gVS_Main_OutPointBitmapTexCoords = - " outPointBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n"; const char* gVS_Main_Position = " gl_Position = projection * transform * position;\n"; -const char* gVS_Main_PointSize = - " gl_PointSize = pointSize;\n"; const char* gVS_Main_AAVertexShape = " alpha = vtxAlpha;\n"; const char* gVS_Footer = @@ -141,9 +133,6 @@ const char* gFS_Header = "precision mediump float;\n\n"; const char* gFS_Uniforms_Color = "uniform vec4 color;\n"; -const char* gFS_Header_Uniforms_PointHasBitmap = - "uniform vec2 textureDimension;\n" - "uniform float pointSize;\n"; const char* gFS_Uniforms_TextureSampler = "uniform sampler2D baseSampler;\n"; const char* gFS_Uniforms_ExternalTextureSampler = @@ -178,10 +167,6 @@ const char* gFS_Main = "\nvoid main(void) {\n" " lowp vec4 fragColor;\n"; -const char* gFS_Main_PointBitmapTexCoords = - " highp vec2 outBitmapTexCoords = outPointBitmapTexCoords + " - "((gl_PointCoord - vec2(0.5, 0.5)) * textureDimension * vec2(pointSize, pointSize));\n"; - const char* gFS_Main_Dither[2] = { // ES 2.0 "texture2D(ditherSampler, ditherTexCoords).a * " STR(DITHER_KERNEL_SIZE_INV_SQUARE), @@ -342,6 +327,12 @@ const char* gFS_Main_ApplyColorOp[4] = { }; const char* gFS_Main_DebugHighlight = " gl_FragColor.rgb = vec3(0.0, gl_FragColor.a, 0.0);\n"; +const char* gFS_Main_EmulateStencil = + " gl_FragColor.rgba = vec4(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 1.0);\n" + " return;\n" + " /*\n"; +const char* gFS_Footer_EmulateStencil = + " */\n"; const char* gFS_Footer = "}\n\n"; @@ -478,9 +469,6 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description if (description.hasBitmap) { shader.append(gVS_Header_Uniforms_HasBitmap); } - if (description.isPoint) { - shader.append(gVS_Header_Uniforms_IsPoint); - } // Varyings if (description.hasTexture || description.hasExternalTexture) { shader.append(gVS_Header_Varyings_HasTexture); @@ -495,9 +483,7 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description shader.append(gVS_Header_Varyings_HasGradient[gradientIndex(description)]); } if (description.hasBitmap) { - shader.append(description.isPoint ? - gVS_Header_Varyings_PointHasBitmap : - gVS_Header_Varyings_HasBitmap); + shader.append(gVS_Header_Varyings_HasBitmap); } // Begin the shader @@ -514,12 +500,7 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description shader.append(gVS_Main_OutColors); } if (description.hasBitmap) { - shader.append(description.isPoint ? - gVS_Main_OutPointBitmapTexCoords : - gVS_Main_OutBitmapTexCoords); - } - if (description.isPoint) { - shader.append(gVS_Main_PointSize); + shader.append(gVS_Main_OutBitmapTexCoords); } // Output transformed position shader.append(gVS_Main_Position); @@ -570,9 +551,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti shader.append(gVS_Header_Varyings_HasGradient[gradientIndex(description)]); } if (description.hasBitmap) { - shader.append(description.isPoint ? - gVS_Header_Varyings_PointHasBitmap : - gVS_Header_Varyings_HasBitmap); + shader.append(gVS_Header_Varyings_HasBitmap); } // Uniforms @@ -593,9 +572,6 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti shader.appendFormat(gFS_Uniforms_GradientSampler[description.isSimpleGradient], gFS_Uniforms_Dither); } - if (description.hasBitmap && description.isPoint) { - shader.append(gFS_Header_Uniforms_PointHasBitmap); - } if (description.hasGammaCorrection) { shader.append(gFS_Uniforms_Gamma); } @@ -603,7 +579,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti // Optimization for common cases if (!description.isAA && !blendFramebuffer && !description.hasColors && description.colorOp == ProgramDescription::kColorNone && - !description.isPoint && !description.hasDebugHighlight) { + !description.hasDebugHighlight && !description.emulateStencil) { bool fast = false; const bool noShader = !description.hasGradient && !description.hasBitmap; @@ -683,6 +659,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti // Begin the shader shader.append(gFS_Main); { + if (description.emulateStencil) { + shader.append(gFS_Main_EmulateStencil); + } // Stores the result in fragColor directly if (description.hasTexture || description.hasExternalTexture) { if (description.hasAlpha8Texture) { @@ -703,9 +682,6 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti shader.appendFormat(gFS_Main_AddDitherToGradient, gFS_Main_Dither[mHasES3]); } if (description.hasBitmap) { - if (description.isPoint) { - shader.append(gFS_Main_PointBitmapTexCoords); - } if (!description.isBitmapNpot) { shader.append(gFS_Main_FetchBitmap); } else { @@ -757,6 +733,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti shader.append(gFS_Main_DebugHighlight); } } + if (description.emulateStencil) { + shader.append(gFS_Footer_EmulateStencil); + } // End the shader shader.append(gFS_Footer); diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 6eea00c..dbbb956 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -71,9 +71,9 @@ enum DebugLevel { /** * Used to enable/disable overdraw debugging. The accepted values are - * "true" and "false". The default value is "false". + * "show", "count" and "false". The default value is "false". */ -#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.show_overdraw" +#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.overdraw" /** * Used to enable/disable non-rectangular clipping debugging. @@ -123,9 +123,9 @@ enum DebugLevel { /** * Indicates whether PBOs can be used to back pixel buffers. - * Accepted values are "true" and "false". + * Accepted values are "true" and "false". Default is true. */ -#define PROPERTY_ENABLE_GPU_PIXEL_BUFFERS "hwui.use_gpu_pixel_buffers" +#define PROPERTY_ENABLE_GPU_PIXEL_BUFFERS "ro.hwui.use_gpu_pixel_buffers" // These properties are defined in mega-bytes #define PROPERTY_TEXTURE_CACHE_SIZE "ro.hwui.texture_cache_size" @@ -133,6 +133,7 @@ enum DebugLevel { #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_PATCH_CACHE_SIZE "ro.hwui.patch_cache_size" #define PROPERTY_DROP_SHADOW_CACHE_SIZE "ro.hwui.drop_shadow_cache_size" #define PROPERTY_FBO_CACHE_SIZE "ro.hwui.fbo_cache_size" @@ -178,7 +179,7 @@ enum DebugLevel { #define DEFAULT_LAYER_CACHE_SIZE 16.0f #define DEFAULT_RENDER_BUFFER_CACHE_SIZE 2.0f #define DEFAULT_PATH_CACHE_SIZE 10.0f -#define DEFAULT_PATCH_CACHE_SIZE 512 +#define DEFAULT_PATCH_CACHE_SIZE 128 // in kB #define DEFAULT_GRADIENT_CACHE_SIZE 0.5f #define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f #define DEFAULT_FBO_CACHE_SIZE 16 @@ -195,6 +196,8 @@ enum DebugLevel { // Converts a number of mega-bytes into bytes #define MB(s) s * 1024 * 1024 +// Converts a number of kilo-bytes into bytes +#define KB(s) s * 1024 static DebugLevel readDebugLevel() { char property[PROPERTY_VALUE_MAX]; diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index f50ac3c..689fe6c0 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -125,11 +125,11 @@ public: return intersect(r.left, r.top, r.right, r.bottom); } - inline bool contains(float l, float t, float r, float b) { + inline bool contains(float l, float t, float r, float b) const { return l >= left && t >= top && r <= right && b <= bottom; } - inline bool contains(const Rect& r) { + inline bool contains(const Rect& r) const { return contains(r.left, r.top, r.right, r.bottom); } diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index c38eedb..797ed10 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -69,9 +69,13 @@ void SkiaShader::copyFrom(const SkiaShader& shader) { mGenerationId = shader.mGenerationId; } +SkiaShader::SkiaShader(): mCaches(NULL) { +} + SkiaShader::SkiaShader(Type type, SkShader* key, SkShader::TileMode tileX, SkShader::TileMode tileY, SkMatrix* matrix, bool blend): - mType(type), mKey(key), mTileX(tileX), mTileY(tileY), mBlend(blend) { + mType(type), mKey(key), mTileX(tileX), mTileY(tileY), mBlend(blend), + mCaches(NULL) { setMatrix(matrix); mGenerationId = 0; } @@ -87,7 +91,7 @@ void SkiaShader::setupProgram(Program* program, const mat4& modelView, const Sna } void SkiaShader::bindTexture(Texture* texture, GLenum wrapS, GLenum wrapT) { - glBindTexture(GL_TEXTURE_2D, texture->id); + mCaches->bindTexture(texture->id); texture->setWrapST(wrapS, wrapT); } @@ -114,7 +118,7 @@ SkiaShader* SkiaBitmapShader::copy() { } void SkiaBitmapShader::describe(ProgramDescription& description, const Extensions& extensions) { - Texture* texture = mTextureCache->get(mBitmap); + Texture* texture = mCaches->textureCache.get(mBitmap); if (!texture) return; mTexture = texture; @@ -229,7 +233,7 @@ void SkiaLinearGradientShader::setupProgram(Program* program, const mat4& modelV GLuint textureSlot = (*textureUnit)++; Caches::getInstance().activeTexture(textureSlot); - Texture* texture = mGradientCache->get(mColors, mPositions, mCount); + Texture* texture = mCaches->gradientCache.get(mColors, mPositions, mCount); // Uniforms bindTexture(texture, gTileModes[mTileX], gTileModes[mTileY]); @@ -349,7 +353,7 @@ void SkiaSweepGradientShader::setupProgram(Program* program, const mat4& modelVi GLuint textureSlot = (*textureUnit)++; Caches::getInstance().activeTexture(textureSlot); - Texture* texture = mGradientCache->get(mColors, mPositions, mCount); + Texture* texture = mCaches->gradientCache.get(mColors, mPositions, mCount); // Uniforms bindTexture(texture, gTileModes[mTileX], gTileModes[mTileY]); @@ -359,7 +363,7 @@ void SkiaSweepGradientShader::setupProgram(Program* program, const mat4& modelVi bindUniformColor(program->getUniform("endColor"), mColors[1]); } - Caches::getInstance().dither.setupProgram(program, textureUnit); + mCaches->dither.setupProgram(program, textureUnit); mat4 screenSpace; computeScreenSpaceMatrix(screenSpace, modelView); @@ -394,12 +398,6 @@ SkiaShader* SkiaComposeShader::copy() { return copy; } -void SkiaComposeShader::set(TextureCache* textureCache, GradientCache* gradientCache) { - SkiaShader::set(textureCache, gradientCache); - mFirst->set(textureCache, gradientCache); - mSecond->set(textureCache, gradientCache); -} - void SkiaComposeShader::describe(ProgramDescription& description, const Extensions& extensions) { mFirst->describe(description, extensions); mSecond->describe(description, extensions); diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h index bc12b0d..a63431c 100644 --- a/libs/hwui/SkiaShader.h +++ b/libs/hwui/SkiaShader.h @@ -33,6 +33,8 @@ namespace android { namespace uirenderer { +class Caches; + /////////////////////////////////////////////////////////////////////////////// // Base shader /////////////////////////////////////////////////////////////////////////////// @@ -77,9 +79,8 @@ struct SkiaShader { return mType; } - virtual void set(TextureCache* textureCache, GradientCache* gradientCache) { - mTextureCache = textureCache; - mGradientCache = gradientCache; + virtual void setCaches(Caches& caches) { + mCaches = &caches; } uint32_t getGenerationId() { @@ -103,8 +104,7 @@ struct SkiaShader { void computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView); protected: - SkiaShader() { - } + SkiaShader(); /** * The appropriate texture unit must have been activated prior to invoking @@ -118,8 +118,7 @@ protected: SkShader::TileMode mTileY; bool mBlend; - TextureCache* mTextureCache; - GradientCache* mGradientCache; + Caches* mCaches; mat4 mUnitMatrix; mat4 mShaderMatrix; @@ -229,7 +228,11 @@ struct SkiaComposeShader: public SkiaShader { ~SkiaComposeShader(); SkiaShader* copy(); - void set(TextureCache* textureCache, GradientCache* gradientCache); + void setCaches(Caches& caches) { + SkiaShader::setCaches(caches); + mFirst->setCaches(caches); + mSecond->setCaches(caches); + } void describe(ProgramDescription& description, const Extensions& extensions); void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp index 6976eaa..0b2c130 100644 --- a/libs/hwui/TextDropShadowCache.cpp +++ b/libs/hwui/TextDropShadowCache.cpp @@ -18,6 +18,7 @@ #include <utils/JenkinsHash.h> +#include "Caches.h" #include "Debug.h" #include "TextDropShadowCache.h" #include "Properties.h" @@ -154,7 +155,7 @@ void TextDropShadowCache::operator()(ShadowText& text, ShadowTexture*& texture) ALOGD("Shadow texture deleted, size = %d", texture->bitmapSize); } - glDeleteTextures(1, &texture->id); + texture->deleteTexture(); delete texture; } } @@ -182,7 +183,9 @@ ShadowTexture* TextDropShadowCache::get(SkPaint* paint, const char* text, uint32 return NULL; } - texture = new ShadowTexture; + Caches& caches = Caches::getInstance(); + + texture = new ShadowTexture(caches); texture->left = shadow.penX; texture->top = shadow.penY; texture->width = shadow.width; @@ -202,7 +205,7 @@ ShadowTexture* TextDropShadowCache::get(SkPaint* paint, const char* text, uint32 glGenTextures(1, &texture->id); - glBindTexture(GL_TEXTURE_2D, texture->id); + caches.bindTexture(texture->id); // Textures are Alpha8 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h index 0bed72b6..04d7357 100644 --- a/libs/hwui/TextDropShadowCache.h +++ b/libs/hwui/TextDropShadowCache.h @@ -30,6 +30,8 @@ namespace android { namespace uirenderer { +class Caches; + struct ShadowText { ShadowText(): len(0), radius(0.0f), textSize(0.0f), typeface(NULL), flags(0), italicStyle(0.0f), scaleX(0), text(NULL), positions(NULL) { @@ -114,7 +116,7 @@ inline hash_t hash_type(const ShadowText& entry) { * Alpha texture used to represent a shadow. */ struct ShadowTexture: public Texture { - ShadowTexture(): Texture() { + ShadowTexture(Caches& caches): Texture(caches) { } float left; diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp new file mode 100644 index 0000000..7923ce7 --- /dev/null +++ b/libs/hwui/Texture.cpp @@ -0,0 +1,84 @@ +/* + * 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 "Caches.h" +#include "Texture.h" + +namespace android { +namespace uirenderer { + +Texture::Texture(): id(0), generation(0), blend(false), width(0), height(0), + cleanup(false), bitmapSize(0), mipMap(false), uvMapper(NULL), + mWrapS(GL_CLAMP_TO_EDGE), mWrapT(GL_CLAMP_TO_EDGE), + mMinFilter(GL_NEAREST), mMagFilter(GL_NEAREST), + mFirstFilter(true), mFirstWrap(true), mCaches(Caches::getInstance()) { +} + +Texture::Texture(Caches& caches): id(0), generation(0), blend(false), width(0), height(0), + cleanup(false), bitmapSize(0), mipMap(false), uvMapper(NULL), + mWrapS(GL_CLAMP_TO_EDGE), mWrapT(GL_CLAMP_TO_EDGE), + mMinFilter(GL_NEAREST), mMagFilter(GL_NEAREST), + mFirstFilter(true), mFirstWrap(true), mCaches(caches) { +} + +void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force, + GLenum renderTarget) { + + if (mFirstWrap || force || wrapS != mWrapS || wrapT != mWrapT) { + mFirstWrap = false; + + mWrapS = wrapS; + mWrapT = wrapT; + + if (bindTexture) { + mCaches.bindTexture(renderTarget, id); + } + + glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS); + glTexParameteri(renderTarget, GL_TEXTURE_WRAP_T, wrapT); + } +} + +void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool force, + GLenum renderTarget) { + + if (mFirstFilter || force || min != mMinFilter || mag != mMagFilter) { + mFirstFilter = false; + + mMinFilter = min; + mMagFilter = mag; + + if (bindTexture) { + mCaches.bindTexture(renderTarget, id); + } + + if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR; + + glTexParameteri(renderTarget, GL_TEXTURE_MIN_FILTER, min); + glTexParameteri(renderTarget, GL_TEXTURE_MAG_FILTER, mag); + } +} + +void Texture::deleteTexture() const { + mCaches.deleteTexture(id); +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h index 8d88bdc..d48ec59 100644 --- a/libs/hwui/Texture.h +++ b/libs/hwui/Texture.h @@ -22,75 +22,39 @@ namespace android { namespace uirenderer { +class Caches; +class UvMapper; + /** * Represents an OpenGL texture. */ -struct Texture { - Texture() { - cleanup = false; - bitmapSize = 0; - - wrapS = GL_CLAMP_TO_EDGE; - wrapT = GL_CLAMP_TO_EDGE; - - minFilter = GL_NEAREST; - magFilter = GL_NEAREST; - - mipMap = false; - - firstFilter = true; - firstWrap = true; +class Texture { +public: + Texture(); + Texture(Caches& caches); - id = 0; - } + virtual ~Texture() { } - void setWrap(GLenum wrap, bool bindTexture = false, bool force = false, + inline void setWrap(GLenum wrap, bool bindTexture = false, bool force = false, GLenum renderTarget = GL_TEXTURE_2D) { setWrapST(wrap, wrap, bindTexture, force, renderTarget); } - void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false, bool force = false, - GLenum renderTarget = GL_TEXTURE_2D) { - - if (firstWrap || force || wrapS != this->wrapS || wrapT != this->wrapT) { - firstWrap = false; - - this->wrapS = wrapS; - this->wrapT = wrapT; - - if (bindTexture) { - glBindTexture(renderTarget, id); - } - - glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS); - glTexParameteri(renderTarget, GL_TEXTURE_WRAP_T, wrapT); - } - } + virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false, + bool force = false, GLenum renderTarget = GL_TEXTURE_2D); - void setFilter(GLenum filter, bool bindTexture = false, bool force = false, + inline void setFilter(GLenum filter, bool bindTexture = false, bool force = false, GLenum renderTarget = GL_TEXTURE_2D) { setFilterMinMag(filter, filter, bindTexture, force, renderTarget); } - void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false, bool force = false, - GLenum renderTarget = GL_TEXTURE_2D) { + virtual void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false, + bool force = false, GLenum renderTarget = GL_TEXTURE_2D); - if (firstFilter || force || min != minFilter || mag != magFilter) { - firstFilter = false; - - minFilter = min; - magFilter = mag; - - if (bindTexture) { - glBindTexture(renderTarget, id); - } - - if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR; - - glTexParameteri(renderTarget, GL_TEXTURE_MIN_FILTER, min); - glTexParameteri(renderTarget, GL_TEXTURE_MAG_FILTER, mag); - } - } + /** + * Convenience method to call glDeleteTextures() on this texture's id. + */ + void deleteTexture() const; /** * Name of the texture. @@ -125,21 +89,28 @@ struct Texture { */ bool mipMap; + /** + * Optional, pointer to a texture coordinates mapper. + */ + const UvMapper* uvMapper; + private: /** * Last wrap modes set on this texture. Defaults to GL_CLAMP_TO_EDGE. */ - GLenum wrapS; - GLenum wrapT; + GLenum mWrapS; + GLenum mWrapT; /** * Last filters set on this texture. Defaults to GL_NEAREST. */ - GLenum minFilter; - GLenum magFilter; + GLenum mMinFilter; + GLenum mMagFilter; + + bool mFirstFilter; + bool mFirstWrap; - bool firstFilter; - bool firstWrap; + Caches& mCaches; }; // struct Texture class AutoTexture { @@ -147,7 +118,7 @@ public: AutoTexture(const Texture* texture): mTexture(texture) { } ~AutoTexture() { if (mTexture && mTexture->cleanup) { - glDeleteTextures(1, &mTexture->id); + mTexture->deleteTexture(); delete mTexture; } } diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index 2378eb5..a63cac6 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -112,7 +112,7 @@ void TextureCache::operator()(SkBitmap*& bitmap, Texture*& texture) { if (mDebugEnabled) { ALOGD("Texture deleted, size = %d", texture->bitmapSize); } - glDeleteTextures(1, &texture->id); + texture->deleteTexture(); delete texture; } } @@ -139,7 +139,7 @@ Texture* TextureCache::get(SkBitmap* bitmap) { } } - texture = new Texture; + texture = new Texture(); texture->bitmapSize = size; generateTexture(bitmap, texture, false); @@ -162,7 +162,7 @@ Texture* TextureCache::get(SkBitmap* bitmap) { } Texture* TextureCache::getTransient(SkBitmap* bitmap) { - Texture* texture = new Texture; + Texture* texture = new Texture(); texture->bitmapSize = bitmap->rowBytes() * bitmap->height(); texture->cleanup = true; @@ -235,7 +235,7 @@ void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool rege texture->width = bitmap->width(); texture->height = bitmap->height(); - glBindTexture(GL_TEXTURE_2D, texture->id); + Caches::getInstance().bindTexture(texture->id); switch (bitmap->getConfig()) { case SkBitmap::kA8_Config: diff --git a/libs/hwui/UvMapper.h b/libs/hwui/UvMapper.h new file mode 100644 index 0000000..70428d2 --- /dev/null +++ b/libs/hwui/UvMapper.h @@ -0,0 +1,133 @@ +/* + * 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_UV_MAPPER_H +#define ANDROID_HWUI_UV_MAPPER_H + +#include "Rect.h" + +namespace android { +namespace uirenderer { + +/** + * This class can be used to map UV coordinates from the [0..1] + * range to other arbitrary ranges. All the methods below assume + * that the input values lie in the [0..1] range already. + */ +class UvMapper { +public: + /** + * Using this constructor is equivalent to not using any mapping at all. + * UV coordinates in the [0..1] range remain in the [0..1] range. + */ + UvMapper(): mIdentity(true), mMinU(0.0f), mMaxU(1.0f), mMinV(0.0f), mMaxV(1.0f) { + } + + /** + * Creates a new mapper with the specified ranges for U and V coordinates. + * The parameter minU must be < maxU and minV must be < maxV. + */ + UvMapper(float minU, float maxU, float minV, float maxV): + mMinU(minU), mMaxU(maxU), mMinV(minV), mMaxV(maxV) { + checkIdentity(); + } + + /** + * Returns true if calling the map*() methods has no effect (that is, + * texture coordinates remain in the 0..1 range.) + */ + bool isIdentity() const { + return mIdentity; + } + + /** + * Changes the U and V mapping ranges. + * The parameter minU must be < maxU and minV must be < maxV. + */ + void setMapping(float minU, float maxU, float minV, float maxV) { + mMinU = minU; + mMaxU = maxU; + mMinV = minV; + mMaxV = maxV; + checkIdentity(); + } + + /** + * Maps a single value in the U range. + */ + void mapU(float& u) const { + if (!mIdentity) u = lerp(mMinU, mMaxU, u); + } + + /** + * Maps a single value in the V range. + */ + void mapV(float& v) const { + if (!mIdentity) v = lerp(mMinV, mMaxV, v); + } + + /** + * Maps the specified rectangle in place. This method assumes: + * - left = min. U + * - top = min. V + * - right = max. U + * - bottom = max. V + */ + void map(Rect& texCoords) const { + if (!mIdentity) { + texCoords.left = lerp(mMinU, mMaxU, texCoords.left); + texCoords.right = lerp(mMinU, mMaxU, texCoords.right); + texCoords.top = lerp(mMinV, mMaxV, texCoords.top); + texCoords.bottom = lerp(mMinV, mMaxV, texCoords.bottom); + } + } + + /** + * Maps the specified UV coordinates in place. + */ + void map(float& u1, float& v1, float& u2, float& v2) const { + if (!mIdentity) { + u1 = lerp(mMinU, mMaxU, u1); + u2 = lerp(mMinU, mMaxU, u2); + v1 = lerp(mMinV, mMaxV, v1); + v2 = lerp(mMinV, mMaxV, v2); + } + } + + void dump() const { + ALOGD("mapper[minU=%.2f maxU=%.2f minV=%.2f maxV=%.2f]", mMinU, mMaxU, mMinV, mMaxV); + } + +private: + static float lerp(float start, float stop, float amount) { + return start + (stop - start) * amount; + } + + void checkIdentity() { + mIdentity = mMinU == 0.0f && mMaxU == 1.0f && mMinV == 0.0f && mMaxV == 1.0f; + } + + bool mIdentity; + float mMinU; + float mMaxU; + float mMinV; + float mMaxV; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_UV_MAPPER_H diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h index 523120e..c06762f 100644 --- a/libs/hwui/Vertex.h +++ b/libs/hwui/Vertex.h @@ -17,6 +17,8 @@ #ifndef ANDROID_HWUI_VERTEX_H #define ANDROID_HWUI_VERTEX_H +#include "Vector.h" + namespace android { namespace uirenderer { @@ -30,6 +32,15 @@ struct Vertex { vertex[0].position[0] = x; vertex[0].position[1] = y; } + + static inline void set(Vertex* vertex, vec2 val) { + set(vertex, val.x, val.y); + } + + static inline void copyWithOffset(Vertex* vertex, const Vertex& src, float x, float y) { + set(vertex, src.position[0] + x, src.position[1] + y); + } + }; // struct Vertex /** @@ -81,6 +92,12 @@ struct AlphaVertex : Vertex { vertex[0].alpha = alpha; } + static inline void copyWithOffset(AlphaVertex* vertex, const AlphaVertex& src, + float x, float y) { + Vertex::set(vertex, src.position[0] + x, src.position[1] + y); + vertex[0].alpha = src.alpha; + } + static inline void setColor(AlphaVertex* vertex, float alpha) { vertex[0].alpha = alpha; } diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp index 6c5267d..5f15724 100644 --- a/libs/hwui/font/CacheTexture.cpp +++ b/libs/hwui/font/CacheTexture.cpp @@ -17,6 +17,7 @@ #include <SkGlyph.h> #include "CacheTexture.h" +#include "../Caches.h" #include "../Debug.h" #include "../Extensions.h" #include "../PixelBuffer.h" @@ -110,7 +111,8 @@ CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) 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) { + mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount), + mCaches(Caches::getInstance()) { mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); @@ -154,7 +156,7 @@ void CacheTexture::releaseTexture() { mTexture = NULL; } if (mTextureId) { - glDeleteTextures(1, &mTextureId); + mCaches.deleteTexture(mTextureId); mTextureId = 0; } mDirty = false; @@ -166,7 +168,7 @@ void CacheTexture::setLinearFiltering(bool linearFiltering, bool bind) { mLinearFiltering = linearFiltering; const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST; - if (bind) glBindTexture(GL_TEXTURE_2D, getTextureId()); + if (bind) mCaches.bindTexture(getTextureId()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); } @@ -186,7 +188,7 @@ void CacheTexture::allocateTexture() { if (!mTextureId) { glGenTextures(1, &mTextureId); - glBindTexture(GL_TEXTURE_2D, mTextureId); + mCaches.bindTexture(mTextureId); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Initialize texture dimensions glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mWidth, mHeight, 0, diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h index ddcc836..8c3ea0b 100644 --- a/libs/hwui/font/CacheTexture.h +++ b/libs/hwui/font/CacheTexture.h @@ -30,6 +30,7 @@ namespace android { namespace uirenderer { +class Caches; class PixelBuffer; /** @@ -178,9 +179,10 @@ private: TextureVertex* mMesh; uint32_t mCurrentQuad; uint32_t mMaxQuadCount; + Caches& mCaches; CacheBlock* mCacheBlocks; - Rect mDirtyRect; bool mHasES3; + Rect mDirtyRect; }; }; // namespace uirenderer diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h index 477314b..f2a216f 100644 --- a/libs/hwui/thread/TaskManager.h +++ b/libs/hwui/thread/TaskManager.h @@ -43,7 +43,7 @@ public: /** * Returns true if this task manager can run tasks, * false otherwise. This method will typically return - * true on a single CPU core device. + * false on a single CPU core device. */ bool canRunTasks() const; |