diff options
Diffstat (limited to 'libs/hwui')
68 files changed, 3922 insertions, 1587 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index a630ea1..411c133 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,26 @@ 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 libstlport + LOCAL_C_INCLUDES += \ + $(intermediates) \ + frameworks/rs/cpp \ + frameworks/rs \ + external/stlport/stlport \ + bionic/ \ + bionic/libstdc++/include + 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..eb8bb9f --- /dev/null +++ b/libs/hwui/AssetAtlas.cpp @@ -0,0 +1,142 @@ +/* + * 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 "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; + } + + mGenerationId++; +} + +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); + texture->id = mTexture->id; + texture->blend = !bitmap->isOpaque(); + texture->width = bitmap->width(); + texture->height = bitmap->height(); + + Entry* entry = new Entry(bitmap, x, y, rotated, texture, mapper, *this); + 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..a28efc6 --- /dev/null +++ b/libs/hwui/AssetAtlas.h @@ -0,0 +1,195 @@ +/* + * 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; + + /** + * Unique identifier used to merge bitmaps and 9-patches stored + * in the atlas. + */ + const void* getMergeId() const { + return texture->blend ? &atlas.mBlendKey : &atlas.mOpaqueKey; + } + + 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), mGenerationId(0), + mBlendKey(true), mOpaqueKey(false) { } + ~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; + + /** + * Returns the current generation id of the atlas. + */ + uint32_t getGenerationId() const { + return mGenerationId; + } + +private: + void createEntries(Caches& caches, int* map, int count); + + Texture* mTexture; + Image* mImage; + + uint32_t mGenerationId; + + const bool mBlendKey; + const bool mOpaqueKey; + + 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..f8d3589 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.hasPixelBufferObjects()) { + 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; @@ -145,11 +166,16 @@ bool Caches::initProperties() { debugLayersUpdates = false; } + debugOverdraw = false; if (property_get(PROPERTY_DEBUG_OVERDRAW, property, NULL) > 0) { INIT_LOGD(" Overdraw debug enabled: %s", property); - debugOverdraw = !strcmp(property, "true"); - } else { - debugOverdraw = false; + if (!strcmp(property, "show")) { + debugOverdraw = true; + mOverdrawDebugColorSet = kColorSet_Default; + } else if (!strcmp(property, "show_deuteranomaly")) { + debugOverdraw = true; + mOverdrawDebugColorSet = kColorSet_Deuteranomaly; + } } // See Properties.h for valid values @@ -191,8 +217,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 +227,12 @@ void Caches::terminate() { programCache.clear(); currentProgram = NULL; + assetAtlas.terminate(); + + patchCache.clear(); + + clearGarbage(); + mInitialized = false; } @@ -207,6 +240,16 @@ void Caches::terminate() { // Debug /////////////////////////////////////////////////////////////////////////////// +uint32_t Caches::getOverdrawColor(uint32_t amount) const { + static uint32_t sOverdrawColors[2][4] = { + { 0x2f0000ff, 0x2f00ff00, 0x3fff0000, 0x7fff0000 }, + { 0x2f0000ff, 0x4fffff00, 0x5fff8ad8, 0x7fff0000 } + }; + if (amount < 1) amount = 1; + if (amount > 4) amount = 4; + return sOverdrawColors[mOverdrawDebugColorSet][amount - 1]; +} + void Caches::dumpMemoryUsage() { String8 stringLog; dumpMemoryUsage(stringLog); @@ -227,15 +270,19 @@ 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); + const uint32_t sizeA8 = fontRenderer->getFontRendererSize(i, GL_ALPHA); + const uint32_t sizeRGBA = fontRenderer->getFontRendererSize(i, GL_RGBA); + log.appendFormat(" FontRenderer %d A8 %8d / %8d\n", i, sizeA8, sizeA8); + log.appendFormat(" FontRenderer %d RGBA %8d / %8d\n", i, sizeRGBA, sizeRGBA); + log.appendFormat(" FontRenderer %d total %8d / %8d\n", i, sizeA8 + sizeRGBA, + sizeA8 + sizeRGBA); } 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,8 +291,10 @@ 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); + total += fontRenderer->getFontRendererSize(i, GL_ALPHA); + total += fontRenderer->getFontRendererSize(i, GL_RGBA); } log.appendFormat("Total memory usage:\n"); @@ -259,6 +308,7 @@ void Caches::dumpMemoryUsage(String8 &log) { void Caches::clearGarbage() { textureCache.clearGarbage(); pathCache.clearGarbage(); + patchCache.clearGarbage(); Vector<DisplayList*> displayLists; Vector<Layer*> layers; @@ -298,6 +348,11 @@ void Caches::deleteDisplayListDeferred(DisplayList* displayList) { void Caches::flush(FlushMode mode) { FLUSH_LOGD("Flushing caches (mode %d)", mode); + // We must stop tasks before clearing caches + if (mode > kFlushMode_Layers) { + tasks.stop(); + } + switch (mode) { case kFlushMode_Full: textureCache.clear(); @@ -305,13 +360,13 @@ void Caches::flush(FlushMode mode) { dropShadowCache.clear(); gradientCache.clear(); fontRenderer->clear(); + fboCache.clear(); dither.clear(); // fall through case kFlushMode_Moderate: fontRenderer->flush(); textureCache.flush(); pathCache.clear(); - tasks.stop(); // fall through case kFlushMode_Layers: layerCache.clear(); @@ -357,6 +412,32 @@ bool Caches::bindIndicesBuffer(const GLuint buffer) { return false; } +bool Caches::bindIndicesBuffer() { + if (!mMeshIndices) { + uint16_t* regionIndices = new uint16_t[gMaxNumberOfQuads * 6]; + for (uint32_t i = 0; i < gMaxNumberOfQuads; 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, gMaxNumberOfQuads * 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 +522,50 @@ void Caches::activeTexture(GLuint textureUnit) { } } +void Caches::resetActiveTexture() { + mTextureUnit = -1; +} + +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 /////////////////////////////////////////////////////////////////////////////// @@ -545,28 +670,7 @@ void Caches::unregisterFunctors(uint32_t functorCount) { 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); + mRegionMesh = new TextureVertex[gMaxNumberOfQuads * 4]; } return mRegionMesh; diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 91b938b..282aee9 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,9 +55,11 @@ 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 +// Maximum number of quads that pre-allocated meshes can draw +static const uint32_t gMaxNumberOfQuads = 2048; // Generates simple and textured vertices #define FV(x, y, u, v) { { x, y }, { u, v } } @@ -74,6 +81,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 +121,7 @@ public: /** * Initialize caches. */ - void init(); + bool init(); /** * Initialize global system properties. @@ -142,6 +150,12 @@ public: } /** + * Returns a non-premultiplied ARGB color for the specified + * amount of overdraw (1 for 1x, 2 for 2x, etc.) + */ + uint32_t getOverdrawColor(uint32_t amount) const; + + /** * Call this on each frame to ensure that garbage is deleted from * GPU memory. */ @@ -172,6 +186,11 @@ public: */ bool unbindMeshBuffer(); + /** + * Binds a global indices buffer that can draw up to + * gMaxNumberOfQuads quads. + */ + bool bindIndicesBuffer(); bool bindIndicesBuffer(const GLuint buffer); bool unbindIndicesBuffer(); @@ -213,6 +232,38 @@ public: void activeTexture(GLuint textureUnit); /** + * Invalidate the cached value of the active texture unit. + */ + void resetActiveTexture(); + + /** + * 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 +341,10 @@ public: Dither dither; Stencil stencil; + AssetAtlas assetAtlas; + + bool gpuPixelBuffersEnabled; + // Debug methods PFNGLINSERTEVENTMARKEREXTPROC eventMark; PFNGLPUSHGROUPMARKEREXTPROC startMark; @@ -299,9 +354,15 @@ public: PFNGLGETOBJECTLABELEXTPROC getLabel; private: + enum OverdrawColorSet { + kColorSet_Default = 0, + kColorSet_Deuteranomaly + }; + 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 +397,9 @@ private: // Used to render layers TextureVertex* mRegionMesh; - GLuint mRegionMeshIndices; + + // Global index buffer + GLuint mMeshIndices; mutable Mutex mGarbageLock; Vector<Layer*> mLayerGarbage; @@ -346,6 +409,10 @@ private: bool mInitialized; uint32_t mFunctorsCount; + + GLuint mBoundTextures[REQUIRED_TEXTURE_UNITS_COUNT]; + + OverdrawColorSet mOverdrawDebugColorSet; }; // 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..7eb7028 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -20,6 +20,8 @@ #include <SkCanvas.h> #include <utils/Trace.h> +#include <ui/Rect.h> +#include <ui/Region.h> #include "Caches.h" #include "Debug.h" @@ -51,32 +53,36 @@ class Batch { public: virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) = 0; virtual ~Batch() {} + virtual bool purelyDrawBatch() { return false; } + virtual bool coversBounds(const Rect& bounds) { return false; } }; class DrawBatch : public Batch { public: - DrawBatch(int batchId, mergeid_t mergeId) : mBatchId(batchId), mMergeId(mergeId) { + DrawBatch(const DeferInfo& deferInfo) : mAllOpsOpaque(true), + mBatchId(deferInfo.batchId), mMergeId(deferInfo.mergeId) { mOps.clear(); } virtual ~DrawBatch() { mOps.clear(); } - void add(DrawOp* op) { + virtual void add(DrawOp* op, const DeferredDisplayState* state, bool opaqueOverBounds) { // NOTE: ignore empty bounds special case, since we don't merge across those ops - mBounds.unionWith(op->state.mBounds); - mOps.add(op); + mBounds.unionWith(state->mBounds); + mAllOpsOpaque &= opaqueOverBounds; + mOps.add(OpStatePair(op, state)); } - bool intersects(Rect& rect) { + bool intersects(const Rect& rect) { if (!rect.intersects(mBounds)) return false; for (unsigned int i = 0; i < mOps.size(); i++) { - if (rect.intersects(mOps[i]->state.mBounds)) { + if (rect.intersects(mOps[i].state->mBounds)) { #if DEBUG_DEFER - DEFER_LOGD("op intersects with op %p with bounds %f %f %f %f:", mOps[i], - mOps[i]->state.mBounds.left, mOps[i]->state.mBounds.top, - mOps[i]->state.mBounds.right, mOps[i]->state.mBounds.bottom); - mOps[i]->output(2); + DEFER_LOGD("op intersects with op %p with bounds %f %f %f %f:", mOps[i].op, + mOps[i].state->mBounds.left, mOps[i].state->mBounds.top, + mOps[i].state->mBounds.right, mOps[i].state->mBounds.bottom); + mOps[i].op->output(2); #endif return true; } @@ -85,15 +91,15 @@ 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(); for (unsigned int i = 0; i < mOps.size(); i++) { - DrawOp* op = mOps[i]; - - renderer.restoreDisplayState(op->state); + DrawOp* op = mOps[i].op; + const DeferredDisplayState* state = mOps[i].state; + renderer.restoreDisplayState(*state); #if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS renderer.eventMark(op->name()); @@ -102,7 +108,7 @@ public: status |= op->applyDraw(renderer, dirty); #if DEBUG_MERGE_BEHAVIOR - Rect& bounds = mOps[i]->state.mBounds; + const Rect& bounds = state->mBounds; int batchColor = 0x1f000000; if (getBatchId() & 0x1) batchColor |= 0x0000ff; if (getBatchId() & 0x2) batchColor |= 0x00ff00; @@ -114,14 +120,28 @@ public: return status; } + virtual bool purelyDrawBatch() { return true; } + + virtual bool coversBounds(const Rect& bounds) { + if (CC_LIKELY(!mAllOpsOpaque || !mBounds.contains(bounds) || count() == 1)) return false; + + Region uncovered(android::Rect(bounds.left, bounds.top, bounds.right, bounds.bottom)); + for (unsigned int i = 0; i < mOps.size(); i++) { + const Rect &r = mOps[i].state->mBounds; + uncovered.subtractSelf(android::Rect(r.left, r.top, r.right, r.bottom)); + } + return uncovered.isEmpty(); + } + inline int getBatchId() const { return mBatchId; } inline mergeid_t getMergeId() const { return mMergeId; } inline int count() const { return mOps.size(); } protected: - Vector<DrawOp*> mOps; - Rect mBounds; + Vector<OpStatePair> mOps; + Rect mBounds; // union of bounds of contained ops private: + bool mAllOpsOpaque; int mBatchId; mergeid_t mMergeId; }; @@ -132,39 +152,77 @@ private: class MergingDrawBatch : public DrawBatch { public: - MergingDrawBatch(int batchId, mergeid_t mergeId) : DrawBatch(batchId, mergeId) {} + MergingDrawBatch(DeferInfo& deferInfo, int width, int height) : + DrawBatch(deferInfo), mClipRect(width, height), + mClipSideFlags(kClipSide_None) {} + + /* + * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds + * and clip side flags. Positive bounds delta means new bounds fit in old. + */ + static inline bool checkSide(const int currentFlags, const int newFlags, const int side, + float boundsDelta) { + bool currentClipExists = currentFlags & side; + bool newClipExists = newFlags & side; + + // if current is clipped, we must be able to fit new bounds in current + if (boundsDelta > 0 && currentClipExists) return false; + + // if new is clipped, we must be able to fit current bounds in new + if (boundsDelta < 0 && newClipExists) return false; + + return true; + } /* * Checks if a (mergeable) op can be merged into this batch * * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is * important to consider all paint attributes used in the draw calls in deciding both a) if an - * op tries to merge at all, and b) if the op + * op tries to merge at all, and b) if the op can merge with another set of ops * * False positives can lead to information from the paints of subsequent merged operations being * dropped, so we make simplifying qualifications on the ops that can merge, per op type. */ - bool canMergeWith(DrawOp* op) { - if (!op->state.mMatrix.isPureTranslate()) return false; - + bool canMergeWith(const DrawOp* op, const DeferredDisplayState* state) { bool isTextBatch = getBatchId() == DeferredDisplayList::kOpBatch_Text || getBatchId() == DeferredDisplayList::kOpBatch_ColorText; // Overlapping other operations is only allowed for text without shadow. For other ops, // multiDraw isn't guaranteed to overdraw correctly - if (!isTextBatch || op->state.mDrawModifiers.mHasShadow) { - if (intersects(op->state.mBounds)) return false; + if (!isTextBatch || state->mDrawModifiers.mHasShadow) { + if (intersects(state->mBounds)) return false; } + const DeferredDisplayState* lhs = state; + const DeferredDisplayState* rhs = mOps[0].state; - const DeferredDisplayState& lhs = op->state; - const DeferredDisplayState& rhs = mOps[0]->state; + if (NEQ_FALPHA(lhs->mAlpha, rhs->mAlpha)) return false; - if (NEQ_FALPHA(lhs.mAlpha, rhs.mAlpha)) return false; + /* Clipping compatibility check + * + * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its + * clip for that side. + */ + const int currentFlags = mClipSideFlags; + const int newFlags = state->mClipSideFlags; + if (currentFlags != kClipSide_None || newFlags != kClipSide_None) { + const Rect& opBounds = state->mBounds; + float boundsDelta = mBounds.left - opBounds.left; + if (!checkSide(currentFlags, newFlags, kClipSide_Left, boundsDelta)) return false; + boundsDelta = mBounds.top - opBounds.top; + if (!checkSide(currentFlags, newFlags, kClipSide_Top, boundsDelta)) return false; + + // right and bottom delta calculation reversed to account for direction + boundsDelta = opBounds.right - mBounds.right; + if (!checkSide(currentFlags, newFlags, kClipSide_Right, boundsDelta)) return false; + boundsDelta = opBounds.bottom - mBounds.bottom; + if (!checkSide(currentFlags, newFlags, kClipSide_Bottom, boundsDelta)) return false; + } // if paints are equal, then modifiers + paint attribs don't need to be compared - if (op->mPaint == mOps[0]->mPaint) return true; + if (op->mPaint == mOps[0].op->mPaint) return true; - if (op->getPaintAlpha() != mOps[0]->getPaintAlpha()) return false; + if (op->getPaintAlpha() != mOps[0].op->getPaintAlpha()) return false; /* Draw Modifiers compatibility check * @@ -178,9 +236,8 @@ public: * * These ignore cases prevent us from simply memcmp'ing the drawModifiers */ - - const DrawModifiers& lhsMod = lhs.mDrawModifiers; - const DrawModifiers& rhsMod = rhs.mDrawModifiers; + const DrawModifiers& lhsMod = lhs->mDrawModifiers; + const DrawModifiers& rhsMod = rhs->mDrawModifiers; if (lhsMod.mShader != rhsMod.mShader) return false; if (lhsMod.mColorFilter != rhsMod.mColorFilter) return false; @@ -192,17 +249,37 @@ public: return true; } + virtual void add(DrawOp* op, const DeferredDisplayState* state, bool opaqueOverBounds) { + DrawBatch::add(op, state, opaqueOverBounds); + + const int newClipSideFlags = state->mClipSideFlags; + mClipSideFlags |= newClipSideFlags; + if (newClipSideFlags & kClipSide_Left) mClipRect.left = state->mClip.left; + if (newClipSideFlags & kClipSide_Top) mClipRect.top = state->mClip.top; + if (newClipSideFlags & kClipSide_Right) mClipRect.right = state->mClip.right; + if (newClipSideFlags & kClipSide_Bottom) mClipRect.bottom = state->mClip.bottom; + } + 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(), getBatchId(), getMergeId()); + DEFER_LOGD("%d replaying MergingDrawBatch %p, with %d ops," + " clip flags %x (batch id %x, merge id %p)", + index, this, mOps.size(), mClipSideFlags, getBatchId(), getMergeId()); if (mOps.size() == 1) { - return DrawBatch::replay(renderer, dirty, false); + return DrawBatch::replay(renderer, dirty, -1); } - DrawOp* op = mOps[0]; + // clipping in the merged case is done ahead of time since all ops share the clip (if any) + renderer.setupMergedMultiDraw(mClipSideFlags ? &mClipRect : NULL); + + DrawOp* op = mOps[0].op; DisplayListLogBuffer& buffer = DisplayListLogBuffer::getInstance(); buffer.writeCommand(0, "multiDraw"); buffer.writeCommand(1, op->name()); + +#if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS + renderer.eventMark("multiDraw"); + renderer.eventMark(op->name()); +#endif status_t status = op->multiDraw(renderer, dirty, mOps, mBounds); #if DEBUG_MERGE_BEHAVIOR @@ -211,16 +288,25 @@ public: #endif return status; } + +private: + /* + * Contains the effective clip rect shared by all merged ops. Initialized to the layer viewport, + * it will shrink if an op must be clipped on a certain side. The clipped sides are reflected in + * mClipSideFlags. + */ + Rect mClipRect; + int mClipSideFlags; }; class StateOpBatch : public Batch { public: // creates a single operation batch - StateOpBatch(StateOp* op) : mOp(op) {} + StateOpBatch(const StateOp* op, const DeferredDisplayState* state) : mOp(op), mState(state) {} virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) { DEFER_LOGD("replaying state op batch %p", this); - renderer.restoreDisplayState(mOp->state); + renderer.restoreDisplayState(*mState); // use invalid save count because it won't be used at flush time - RestoreToCountOp is the // only one to use it, and we don't use that class at flush time, instead calling @@ -232,16 +318,18 @@ public: private: const StateOp* mOp; + const DeferredDisplayState* mState; }; class RestoreToCountBatch : public Batch { public: - RestoreToCountBatch(StateOp* op, int restoreCount) : mOp(op), mRestoreCount(restoreCount) {} + RestoreToCountBatch(const StateOp* op, const DeferredDisplayState* state, int restoreCount) : + mOp(op), mState(state), mRestoreCount(restoreCount) {} virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) { DEFER_LOGD("batch %p restoring to count %d", this, mRestoreCount); - renderer.restoreDisplayState(mOp->state); + renderer.restoreDisplayState(*mState); renderer.restoreToCount(mRestoreCount); return DrawGlInfo::kStatusDone; } @@ -249,6 +337,8 @@ public: private: // we use the state storage for the RestoreToCountOp, but don't replay the op itself const StateOp* mOp; + const DeferredDisplayState* mState; + /* * The count used here represents the flush() time saveCount. This is as opposed to the * DisplayList record time, or defer() time values (which are RestoreToCountOp's mCount, and @@ -294,6 +384,7 @@ void DeferredDisplayList::clear() { mBatches.clear(); mSaveStack.clear(); mEarliestBatchIndex = 0; + mEarliestUnclearedIndex = 0; } ///////////////////////////////////////////////////////////////////////////////// @@ -398,22 +489,45 @@ void DeferredDisplayList::addRestoreToCount(OpenGLRenderer& renderer, StateOp* o } void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { - if (renderer.storeDisplayState(op->state, getDrawOpDeferFlags())) { + /* 1: op calculates local bounds */ + DeferredDisplayState* const state = createState(); + if (op->getLocalBounds(renderer.getDrawModifiers(), state->mBounds)) { + if (state->mBounds.isEmpty()) { + // valid empty bounds, don't bother deferring + tryRecycleState(state); + return; + } + } else { + state->mBounds.setEmpty(); + } + + /* 2: renderer calculates global bounds + stores state */ + if (renderer.storeDisplayState(*state, getDrawOpDeferFlags())) { + tryRecycleState(state); return; // quick rejected } - int batchId = kOpBatch_None; - mergeid_t mergeId = (mergeid_t) -1; - bool mergeable = op->onDefer(renderer, &batchId, &mergeId); + /* 3: ask op for defer info, given renderer state */ + DeferInfo deferInfo; + op->onDefer(renderer, deferInfo, *state); // complex clip has a complex set of expectations on the renderer state - for now, avoid taking // the merge path in those cases - mergeable &= !recordingComplexClip(); + deferInfo.mergeable &= !recordingComplexClip(); + deferInfo.opaqueOverBounds &= !recordingComplexClip() && mSaveStack.isEmpty(); + + if (CC_LIKELY(mAvoidOverdraw) && mBatches.size() && + state->mClipSideFlags != kClipSide_ConservativeFull && + deferInfo.opaqueOverBounds && state->mBounds.contains(mBounds)) { + // avoid overdraw by resetting drawing state + discarding drawing ops + discardDrawingBatches(mBatches.size() - 1); + resetBatchingState(); + } if (CC_UNLIKELY(renderer.getCaches().drawReorderDisabled)) { // TODO: elegant way to reuse batches? - DrawBatch* b = new DrawBatch(batchId, mergeId); - b->add(op); + DrawBatch* b = new DrawBatch(deferInfo); + b->add(op, state, deferInfo.opaqueOverBounds); mBatches.add(b); return; } @@ -425,10 +539,10 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { // (eventually, should be similar shader) int insertBatchIndex = mBatches.size(); if (!mBatches.isEmpty()) { - if (op->state.mBounds.isEmpty()) { + if (state->mBounds.isEmpty()) { // don't know the bounds for op, so add to last batch and start from scratch on next op - DrawBatch* b = new DrawBatch(batchId, mergeId); - b->add(op); + DrawBatch* b = new DrawBatch(deferInfo); + b->add(op, state, deferInfo.opaqueOverBounds); mBatches.add(b); resetBatchingState(); #if DEBUG_DEFER @@ -438,19 +552,19 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { return; } - if (mergeable) { + if (deferInfo.mergeable) { // Try to merge with any existing batch with same mergeId. - if (mMergingBatches[batchId].get(mergeId, targetBatch)) { - if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op)) { + if (mMergingBatches[deferInfo.batchId].get(deferInfo.mergeId, targetBatch)) { + if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op, state)) { targetBatch = NULL; } } } else { // join with similar, non-merging batch - targetBatch = (DrawBatch*)mBatchLookup[batchId]; + targetBatch = (DrawBatch*)mBatchLookup[deferInfo.batchId]; } - if (targetBatch || mergeable) { + if (targetBatch || deferInfo.mergeable) { // iterate back toward target to see if anything drawn since should overlap the new op // if no target, merging ops still interate to find similar batch to insert after for (int i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) { @@ -459,19 +573,19 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { if (overBatch == targetBatch) break; // TODO: also consider shader shared between batch types - if (batchId == overBatch->getBatchId()) { + if (deferInfo.batchId == overBatch->getBatchId()) { insertBatchIndex = i + 1; if (!targetBatch) break; // found insert position, quit } - if (overBatch->intersects(op->state.mBounds)) { + if (overBatch->intersects(state->mBounds)) { // NOTE: it may be possible to optimize for special cases where two operations // of the same batch/paint could swap order, such as with a non-mergeable // (clipped) and a mergeable text operation targetBatch = NULL; #if DEBUG_DEFER - DEFER_LOGD("op couldn't join batch %d, was intersected by batch %d", - targetIndex, i); + DEFER_LOGD("op couldn't join batch %p, was intersected by batch %d", + targetBatch, i); op->output(2); #endif break; @@ -481,27 +595,30 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { } if (!targetBatch) { - if (mergeable) { - targetBatch = new MergingDrawBatch(batchId, mergeId); - mMergingBatches[batchId].put(mergeId, targetBatch); + if (deferInfo.mergeable) { + targetBatch = new MergingDrawBatch(deferInfo, + renderer.getViewportWidth(), renderer.getViewportHeight()); + mMergingBatches[deferInfo.batchId].put(deferInfo.mergeId, targetBatch); } else { - targetBatch = new DrawBatch(batchId, mergeId); - mBatchLookup[batchId] = targetBatch; - DEFER_LOGD("creating Batch %p, bid %x, at %d", - targetBatch, batchId, insertBatchIndex); + targetBatch = new DrawBatch(deferInfo); + mBatchLookup[deferInfo.batchId] = targetBatch; } + DEFER_LOGD("creating %singBatch %p, bid %x, at %d", + deferInfo.mergeable ? "Merg" : "Draw", + targetBatch, deferInfo.batchId, insertBatchIndex); mBatches.insertAt(targetBatch, insertBatchIndex); } - targetBatch->add(op); + targetBatch->add(op, state, deferInfo.opaqueOverBounds); } void DeferredDisplayList::storeStateOpBarrier(OpenGLRenderer& renderer, StateOp* op) { DEFER_LOGD("%p adding state op barrier at pos %d", this, mBatches.size()); - renderer.storeDisplayState(op->state, getStateOpDeferFlags()); - mBatches.add(new StateOpBatch(op)); + DeferredDisplayState* state = createState(); + renderer.storeDisplayState(*state, getStateOpDeferFlags()); + mBatches.add(new StateOpBatch(op, state)); resetBatchingState(); } @@ -512,8 +629,9 @@ void DeferredDisplayList::storeRestoreToCountBarrier(OpenGLRenderer& renderer, S // store displayState for the restore operation, as it may be associated with a saveLayer that // doesn't have kClip_SaveFlag set - renderer.storeDisplayState(op->state, getStateOpDeferFlags()); - mBatches.add(new RestoreToCountBatch(op, newSaveCount)); + DeferredDisplayState* state = createState(); + renderer.storeDisplayState(*state, getStateOpDeferFlags()); + mBatches.add(new RestoreToCountBatch(op, state, newSaveCount)); resetBatchingState(); } @@ -526,7 +644,9 @@ static status_t replayBatchList(const Vector<Batch*>& batchList, status_t status = DrawGlInfo::kStatusDone; for (unsigned int i = 0; i < batchList.size(); i++) { - status |= batchList[i]->replay(renderer, dirty, i); + if (batchList[i]) { + status |= batchList[i]->replay(renderer, dirty, i); + } } DEFER_LOGD("--flushed, drew %d batches", batchList.size()); return status; @@ -548,6 +668,13 @@ status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) { DrawModifiers restoreDrawModifiers = renderer.getDrawModifiers(); renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + if (CC_LIKELY(mAvoidOverdraw)) { + for (unsigned int i = 1; i < mBatches.size(); i++) { + if (mBatches[i] && mBatches[i]->coversBounds(mBounds)) { + discardDrawingBatches(i - 1); + } + } + } // NOTE: depth of the save stack at this point, before playback, should be reflected in // FLUSH_SAVE_STACK_DEPTH, so that save/restores match up correctly status |= replayBatchList(mBatches, renderer, dirty); @@ -560,5 +687,17 @@ status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) { return status; } +void DeferredDisplayList::discardDrawingBatches(const unsigned int maxIndex) { + for (unsigned int i = mEarliestUnclearedIndex; i <= maxIndex; i++) { + // leave deferred state ops alone for simplicity (empty save restore pairs may now exist) + if (mBatches[i] && mBatches[i]->purelyDrawBatch()) { + DrawBatch* b = (DrawBatch*) mBatches[i]; + delete mBatches[i]; + mBatches.replaceAt(NULL, i); + } + } + mEarliestUnclearedIndex = maxIndex + 1; +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h index 9782c1c..3dcbd0b 100644 --- a/libs/hwui/DeferredDisplayList.h +++ b/libs/hwui/DeferredDisplayList.h @@ -18,11 +18,13 @@ #define ANDROID_HWUI_DEFERRED_DISPLAY_LIST_H #include <utils/Errors.h> +#include <utils/LinearAllocator.h> #include <utils/Vector.h> +#include <utils/TinyHashMap.h> #include "Matrix.h" +#include "OpenGLRenderer.h" #include "Rect.h" -#include "utils/TinyHashMap.h" class SkBitmap; @@ -34,18 +36,56 @@ class DrawOp; class SaveOp; class SaveLayerOp; class StateOp; + +class DeferredDisplayState; class OpenGLRenderer; class Batch; class DrawBatch; class MergingDrawBatch; -typedef void* mergeid_t; +typedef const void* mergeid_t; + +class DeferredDisplayState { +public: + /** static void* operator new(size_t size); PURPOSELY OMITTED **/ + static void* operator new(size_t size, LinearAllocator& allocator) { + return allocator.alloc(size); + } + + // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped + Rect mBounds; + + // the below are set and used by the OpenGLRenderer at record and deferred playback + bool mClipValid; + Rect mClip; + int mClipSideFlags; // specifies which sides of the bounds are clipped, unclipped if cleared + bool mClipped; + mat4 mMatrix; + DrawModifiers mDrawModifiers; + float mAlpha; +}; + +class OpStatePair { +public: + OpStatePair() + : op(NULL), state(NULL) {} + OpStatePair(DrawOp* newOp, const DeferredDisplayState* newState) + : op(newOp), state(newState) {} + OpStatePair(const OpStatePair& other) + : op(other.op), state(other.state) {} + DrawOp* op; + const DeferredDisplayState* state; +}; class DeferredDisplayList { public: - DeferredDisplayList() { clear(); } + DeferredDisplayList(const Rect& bounds, bool avoidOverdraw = true) : + mBounds(bounds), mAvoidOverdraw(avoidOverdraw) { + clear(); + } ~DeferredDisplayList() { clear(); } + void reset(const Rect& bounds) { mBounds.set(bounds); } enum OpBatchId { kOpBatch_None = 0, // Don't batch @@ -75,11 +115,19 @@ public: /** * Add a draw op into the DeferredDisplayList, reordering as needed (for performance) if - * disallowReorder is false, respecting draw order when overlaps occur + * disallowReorder is false, respecting draw order when overlaps occur. */ void addDrawOp(OpenGLRenderer& renderer, DrawOp* op); private: + DeferredDisplayState* createState() { + return new (mAllocator) DeferredDisplayState(); + } + + void tryRecycleState(DeferredDisplayState* state) { + mAllocator.rewindIfLastAlloc(state, sizeof(DeferredDisplayState)); + } + /** * Resets the batching back-pointers, creating a barrier in the operation stream so that no ops * added in the future will be inserted into a batch that already exist. @@ -96,6 +144,12 @@ private: int getStateOpDeferFlags() const; int getDrawOpDeferFlags() const; + void discardDrawingBatches(const unsigned int maxIndex); + + // layer space bounds of rendering + Rect mBounds; + const bool mAvoidOverdraw; + /** * At defer time, stores the *defer time* savecount of save/saveLayer ops that were deferred, so * that when an associated restoreToCount is deferred, it can be recorded as a @@ -112,12 +166,35 @@ private: // Points to the index after the most recent barrier int mEarliestBatchIndex; + // Points to the first index that may contain a pure drawing batch + int mEarliestUnclearedIndex; + /** * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not * collide, which avoids the need to resolve mergeid collisions. */ TinyHashMap<mergeid_t, DrawBatch*> mMergingBatches[kOpBatch_Count]; + + LinearAllocator mAllocator; +}; + +/** + * Struct containing information that instructs the defer + */ +struct DeferInfo { +public: + DeferInfo() : + batchId(DeferredDisplayList::kOpBatch_None), + mergeId((mergeid_t) -1), + mergeable(false), + opaqueOverBounds(false) { + }; + + int batchId; + mergeid_t mergeId; + bool mergeable; + bool opaqueOverBounds; // opaque over bounds in DeferredDisplayState - can skip ops below }; }; // namespace uirenderer diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index 1cbd531..bb6526e 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -44,13 +44,14 @@ void DisplayList::outputLogBuffer(int fd) { } DisplayList::DisplayList(const DisplayListRenderer& recorder) : - mTransformMatrix(NULL), mTransformCamera(NULL), mTransformMatrix3D(NULL), + mDestroyed(false), mTransformMatrix(NULL), mTransformCamera(NULL), mTransformMatrix3D(NULL), mStaticMatrix(NULL), mAnimationMatrix(NULL) { initFromDisplayListRenderer(recorder); } DisplayList::~DisplayList() { + mDestroyed = true; clearResources(); } @@ -63,7 +64,6 @@ void DisplayList::destroyDisplayListDeferred(DisplayList* displayList) { void DisplayList::clearResources() { mDisplayListData = NULL; - mSize = 0; // TODO: shouldn't be needed, WAR possible use after delete mClipRectOp = NULL; mSaveLayerOp = NULL; @@ -100,6 +100,10 @@ void DisplayList::clearResources() { caches.resourceCache.decrementRefcountLocked(mFilterResources.itemAt(i)); } + for (size_t i = 0; i < mPatchResources.size(); i++) { + caches.resourceCache.decrementRefcountLocked(mPatchResources.itemAt(i)); + } + for (size_t i = 0; i < mShaders.size(); i++) { caches.resourceCache.decrementRefcountLocked(mShaders.itemAt(i)); caches.resourceCache.destructorLocked(mShaders.itemAt(i)); @@ -134,6 +138,7 @@ void DisplayList::clearResources() { mBitmapResources.clear(); mOwnedBitmapResources.clear(); mFilterResources.clear(); + mPatchResources.clear(); mShaders.clear(); mSourcePaths.clear(); mPaints.clear(); @@ -169,6 +174,10 @@ void DisplayList::initFromDisplayListRenderer(const DisplayListRenderer& recorde mSaveLayerOp = new (alloc) SaveLayerOp(); mSaveOp = new (alloc) SaveOp(); mRestoreToCountOp = new (alloc) RestoreToCountOp(); + if (CC_UNLIKELY(!mSaveOp)) { // temporary debug logging + ALOGW("Error: %s's SaveOp not allocated, size %d", getName(), mSize); + CRASH(); + } mFunctorCount = recorder.getFunctorCount(); @@ -197,6 +206,13 @@ void DisplayList::initFromDisplayListRenderer(const DisplayListRenderer& recorde caches.resourceCache.incrementRefcountLocked(resource); } + const Vector<Res_png_9patch*>& patchResources = recorder.getPatchResources(); + for (size_t i = 0; i < patchResources.size(); i++) { + Res_png_9patch* resource = patchResources.itemAt(i); + mPatchResources.add(resource); + caches.resourceCache.incrementRefcountLocked(resource); + } + const Vector<SkiaShader*>& shaders = recorder.getShaders(); for (size_t i = 0; i < shaders.size(); i++) { SkiaShader* resource = shaders.itemAt(i); @@ -342,7 +358,7 @@ void DisplayList::outputViewProperties(const int level) { } if (mAnimationMatrix) { ALOGD("%*sConcatMatrix (animation) %p: " MATRIX_STRING, - level * 2, "", mAnimationMatrix, MATRIX_ARGS(mStaticMatrix)); + level * 2, "", mAnimationMatrix, MATRIX_ARGS(mAnimationMatrix)); } if (mMatrixFlags != 0) { if (mMatrixFlags == TRANSLATION) { @@ -352,6 +368,8 @@ void DisplayList::outputViewProperties(const int level) { level * 2, "", mTransformMatrix, MATRIX_ARGS(mTransformMatrix)); } } + + bool clipToBoundsNeeded = mCaching ? false : mClipToBounds; if (mAlpha < 1) { if (mCaching) { ALOGD("%*sSetOverrideLayerAlpha %.2f", level * 2, "", mAlpha); @@ -359,15 +377,16 @@ void DisplayList::outputViewProperties(const int level) { ALOGD("%*sScaleAlpha %.2f", level * 2, "", mAlpha); } else { int flags = SkCanvas::kHasAlphaLayer_SaveFlag; - if (mClipToBounds) { + if (clipToBoundsNeeded) { flags |= SkCanvas::kClipToLayer_SaveFlag; + clipToBoundsNeeded = false; // clipping done by save layer } ALOGD("%*sSaveLayerAlpha %.2f, %.2f, %.2f, %.2f, %d, 0x%x", level * 2, "", (float) 0, (float) 0, (float) mRight - mLeft, (float) mBottom - mTop, (int)(mAlpha * 255), flags); } } - if (mClipToBounds && !mCaching) { + if (clipToBoundsNeeded) { ALOGD("%*sClipRect %.2f, %.2f, %.2f, %.2f", level * 2, "", 0.0f, 0.0f, (float) mRight - mLeft, (float) mBottom - mTop); } @@ -402,6 +421,7 @@ void DisplayList::setViewProperties(OpenGLRenderer& renderer, T& handler, renderer.concatMatrix(mTransformMatrix); } } + bool clipToBoundsNeeded = mCaching ? false : mClipToBounds; if (mAlpha < 1) { if (mCaching) { renderer.setOverrideLayerAlpha(mAlpha); @@ -412,15 +432,16 @@ void DisplayList::setViewProperties(OpenGLRenderer& renderer, T& handler, // have to pass it into this call. In fact, this information might be in the // location/size info that we store with the new native transform data. int saveFlags = SkCanvas::kHasAlphaLayer_SaveFlag; - if (mClipToBounds) { + if (clipToBoundsNeeded) { saveFlags |= SkCanvas::kClipToLayer_SaveFlag; + clipToBoundsNeeded = false; // clipping done by saveLayer } handler(mSaveLayerOp->reinit(0, 0, mRight - mLeft, mBottom - mTop, mAlpha * 255, SkXfermode::kSrcOver_Mode, saveFlags), PROPERTY_SAVECOUNT, mClipToBounds); } } - if (mClipToBounds && !mCaching) { + if (clipToBoundsNeeded) { handler(mClipRectOp->reinit(0, 0, mRight - mLeft, mBottom - mTop, SkRegion::kIntersect_Op), PROPERTY_SAVECOUNT, mClipToBounds); } @@ -480,7 +501,11 @@ void DisplayList::replay(ReplayStateStruct& replayStruct, const int level) { */ template <class T> void DisplayList::iterate(OpenGLRenderer& renderer, T& handler, const int level) { - if (mSize == 0 || mAlpha <= 0 || CC_UNLIKELY(!mSaveOp)) { // TODO: shouldn't need mSaveOp check + if (CC_UNLIKELY(mDestroyed)) { // temporary debug logging + ALOGW("Error: %s is drawing after destruction, size %d", getName(), mSize); + CRASH(); + } + if (mSize == 0 || mAlpha <= 0) { DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", level * 2, "", this, mName.string()); return; } @@ -514,6 +539,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..1cd5f1c 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -26,13 +26,15 @@ #include <private/hwui/DrawGlInfo.h> +#include <utils/LinearAllocator.h> #include <utils/RefBase.h> #include <utils/SortedVector.h> #include <utils/String8.h> #include <utils/Vector.h> + #include <cutils/compiler.h> -#include "utils/LinearAllocator.h" +#include <androidfw/ResourceTypes.h> #include "Debug.h" @@ -111,7 +113,6 @@ public: void initFromDisplayListRenderer(const DisplayListRenderer& recorder, bool reusing = false); - void defer(DeferStateStruct& deferStruct, const int level); void replay(ReplayStateStruct& replayStruct, const int level); @@ -129,7 +130,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); + } } } @@ -479,6 +485,7 @@ private: Vector<SkBitmap*> mBitmapResources; Vector<SkBitmap*> mOwnedBitmapResources; Vector<SkiaColorFilter*> mFilterResources; + Vector<Res_png_9patch*> mPatchResources; Vector<SkPaint*> mPaints; Vector<SkPath*> mPaths; @@ -496,6 +503,7 @@ private: uint32_t mFunctorCount; String8 mName; + bool mDestroyed; // used for debugging crash, TODO: remove once invalid state crash fixed // View properties bool mClipToBounds; @@ -525,11 +533,11 @@ private: * an alpha causes a SaveLayerAlpha to occur). These operations point into mDisplayListData's * allocation, or null if uninitialized. * - * These are initialized (via friend constructors) when a displayList is issued in either replay - * or deferred mode. If replaying, the ops are not used until the next frame. If deferring, the - * ops may be stored in the DeferredDisplayList to be played back a second time. + * These are initialized (via friend re-constructors) when a displayList is issued in either + * replay or deferred mode. If replaying, the ops are not used until the next frame. If + * deferring, the ops may be stored in the DeferredDisplayList to be played back a second time. * - * They should be used at most once per frame (one call to iterate) + * They should be used at most once per frame (one call to 'iterate') to avoid overwriting data */ ClipRectOp* mClipRectOp; SaveLayerOp* mSaveLayerOp; diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index a0290e3..326805a 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -26,26 +26,19 @@ #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 { \ - *(int *)(uintptr_t)0xbbadbeef = 0; \ + *(int *)(uintptr_t) 0xbbadbeef = 0; \ ((void(*)())0)(); /* More reliable, but doesn't say BBADBEEF */ \ } while(false) -#define MATRIX_STRING "[%.2f %.2f %.2f] [%.2f %.2f %.2f] [%.2f %.2f %.2f]" -#define MATRIX_ARGS(m) \ - m->get(0), m->get(1), m->get(2), \ - m->get(3), m->get(4), m->get(5), \ - m->get(6), m->get(7), m->get(8) -#define RECT_STRING "%.2f %.2f %.2f %.2f" -#define RECT_ARGS(r) \ - r.left, r.top, r.right, r.bottom - // Use OP_LOG for logging with arglist, OP_LOGS if just printing char* -#define OP_LOGS(s) OP_LOG("%s", s) +#define OP_LOGS(s) OP_LOG("%s", (s)) #define OP_LOG(s, ...) ALOGD( "%*s" s, level * 2, "", __VA_ARGS__ ) namespace android { @@ -84,20 +77,11 @@ public: virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level, bool useQuickReject) = 0; - virtual void output(int level, uint32_t logFlags = 0) = 0; + virtual void output(int level, uint32_t logFlags = 0) const = 0; // NOTE: it would be nice to declare constants and overriding the implementation in each op to // point at the constants, but that seems to require a .cpp file virtual const char* name() = 0; - - /** - * Stores the relevant canvas state of the object between deferral and replay (if the canvas - * state supports being stored) See OpenGLRenderer::simpleClipAndState() - * - * TODO: don't reserve space for StateOps that won't be deferred - */ - DeferredDisplayState state; - }; class StateOp : public DisplayListOp { @@ -136,11 +120,6 @@ public: return; } - if (!getLocalBounds(state.mBounds)) { - // empty bounds signify bounds can't be calculated - state.mBounds.setEmpty(); - } - deferStruct.mDeferredList.addDrawOp(deferStruct.mRenderer, this); } @@ -163,16 +142,16 @@ public: * reducing which operations are tagged as mergeable. */ virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty, - const Vector<DrawOp*>& ops, const Rect& bounds) { + const Vector<OpStatePair>& ops, const Rect& bounds) { status_t status = DrawGlInfo::kStatusDone; for (unsigned int i = 0; i < ops.size(); i++) { - renderer.restoreDisplayState(ops[i]->state); - status |= ops[i]->applyDraw(renderer, dirty); + renderer.restoreDisplayState(*(ops[i].state), true); + status |= ops[i].op->applyDraw(renderer, dirty); } return status; } - /* + /** * When this method is invoked the state field is initialized to have the * final rendering state. We can thus use it to process data as it will be * used at draw time. @@ -180,21 +159,25 @@ public: * Additionally, this method allows subclasses to provide defer-time preferences for batching * and merging. * - * Return true if the op can merge with others of its kind (such subclasses should implement - * multiDraw) + * if a subclass can set deferInfo.mergeable to true, it should implement multiDraw() */ - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) {} + + /** + * Query the conservative, local bounds (unmapped) bounds of the op. + * + * returns true if bounds exist + */ + virtual bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) { return false; } - // returns true if bounds exist - virtual bool getLocalBounds(Rect& localBounds) { return false; } - // TODO: better refine localbounds usage void setQuickRejected(bool quickRejected) { mQuickRejected = quickRejected; } bool getQuickRejected() { return mQuickRejected; } - inline int getPaintAlpha() { + inline int getPaintAlpha() const { return OpenGLRenderer::getAlphaDirect(mPaint); } @@ -209,6 +192,23 @@ protected: return renderer.filterPaint(mPaint); } + // Helper method for determining op opaqueness. Assumes op fills its bounds in local + // coordinates, and that paint's alpha is used + inline bool isOpaqueOverBounds(const DeferredDisplayState& state) { + // ensure that local bounds cover mapped bounds + if (!state.mMatrix.isSimple()) return false; + + // check state/paint for transparency + if (state.mDrawModifiers.mShader || + state.mAlpha != 1.0f || + (mPaint && mPaint->getAlpha() != 0xFF)) return false; + + SkXfermode::Mode mode = OpenGLRenderer::getXfermodeDirect(mPaint); + return (mode == SkXfermode::kSrcOver_Mode || + mode == SkXfermode::kSrc_Mode); + + } + SkPaint* mPaint; // should be accessed via getPaint() when applying bool mQuickRejected; }; @@ -218,6 +218,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) @@ -232,24 +235,20 @@ public: } // default empty constructor for bounds, to be overridden in child constructor body - DrawBoundedOp(SkPaint* paint) - : DrawOp(paint) {} + DrawBoundedOp(SkPaint* paint): DrawOp(paint) { } - bool getLocalBounds(Rect& localBounds) { + bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) { localBounds.set(mLocalBounds); + if (drawModifiers.mHasShadow) { + // TODO: inspect paint's looper directly + Rect shadow(mLocalBounds); + shadow.translate(drawModifiers.mShadowDx, drawModifiers.mShadowDy); + shadow.outset(drawModifiers.mShadowRadius); + localBounds.unionWith(shadow); + } return true; } - bool mergeAllowed() { - if (!state.mMatrix.isPureTranslate()) return false; - - // checks that we're unclipped, and srcover - const Rect& opBounds = state.mBounds; - return fabs(opBounds.getWidth() - mLocalBounds.getWidth()) < 0.1 && - fabs(opBounds.getHeight() - mLocalBounds.getHeight()) < 0.1 && - (OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode); - } - protected: Rect mLocalBounds; // displayed area in LOCAL coord. doesn't incorporate stroke, so check paint }; @@ -275,7 +274,7 @@ public: renderer.save(mFlags); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Save flags %x", mFlags); } @@ -309,7 +308,7 @@ public: renderer.restoreToCount(saveCount + mCount); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Restore to count %d", mCount); } @@ -348,7 +347,7 @@ public: renderer.saveLayer(mArea.left, mArea.top, mArea.right, mArea.bottom, mAlpha, mMode, mFlags); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("SaveLayer%s of area " RECT_STRING, (isSaveLayerAlpha() ? "Alpha" : ""),RECT_ARGS(mArea)); } @@ -369,7 +368,7 @@ private: return this; } - bool isSaveLayerAlpha() { return mAlpha < 255 && mMode == SkXfermode::kSrcOver_Mode; } + bool isSaveLayerAlpha() const { return mAlpha < 255 && mMode == SkXfermode::kSrcOver_Mode; } Rect mArea; int mAlpha; SkXfermode::Mode mMode; @@ -385,7 +384,7 @@ public: renderer.translate(mDx, mDy); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Translate by %f %f", mDx, mDy); } @@ -405,7 +404,7 @@ public: renderer.rotate(mDegrees); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Rotate by %f degrees", mDegrees); } @@ -424,7 +423,7 @@ public: renderer.scale(mSx, mSy); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Scale by %f %f", mSx, mSy); } @@ -444,7 +443,7 @@ public: renderer.skew(mSx, mSy); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Skew by %f %f", mSx, mSy); } @@ -464,8 +463,12 @@ public: renderer.setMatrix(mMatrix); } - virtual void output(int level, uint32_t logFlags) { - OP_LOG("SetMatrix " MATRIX_STRING, MATRIX_ARGS(mMatrix)); + virtual void output(int level, uint32_t logFlags) const { + if (mMatrix) { + OP_LOG("SetMatrix " MATRIX_STRING, MATRIX_ARGS(mMatrix)); + } else { + OP_LOGS("SetMatrix (reset)"); + } } virtual const char* name() { return "SetMatrix"; } @@ -483,7 +486,7 @@ public: renderer.concatMatrix(mMatrix); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("ConcatMatrix " MATRIX_STRING, MATRIX_ARGS(mMatrix)); } @@ -527,7 +530,7 @@ public: renderer.clipRect(mArea.left, mArea.top, mArea.right, mArea.bottom, mOp); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("ClipRect " RECT_STRING, RECT_ARGS(mArea)); } @@ -556,7 +559,7 @@ public: renderer.clipPath(mPath, mOp); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { SkRect bounds = mPath->getBounds(); OP_LOG("ClipPath bounds " RECT_STRING, bounds.left(), bounds.top(), bounds.right(), bounds.bottom()); @@ -577,7 +580,7 @@ public: renderer.clipRegion(mRegion, mOp); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { SkIRect bounds = mRegion->getBounds(); OP_LOG("ClipRegion bounds %d %d %d %d", bounds.left(), bounds.top(), bounds.right(), bounds.bottom()); @@ -587,7 +590,6 @@ public: private: SkRegion* mRegion; - SkRegion::Op mOp; }; class ResetShaderOp : public StateOp { @@ -596,7 +598,7 @@ public: renderer.resetShader(); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOGS("ResetShader"); } @@ -611,7 +613,7 @@ public: renderer.setupShader(mShader); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("SetupShader, shader %p", mShader); } @@ -627,7 +629,7 @@ public: renderer.resetColorFilter(); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOGS("ResetColorFilter"); } @@ -643,7 +645,7 @@ public: renderer.setupColorFilter(mColorFilter); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("SetupColorFilter, filter %p", mColorFilter); } @@ -659,7 +661,7 @@ public: renderer.resetShadow(); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOGS("ResetShadow"); } @@ -675,7 +677,7 @@ public: renderer.setupShadow(mRadius, mDx, mDy, mColor); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("SetupShadow, radius %f, %f, %f, color %#x", mRadius, mDx, mDy, mColor); } @@ -694,7 +696,7 @@ public: renderer.resetPaintFilter(); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOGS("ResetPaintFilter"); } @@ -710,7 +712,7 @@ public: renderer.setupPaintFilter(mClearBits, mSetBits); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("SetupPaintFilter, clear %#x, set %#x", mClearBits, mSetBits); } @@ -721,7 +723,6 @@ private: int mSetBits; }; - /////////////////////////////////////////////////////////////////////////////// // DRAW OPERATIONS - these are operations that can draw to the canvas's device /////////////////////////////////////////////////////////////////////////////// @@ -729,34 +730,63 @@ 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), mAtlas(Caches::getInstance().assetAtlas) { + mEntry = mAtlas.getEntry(bitmap); + if (mEntry) { + mEntryGenerationId = mAtlas.getGenerationId(); + mUvMapper = mEntry->uvMapper; + } + } virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawBitmap(mBitmap, mLocalBounds.left, mLocalBounds.top, getPaint(renderer)); } + AssetAtlas::Entry* getAtlasEntry() { + // The atlas entry is stale, let's get a new one + if (mEntry && mEntryGenerationId != mAtlas.getGenerationId()) { + mEntryGenerationId = mAtlas.getGenerationId(); + mEntry = mAtlas.getEntry(mBitmap); + mUvMapper = mEntry->uvMapper; + } + return mEntry; + } + #define SET_TEXTURE(ptr, posRect, offsetRect, texCoordsRect, xDim, yDim) \ TextureVertex::set(ptr++, posRect.xDim - offsetRect.left, posRect.yDim - offsetRect.top, \ texCoordsRect.xDim, texCoordsRect.yDim) + /** + * This multi-draw operation builds a mesh on the stack by generating a quad + * for each bitmap in the batch. This method is also responsible for dirtying + * the current layer, if any. + */ virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty, - const Vector<DrawOp*>& ops, const Rect& bounds) { - renderer.restoreDisplayState(state, true); // restore all but the clip - renderer.setFullScreenClip(); // ensure merged ops aren't clipped + const Vector<OpStatePair>& ops, const Rect& bounds) { + const DeferredDisplayState& firstState = *(ops[0].state); + renderer.restoreDisplayState(firstState, true); // restore all but the clip + 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); + const bool hasLayer = renderer.hasLayer(); + bool pureTranslate = true; - 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; + const DeferredDisplayState& state = *(ops[i].state); + const Rect& opBounds = 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 + pureTranslate &= state.mMatrix.isPureTranslate(); + + Rect texCoords(0, 0, 1, 1); + ((DrawBitmapOp*) ops[i].op)->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); @@ -764,29 +794,45 @@ public: SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, bottom); SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, top); SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, bottom); + + if (hasLayer) { + renderer.dirtyLayer(opBounds.left, opBounds.top, opBounds.right, opBounds.bottom); + } } - return renderer.drawBitmaps(mBitmap, ops.size(), &vertices[0], bounds, mPaint); + return renderer.drawBitmaps(mBitmap, mEntry, ops.size(), &vertices[0], + pureTranslate, bounds, mPaint); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw bitmap %p at %f %f", mBitmap, mLocalBounds.left, mLocalBounds.top); } virtual const char* name() { return "DrawBitmap"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Bitmap; - *mergeId = (mergeid_t)mBitmap; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Bitmap; + deferInfo.mergeId = getAtlasEntry() ? + (mergeid_t) mEntry->getMergeId() : (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); + // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation + // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in + // MergingDrawBatch::canMergeWith() + // TODO: support clipped bitmaps by handling them in SET_TEXTURE + deferInfo.mergeable = state.mMatrix.isSimple() && state.mMatrix.positiveScale() && + !state.mClipSideFlags && + OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode && + (mBitmap->getConfig() != SkBitmap::kA8_Config); } const SkBitmap* bitmap() { return mBitmap; } protected: SkBitmap* mBitmap; + const AssetAtlas& mAtlas; + uint32_t mEntryGenerationId; + AssetAtlas::Entry* mEntry; + UvMapper mUvMapper; }; class DrawBitmapMatrixOp : public DrawBoundedOp { @@ -802,15 +848,15 @@ public: return renderer.drawBitmap(mBitmap, mMatrix, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw bitmap %p matrix " MATRIX_STRING, mBitmap, MATRIX_ARGS(mMatrix)); } virtual const char* name() { return "DrawBitmapMatrix"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Bitmap; - return false; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Bitmap; } private: @@ -831,16 +877,16 @@ public: getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw bitmap %p src="RECT_STRING", dst="RECT_STRING, mBitmap, RECT_ARGS(mSrc), RECT_ARGS(mLocalBounds)); } virtual const char* name() { return "DrawBitmapRect"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Bitmap; - return false; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Bitmap; } private: @@ -858,15 +904,15 @@ public: mLocalBounds.top, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw bitmap %p", mBitmap); } virtual const char* name() { return "DrawBitmapData"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Bitmap; - return false; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Bitmap; } }; @@ -883,15 +929,15 @@ public: mVertices, mColors, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw bitmap %p mesh %d x %d", mBitmap, mMeshWidth, mMeshHeight); } virtual const char* name() { return "DrawBitmapMesh"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Bitmap; - return false; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Bitmap; } private: @@ -904,45 +950,148 @@ 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) - : DrawBoundedOp(left, top, right, bottom, 0), - mBitmap(bitmap), mxDivs(xDivs), myDivs(yDivs), - mColors(colors), mxDivsCount(width), myDivsCount(height), - mNumColors(numColors), mAlpha(alpha), mMode(mode) {}; + DrawPatchOp(SkBitmap* bitmap, Res_png_9patch* patch, + float left, float top, float right, float bottom, SkPaint* paint) + : DrawBoundedOp(left, top, right, bottom, paint), + mBitmap(bitmap), mPatch(patch), mGenerationId(0), mMesh(NULL), + mAtlas(Caches::getInstance().assetAtlas) { + mEntry = mAtlas.getEntry(bitmap); + if (mEntry) { + mEntryGenerationId = mAtlas.getGenerationId(); + } + }; + + AssetAtlas::Entry* getAtlasEntry() { + // The atlas entry is stale, let's get a new one + if (mEntry && mEntryGenerationId != mAtlas.getGenerationId()) { + mEntryGenerationId = mAtlas.getGenerationId(); + mEntry = mAtlas.getEntry(mBitmap); + } + return mEntry; + } + + const Patch* getMesh(OpenGLRenderer& renderer) { + if (!mMesh || renderer.getCaches().patchCache.getGenerationId() != mGenerationId) { + PatchCache& cache = renderer.getCaches().patchCache; + mMesh = cache.get(getAtlasEntry(), mBitmap->width(), mBitmap->height(), + mLocalBounds.getWidth(), mLocalBounds.getHeight(), mPatch); + mGenerationId = cache.getGenerationId(); + } + return mMesh; + } + + /** + * This multi-draw operation builds an indexed mesh on the stack by copying + * and transforming the vertices of each 9-patch in the batch. This method + * is also responsible for dirtying the current layer, if any. + */ + virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty, + const Vector<OpStatePair>& ops, const Rect& bounds) { + const DeferredDisplayState& firstState = *(ops[0].state); + renderer.restoreDisplayState(firstState, true); // restore all but the clip + + // Batches will usually contain a small number of items so it's + // worth performing a first iteration to count the exact number + // of vertices we need in the new mesh + uint32_t totalVertices = 0; + for (unsigned int i = 0; i < ops.size(); i++) { + totalVertices += ((DrawPatchOp*) ops[i].op)->getMesh(renderer)->verticesCount; + } + + const bool hasLayer = renderer.hasLayer(); + + uint32_t indexCount = 0; + + TextureVertex vertices[totalVertices]; + TextureVertex* vertex = &vertices[0]; + + // Create a mesh that contains the transformed vertices for all the + // 9-patch objects that are part of the batch. Note that onDefer() + // enforces ops drawn by this function to have a pure translate or + // identity matrix + for (unsigned int i = 0; i < ops.size(); i++) { + DrawPatchOp* patchOp = (DrawPatchOp*) ops[i].op; + const DeferredDisplayState* state = ops[i].state; + const Patch* opMesh = patchOp->getMesh(renderer); + uint32_t vertexCount = opMesh->verticesCount; + if (vertexCount == 0) continue; + + // We use the bounds to know where to translate our vertices + // Using patchOp->state.mBounds wouldn't work because these + // bounds are clipped + const float tx = (int) floorf(state->mMatrix.getTranslateX() + + patchOp->mLocalBounds.left + 0.5f); + const float ty = (int) floorf(state->mMatrix.getTranslateY() + + patchOp->mLocalBounds.top + 0.5f); + + // Copy & transform all the vertices for the current operation + TextureVertex* opVertices = opMesh->vertices; + for (uint32_t j = 0; j < vertexCount; j++, opVertices++) { + TextureVertex::set(vertex++, + opVertices->position[0] + tx, opVertices->position[1] + ty, + opVertices->texture[0], opVertices->texture[1]); + } + + // Dirty the current layer if possible. When the 9-patch does not + // contain empty quads we can take a shortcut and simply set the + // dirty rect to the object's bounds. + if (hasLayer) { + if (!opMesh->hasEmptyQuads) { + renderer.dirtyLayer(tx, ty, + tx + patchOp->mLocalBounds.getWidth(), + ty + patchOp->mLocalBounds.getHeight()); + } else { + const size_t count = opMesh->quads.size(); + for (size_t i = 0; i < count; i++) { + const Rect& quadBounds = opMesh->quads[i]; + const float x = tx + quadBounds.left; + const float y = ty + quadBounds.top; + renderer.dirtyLayer(x, y, + x + quadBounds.getWidth(), y + quadBounds.getHeight()); + } + } + } + + indexCount += opMesh->indexCount; + } + + return renderer.drawPatches(mBitmap, getAtlasEntry(), + &vertices[0], indexCount, getPaint(renderer)); + } 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, - mLocalBounds.right, mLocalBounds.bottom, mAlpha, mMode); + // 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, getMesh(renderer), getAtlasEntry(), + mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom, + getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw patch "RECT_STRING, RECT_ARGS(mLocalBounds)); } virtual const char* name() { return "DrawPatch"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Patch; - *mergeId = (mergeid_t)mBitmap; - return true; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Patch; + deferInfo.mergeId = getAtlasEntry() ? (mergeid_t) mEntry->getMergeId() : (mergeid_t) mBitmap; + deferInfo.mergeable = state.mMatrix.isPureTranslate() && + OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; + deferInfo.opaqueOverBounds = isOpaqueOverBounds(state) && mBitmap->isOpaque(); } private: SkBitmap* mBitmap; - const int32_t* mxDivs; - const int32_t* myDivs; - const uint32_t* mColors; - uint32_t mxDivsCount; - uint32_t myDivsCount; - int8_t mNumColors; - int mAlpha; - SkXfermode::Mode mMode; + Res_png_9patch* mPatch; + + uint32_t mGenerationId; + const Patch* mMesh; + + const AssetAtlas& mAtlas; + uint32_t mEntryGenerationId; + AssetAtlas::Entry* mEntry; }; class DrawColorOp : public DrawOp { @@ -954,7 +1103,7 @@ public: return renderer.drawColor(mColor, mMode); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw color %#x, mode %d", mColor, mMode); } @@ -970,7 +1119,7 @@ public: DrawStrokableOp(float left, float top, float right, float bottom, SkPaint* paint) : DrawBoundedOp(left, top, right, bottom, paint) {}; - bool getLocalBounds(Rect& localBounds) { + bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) { localBounds.set(mLocalBounds); if (mPaint && mPaint->getStyle() != SkPaint::kFill_Style) { localBounds.outset(strokeWidthOutset()); @@ -978,15 +1127,15 @@ public: return true; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { if (mPaint->getPathEffect()) { - *batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture; + deferInfo.batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture; } else { - *batchId = mPaint->isAntiAlias() ? + deferInfo.batchId = mPaint->isAntiAlias() ? DeferredDisplayList::kOpBatch_AlphaVertices : DeferredDisplayList::kOpBatch_Vertices; } - return false; } }; @@ -1000,10 +1149,17 @@ public: mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Rect "RECT_STRING, RECT_ARGS(mLocalBounds)); } + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + DrawStrokableOp::onDefer(renderer, deferInfo, state); + deferInfo.opaqueOverBounds = isOpaqueOverBounds(state) && + mPaint->getStyle() == SkPaint::kFill_Style; + } + virtual const char* name() { return "DrawRect"; } }; @@ -1017,15 +1173,15 @@ public: return renderer.drawRects(mRects, mCount, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Rects count %d", mCount); } virtual const char* name() { return "DrawRects"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Vertices; - return false; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Vertices; } private: @@ -1044,7 +1200,7 @@ public: mLocalBounds.right, mLocalBounds.bottom, mRx, mRy, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw RoundRect "RECT_STRING", rx %f, ry %f", RECT_ARGS(mLocalBounds), mRx, mRy); } @@ -1065,7 +1221,7 @@ public: return renderer.drawCircle(mX, mY, mRadius, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Circle x %f, y %f, r %f", mX, mY, mRadius); } @@ -1087,7 +1243,7 @@ public: mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Oval "RECT_STRING, RECT_ARGS(mLocalBounds)); } @@ -1107,7 +1263,7 @@ public: mStartAngle, mSweepAngle, mUseCenter, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Arc "RECT_STRING", start %f, sweep %f, useCenter %d", RECT_ARGS(mLocalBounds), mStartAngle, mSweepAngle, mUseCenter); } @@ -1136,15 +1292,15 @@ public: return renderer.drawPath(mPath, getPaint(renderer)); } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { SkPaint* paint = getPaint(renderer); renderer.getCaches().pathCache.precache(mPath, paint); - *batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture; - return false; + deferInfo.batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture; } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Path %p in "RECT_STRING, mPath, RECT_ARGS(mLocalBounds)); } @@ -1166,17 +1322,17 @@ public: return renderer.drawLines(mPoints, mCount, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Lines count %d", mCount); } virtual const char* name() { return "DrawLines"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = mPaint->isAntiAlias() ? + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = mPaint->isAntiAlias() ? DeferredDisplayList::kOpBatch_AlphaVertices : DeferredDisplayList::kOpBatch_Vertices; - return false; } protected: @@ -1193,7 +1349,7 @@ public: return renderer.drawPoints(mPoints, mCount, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Points count %d", mCount); } @@ -1205,20 +1361,19 @@ public: DrawSomeTextOp(const char* text, int bytesCount, int count, SkPaint* paint) : DrawOp(paint), mText(text), mBytesCount(bytesCount), mCount(count) {}; - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw some text, %d bytes", mBytesCount); } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { SkPaint* paint = getPaint(renderer); FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint); fontRenderer.precache(paint, mText, mCount, mat4::identity()); - *batchId = mPaint->getColor() == 0xff000000 ? + deferInfo.batchId = mPaint->getColor() == 0xff000000 ? DeferredDisplayList::kOpBatch_Text : DeferredDisplayList::kOpBatch_ColorText; - - return false; } protected: @@ -1270,27 +1425,14 @@ 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) { memset(&mPrecacheTransform.data[0], 0xff, 16 * sizeof(float)); } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { SkPaint* paint = getPaint(renderer); FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint); const mat4& transform = renderer.findBestFontTransform(state.mMatrix); @@ -1298,39 +1440,45 @@ public: fontRenderer.precache(paint, mText, mCount, transform); mPrecacheTransform = transform; } - *batchId = mPaint->getColor() == 0xff000000 ? + deferInfo.batchId = mPaint->getColor() == 0xff000000 ? DeferredDisplayList::kOpBatch_Text : DeferredDisplayList::kOpBatch_ColorText; - *mergeId = (mergeid_t)mPaint->getColor(); + deferInfo.mergeId = (mergeid_t)mPaint->getColor(); // don't merge decorated text - the decorations won't draw in order bool noDecorations = !(mPaint->getFlags() & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)); - return mergeAllowed() && noDecorations; + deferInfo.mergeable = state.mMatrix.isPureTranslate() && noDecorations && + OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; } virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { + Rect bounds; + getLocalBounds(renderer.getDrawModifiers(), bounds); return renderer.drawText(mText, mBytesCount, mCount, mX, mY, - mPositions, getPaint(renderer), mLength); + mPositions, getPaint(renderer), mTotalAdvance, bounds); } virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty, - const Vector<DrawOp*>& ops, const Rect& bounds) { + const Vector<OpStatePair>& ops, const Rect& bounds) { status_t status = DrawGlInfo::kStatusDone; - renderer.setFullScreenClip(); // ensure merged ops aren't clipped for (unsigned int i = 0; i < ops.size(); i++) { + const DeferredDisplayState& state = *(ops[i].state); DrawOpMode drawOpMode = (i == ops.size() - 1) ? kDrawOpMode_Flush : kDrawOpMode_Defer; - renderer.restoreDisplayState(ops[i]->state, true); // restore all but the clip + renderer.restoreDisplayState(state, true); // restore all but the clip - DrawTextOp& op = *((DrawTextOp*)ops[i]); + DrawTextOp& op = *((DrawTextOp*)ops[i].op); + // quickReject() will not occure in drawText() so we can use mLocalBounds + // directly, we do not need to account for shadow by calling getLocalBounds() 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; } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Text of count %d, bytes %d", mCount, mBytesCount); } @@ -1343,7 +1491,7 @@ private: float mX; float mY; const float* mPositions; - float mLength; + float mTotalAdvance; mat4 mPrecacheTransform; }; @@ -1363,7 +1511,7 @@ public: return ret; } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Functor %p", mFunctor); } @@ -1397,7 +1545,7 @@ public: return DrawGlInfo::kStatusDone; } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Display List %p, flags %#x", mDisplayList, mFlags); if (mDisplayList && (logFlags & kOpLogFlag_Recurse)) { mDisplayList->output(level + 1); @@ -1420,7 +1568,7 @@ public: return renderer.drawLayer(mLayer, mX, mY); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Layer %p at %f %f", mLayer, mX, mY); } diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 876c38a..8866029 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -57,6 +57,10 @@ void DisplayListRenderer::reset() { mCaches.resourceCache.decrementRefcountLocked(mFilterResources.itemAt(i)); } + for (size_t i = 0; i < mPatchResources.size(); i++) { + mCaches.resourceCache.decrementRefcountLocked(mPatchResources.itemAt(i)); + } + for (size_t i = 0; i < mShaders.size(); i++) { mCaches.resourceCache.decrementRefcountLocked(mShaders.itemAt(i)); } @@ -74,6 +78,7 @@ void DisplayListRenderer::reset() { mBitmapResources.clear(); mOwnedBitmapResources.clear(); mFilterResources.clear(); + mPatchResources.clear(); mSourcePaths.clear(); mShaders.clear(); @@ -313,20 +318,13 @@ 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) { - int alpha; - SkXfermode::Mode mode; - OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode); - +status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, + float left, float top, float right, float bottom, SkPaint* paint) { bitmap = refBitmap(bitmap); - xDivs = refBuffer<int>(xDivs, width); - yDivs = refBuffer<int>(yDivs, height); - colors = refBuffer<uint32_t>(colors, numColors); + patch = refPatch(patch); + paint = refPaint(paint); - 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, paint)); return DrawGlInfo::kStatusDone; } @@ -423,17 +421,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; } @@ -467,10 +464,12 @@ void DisplayListRenderer::setupColorFilter(SkiaColorFilter* filter) { void DisplayListRenderer::resetShadow() { addStateOp(new (alloc()) ResetShadowOp()); + OpenGLRenderer::resetShadow(); } void DisplayListRenderer::setupShadow(float radius, float dx, float dy, int color) { addStateOp(new (alloc()) SetupShadowOp(radius, dx, dy, color)); + OpenGLRenderer::setupShadow(radius, dx, dy, color); } void DisplayListRenderer::resetPaintFilter() { @@ -506,11 +505,12 @@ void DisplayListRenderer::addStateOp(StateOp* op) { void DisplayListRenderer::addDrawOp(DrawOp* op) { Rect localBounds; - if (op->getLocalBounds(localBounds)) { + if (op->getLocalBounds(mDrawModifiers, localBounds)) { bool rejected = quickRejectNoScissor(localBounds.left, localBounds.top, localBounds.right, localBounds.bottom); op->setQuickRejected(rejected); } + mHasDrawOps = true; addOpInternal(op); } diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index 75abad6..d233150 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); @@ -156,6 +156,10 @@ public: return mFilterResources; } + const Vector<Res_png_9patch*>& getPatchResources() const { + return mPatchResources; + } + const Vector<SkiaShader*>& getShaders() const { return mShaders; } @@ -265,11 +269,14 @@ private: } inline SkMatrix* refMatrix(SkMatrix* matrix) { - // Copying the matrix is cheap and prevents against the user changing the original - // matrix before the operation that uses it - SkMatrix* copy = new SkMatrix(*matrix); - mMatrices.add(copy); - return copy; + if (matrix) { + // Copying the matrix is cheap and prevents against the user changing + // the original matrix before the operation that uses it + SkMatrix* copy = new SkMatrix(*matrix); + mMatrices.add(copy); + return copy; + } + return matrix; } inline Layer* refLayer(Layer* layer) { @@ -315,9 +322,16 @@ private: return colorFilter; } + inline Res_png_9patch* refPatch(Res_png_9patch* patch) { + mPatchResources.add(patch); + mCaches.resourceCache.incrementRefcount(patch); + return patch; + } + Vector<SkBitmap*> mBitmapResources; Vector<SkBitmap*> mOwnedBitmapResources; Vector<SkiaColorFilter*> mFilterResources; + Vector<Res_png_9patch*> mPatchResources; Vector<SkPaint*> mPaints; DefaultKeyedVector<SkPaint*, SkPaint*> mPaintMap; diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp index 19b3849..77006a4 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; + bool useFloatTexture = Extensions::getInstance().hasFloatTextures(); 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..84e7e65 100644 --- a/libs/hwui/Extensions.cpp +++ b/libs/hwui/Extensions.cpp @@ -14,8 +14,19 @@ * 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" +#include "Properties.h" namespace android { @@ -40,33 +51,28 @@ 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); + + char property[PROPERTY_VALUE_MAX]; + if (property_get(PROPERTY_DEBUG_NV_PROFILING, property, NULL) > 0) { + mHasNvSystemTime = !strcmp(property, "true") && hasEglExtension("EGL_NV_system_time"); + } else { + mHasNvSystemTime = false; + } 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: @@ -80,7 +86,7 @@ Extensions::Extensions(): Singleton<Extensions>() { // or more digits. The release number and vendor specific information are // optional." - if (sscanf(version, "OpenGL ES %d.%d", &mVersionMajor, &mVersionMinor) !=2) { + if (sscanf(version, "OpenGL ES %d.%d", &mVersionMajor, &mVersionMinor) != 2) { // If we cannot parse the version number, assume OpenGL ES 2.0 mVersionMajor = 2; mVersionMinor = 0; @@ -88,22 +94,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 mGlExtensionList.indexOf(s) >= 0; +} + +bool Extensions::hasEglExtension(const char* extension) const { const String8 s(extension); - return mExtensionList.indexOf(s) >= 0; + 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..25d4c5e 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,30 @@ 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 bool hasUnpackRowLength() const { return mVersionMajor >= 3; } + inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; } + inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; } + inline bool hasFloatTextures() const { return mVersionMajor >= 3; } 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 +73,7 @@ private: bool mHasTiledRendering; bool mHas1BitStencil; bool mHas4BitStencil; + bool mHasNvSystemTime; int mVersionMajor; int mVersionMinor; diff --git a/libs/hwui/Fence.h b/libs/hwui/Fence.h new file mode 100644 index 0000000..f175e98 --- /dev/null +++ b/libs/hwui/Fence.h @@ -0,0 +1,113 @@ +/* + * 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_FENCE_H +#define ANDROID_HWUI_FENCE_H + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +namespace android { +namespace uirenderer { + +/** + * Creating a Fence instance inserts a new sync fence in the OpenGL + * commands stream. The caller can then wait for the fence to be signaled + * by calling the wait method. + */ +class Fence { +public: + enum { + /** + * Default timeout in nano-seconds for wait() + */ + kDefaultTimeout = 1000000000 + }; + + /** + * Inserts a new sync fence in the OpenGL commands stream. + */ + Fence() { + mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (mDisplay != EGL_NO_DISPLAY) { + mFence = eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, NULL); + } else { + mFence = EGL_NO_SYNC_KHR; + } + } + + /** + * Destroys the fence. Any caller waiting on the fence will be + * signaled immediately. + */ + ~Fence() { + if (mFence != EGL_NO_SYNC_KHR) { + eglDestroySyncKHR(mDisplay, mFence); + } + } + + /** + * Blocks the calling thread until this fence is signaled, or until + * <timeout> nanoseconds have passed. + * + * Returns true if waiting for the fence was successful, false if + * a timeout or an error occurred. + */ + bool wait(EGLTimeKHR timeout = kDefaultTimeout) { + EGLint waitStatus = eglClientWaitSyncKHR(mDisplay, mFence, + EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, timeout); + if (waitStatus == EGL_FALSE) { + ALOGW("Failed to wait for the fence %#x", eglGetError()); + } + return waitStatus == EGL_CONDITION_SATISFIED_KHR; + } + +private: + EGLDisplay mDisplay; + EGLSyncKHR mFence; + +}; // class Fence + +/** + * An AutoFence creates a Fence instance and waits for the fence + * to be signaled when the AutoFence is destroyed. This is useful + * to automatically wait for a series of OpenGL commands to be + * executed. For example: + * + * void drawAndWait() { + * glDrawElements(); + * AutoFence fence; + * } + */ +class AutoFence { +public: + AutoFence(EGLTimeKHR timeout = Fence::kDefaultTimeout): mTimeout(timeout) { + } + + ~AutoFence() { + mFence.wait(mTimeout); + } + +private: + EGLTimeKHR mTimeout; + Fence mFence; + +}; // class AutoFence + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_FENCE_H diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 543cfa2..00e7870 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -21,10 +21,11 @@ #include <cutils/properties.h> -#include <utils/Functor.h> #include <utils/Log.h> +#ifdef ANDROID_ENABLE_RENDERSCRIPT #include <RenderScript.h> +#endif #include "utils/Blur.h" #include "utils/Timing.h" @@ -33,6 +34,7 @@ #include "Debug.h" #include "Extensions.h" #include "FontRenderer.h" +#include "OpenGLRenderer.h" #include "PixelBuffer.h" #include "Rect.h" @@ -43,6 +45,52 @@ namespace uirenderer { #define RS_MIN_INPUT_CUTOFF 10000 /////////////////////////////////////////////////////////////////////////////// +// TextSetupFunctor +/////////////////////////////////////////////////////////////////////////////// +status_t TextSetupFunctor::operator ()(int what, void* data) { + Data* typedData = reinterpret_cast<Data*>(data); + GLenum glyphFormat = typedData ? typedData->glyphFormat : GL_ALPHA; + + renderer->setupDraw(); + renderer->setupDrawTextGamma(paint); + renderer->setupDrawDirtyRegionsDisabled(); + renderer->setupDrawWithTexture(glyphFormat == GL_ALPHA); + switch (glyphFormat) { + case GL_ALPHA: { + renderer->setupDrawAlpha8Color(paint->getColor(), alpha); + break; + } + case GL_RGBA: { + float floatAlpha = alpha / 255.0f; + renderer->setupDrawColor(floatAlpha, floatAlpha, floatAlpha, floatAlpha); + break; + } + default: { +#if DEBUG_FONT_RENDERER + ALOGD("TextSetupFunctor: called with unknown glyph format %x", glyphFormat); +#endif + break; + } + } + renderer->setupDrawColorFilter(); + renderer->setupDrawShader(); + renderer->setupDrawBlending(true, mode); + renderer->setupDrawProgram(); + renderer->setupDrawModelView(x, y, x, y, pureTranslate, true); + // Calling setupDrawTexture with the name 0 will enable the + // uv attributes and increase the texture unit count + // texture binding will be performed by the font renderer as + // needed + renderer->setupDrawTexture(0); + renderer->setupDrawPureColorUniforms(); + renderer->setupDrawColorFilterUniforms(); + renderer->setupDrawShaderUniforms(pureTranslate); + renderer->setupDrawTextGammaUniforms(); + + return NO_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////// // FontRenderer /////////////////////////////////////////////////////////////////////////////// @@ -62,8 +110,6 @@ FontRenderer::FontRenderer() : mLinearFiltering = false; - mIndexBufferID = 0; - mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH; mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT; mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH; @@ -103,17 +149,16 @@ FontRenderer::FontRenderer() : sLogFontRendererCreate = false; } -FontRenderer::~FontRenderer() { - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - delete mCacheTextures[i]; +void clearCacheTextures(Vector<CacheTexture*>& cacheTextures) { + for (uint32_t i = 0; i < cacheTextures.size(); i++) { + delete cacheTextures[i]; } - mCacheTextures.clear(); + cacheTextures.clear(); +} - if (mInitialized) { - // Unbinding the buffer shouldn't be necessary but it crashes with some drivers - Caches::getInstance().unbindIndicesBuffer(); - glDeleteBuffers(1, &mIndexBufferID); - } +FontRenderer::~FontRenderer() { + clearCacheTextures(mACacheTextures); + clearCacheTextures(mRGBACacheTextures); LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); while (it.next()) { @@ -130,15 +175,19 @@ void FontRenderer::flushAllAndInvalidate() { it.value()->invalidateTextureCache(); } - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - mCacheTextures[i]->init(); + for (uint32_t i = 0; i < mACacheTextures.size(); i++) { + mACacheTextures[i]->init(); + } + + for (uint32_t i = 0; i < mRGBACacheTextures.size(); i++) { + mRGBACacheTextures[i]->init(); } } -void FontRenderer::flushLargeCaches() { +void FontRenderer::flushLargeCaches(Vector<CacheTexture*>& cacheTextures) { // Start from 1; don't deallocate smallest/default texture - for (uint32_t i = 1; i < mCacheTextures.size(); i++) { - CacheTexture* cacheTexture = mCacheTextures[i]; + for (uint32_t i = 1; i < cacheTextures.size(); i++) { + CacheTexture* cacheTexture = cacheTextures[i]; if (cacheTexture->getPixelBuffer()) { cacheTexture->init(); LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); @@ -150,11 +199,16 @@ void FontRenderer::flushLargeCaches() { } } -CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph, - uint32_t* startX, uint32_t* startY) { - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) { - return mCacheTextures[i]; +void FontRenderer::flushLargeCaches() { + flushLargeCaches(mACacheTextures); + flushLargeCaches(mRGBACacheTextures); +} + +CacheTexture* FontRenderer::cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures, + const SkGlyph& glyph, uint32_t* startX, uint32_t* startY) { + for (uint32_t i = 0; i < cacheTextures.size(); i++) { + if (cacheTextures[i]->fitBitmap(glyph, startX, startY)) { + return cacheTextures[i]; } } // Could not fit glyph into current cache textures @@ -175,9 +229,27 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp cachedGlyph->mIsValid = false; + // choose an appropriate cache texture list for this glyph format + SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat); + Vector<CacheTexture*>* cacheTextures = NULL; + switch (format) { + case SkMask::kA8_Format: + case SkMask::kBW_Format: + cacheTextures = &mACacheTextures; + break; + case SkMask::kARGB32_Format: + cacheTextures = &mRGBACacheTextures; + break; + default: +#if DEBUG_FONT_RENDERER + ALOGD("getCacheTexturesForFormat: unknown SkMask format %x", format); +#endif + return; + } + // If the glyph is too tall, don't cache it if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > - mCacheTextures[mCacheTextures.size() - 1]->getHeight()) { + (*cacheTextures)[cacheTextures->size() - 1]->getHeight()) { ALOGE("Font size too large to fit in cache. width, height = %i, %i", (int) glyph.fWidth, (int) glyph.fHeight); return; @@ -187,14 +259,14 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp uint32_t startX = 0; uint32_t startY = 0; - CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); + CacheTexture* cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY); if (!cacheTexture) { if (!precaching) { // If the new glyph didn't fit and we are not just trying to precache it, // clear out the cache and try again flushAllAndInvalidate(); - cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); + cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY); } if (!cacheTexture) { @@ -222,24 +294,21 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp cacheTexture->allocateMesh(); } - // Tells us whether the glyphs is B&W (1 bit per pixel) - // or anti-aliased (8 bits per pixel) - SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat); - uint8_t* cacheBuffer = cacheTexture->getPixelBuffer()->map(); - uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; - - // Copy the glyph image, taking the mask format into account uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage; - int stride = glyph.rowBytes(); - - uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE; - memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); + int srcStride = glyph.rowBytes(); + // Copy the glyph image, taking the mask format into account switch (format) { case SkMask::kA8_Format: { + uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; + uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX + - TEXTURE_BORDER_SIZE; + // write leading border line + memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); + // write glyph data if (mGammaTable) { - for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) { + for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { row = cacheY * cacheWidth; cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { @@ -249,21 +318,55 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; } } else { - for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) { + for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { row = cacheY * cacheWidth; memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth); cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; } } + // write trailing border line + row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE; + memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); + break; + } + case SkMask::kARGB32_Format: { + // prep data lengths + const size_t formatSize = PixelBuffer::formatSize(GL_RGBA); + const size_t borderSize = formatSize * TEXTURE_BORDER_SIZE; + size_t rowSize = formatSize * glyph.fWidth; + // prep advances + size_t dstStride = formatSize * cacheWidth; + // prep indices + // - we actually start one row early, and then increment before first copy + uint8_t* src = &bitmapBuffer[0 - srcStride]; + uint8_t* dst = &cacheBuffer[cacheTexture->getOffset(startX, startY - 1)]; + uint8_t* dstEnd = &cacheBuffer[cacheTexture->getOffset(startX, endY - 1)]; + uint8_t* dstL = dst - borderSize; + uint8_t* dstR = dst + rowSize; + // write leading border line + memset(dstL, 0, rowSize + 2 * borderSize); + // write glyph data + while (dst < dstEnd) { + memset(dstL += dstStride, 0, borderSize); // leading border column + memcpy(dst += dstStride, src += srcStride, rowSize); // glyph data + memset(dstR += dstStride, 0, borderSize); // trailing border column + } + // write trailing border line + memset(dstL += dstStride, 0, rowSize + 2 * borderSize); break; } case SkMask::kBW_Format: { + uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; + uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX + - TEXTURE_BORDER_SIZE; static const uint8_t COLORS[2] = { 0, 255 }; - + // write leading border line + memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); + // write glyph data for (cacheY = startY; cacheY < endY; cacheY++) { cacheX = startX; - int rowBytes = stride; + int rowBytes = srcStride; uint8_t* buffer = bitmapBuffer; row = cacheY * cacheWidth; @@ -276,23 +379,24 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp } cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; - bitmapBuffer += stride; + bitmapBuffer += srcStride; } + // write trailing border line + row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE; + memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); break; } default: - ALOGW("Unkown glyph format: 0x%x", format); + ALOGW("Unknown glyph format: 0x%x", format); break; } - row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE; - memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); - cachedGlyph->mIsValid = true; } -CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) { - CacheTexture* cacheTexture = new CacheTexture(width, height, gMaxNumberOfQuads); +CacheTexture* FontRenderer::createCacheTexture(int width, int height, GLenum format, + bool allocate) { + CacheTexture* cacheTexture = new CacheTexture(width, height, format, gMaxNumberOfQuads); if (allocate) { Caches::getInstance().activeTexture(0); @@ -304,44 +408,23 @@ CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool alloc } void FontRenderer::initTextTexture() { - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - delete mCacheTextures[i]; - } - mCacheTextures.clear(); + clearCacheTextures(mACacheTextures); + clearCacheTextures(mRGBACacheTextures); mUploadTexture = false; - mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true)); - mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false)); - mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false)); - mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false)); - mCurrentCacheTexture = mCacheTextures[0]; -} - -// Avoid having to reallocate memory and render quad by quad -void FontRenderer::initVertexArrayBuffers() { - uint32_t numIndices = gMaxNumberOfQuads * 6; - uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t); - uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes); - - // Four verts, two triangles , six indices per quad - for (uint32_t i = 0; i < gMaxNumberOfQuads; i++) { - int i6 = i * 6; - int i4 = i * 4; - - indexBufferData[i6 + 0] = i4 + 0; - indexBufferData[i6 + 1] = i4 + 1; - indexBufferData[i6 + 2] = i4 + 2; - - indexBufferData[i6 + 3] = i4 + 0; - indexBufferData[i6 + 4] = i4 + 2; - indexBufferData[i6 + 5] = i4 + 3; - } - - glGenBuffers(1, &mIndexBufferID); - Caches::getInstance().bindIndicesBuffer(mIndexBufferID); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW); - - free(indexBufferData); + mACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, + GL_ALPHA, true)); + mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, + GL_ALPHA, false)); + mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, + GL_ALPHA, false)); + mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, + GL_ALPHA, false)); + mRGBACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, + GL_RGBA, false)); + mRGBACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, + GL_RGBA, false)); + mCurrentCacheTexture = mACacheTextures[0]; } // We don't want to allocate anything unless we actually draw text @@ -351,11 +434,28 @@ void FontRenderer::checkInit() { } initTextTexture(); - initVertexArrayBuffers(); mInitialized = true; } +void checkTextureUpdateForCache(Caches& caches, Vector<CacheTexture*>& cacheTextures, + bool& resetPixelStore, GLuint& lastTextureId) { + for (uint32_t i = 0; i < cacheTextures.size(); i++) { + CacheTexture* cacheTexture = cacheTextures[i]; + if (cacheTexture->isDirty() && cacheTexture->getPixelBuffer()) { + if (cacheTexture->getTextureId() != lastTextureId) { + lastTextureId = cacheTexture->getTextureId(); + caches.activeTexture(0); + caches.bindTexture(lastTextureId); + } + + if (cacheTexture->upload()) { + resetPixelStore = true; + } + } + } +} + void FontRenderer::checkTextureUpdate() { if (!mUploadTexture) { return; @@ -368,25 +468,8 @@ void FontRenderer::checkTextureUpdate() { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Iterate over all the cache textures and see which ones need to be updated - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - CacheTexture* cacheTexture = mCacheTextures[i]; - if (cacheTexture->isDirty() && cacheTexture->getPixelBuffer()) { - if (cacheTexture->getTextureId() != lastTextureId) { - lastTextureId = cacheTexture->getTextureId(); - caches.activeTexture(0); - glBindTexture(GL_TEXTURE_2D, lastTextureId); - } - - if (cacheTexture->upload()) { - resetPixelStore = true; - } - -#if DEBUG_FONT_RENDERER - ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d", - i, x, y, width, height); -#endif - } - } + checkTextureUpdateForCache(caches, mACacheTextures, resetPixelStore, lastTextureId); + checkTextureUpdateForCache(caches, mRGBACacheTextures, resetPixelStore, lastTextureId); // Unbind any PBO we might have used to update textures caches.unbindPixelBuffer(); @@ -400,21 +483,21 @@ void FontRenderer::checkTextureUpdate() { mUploadTexture = false; } -void FontRenderer::issueDrawCommand() { +void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) { + Caches& caches = Caches::getInstance(); bool first = true; bool force = false; - - GLuint lastId = 0; - Caches& caches = Caches::getInstance(); - - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - CacheTexture* texture = mCacheTextures[i]; + for (uint32_t i = 0; i < cacheTextures.size(); i++) { + CacheTexture* texture = cacheTextures[i]; if (texture->canDraw()) { if (first) { - if (mFunctor) (*mFunctor)(0, NULL); + if (mFunctor) { + TextSetupFunctor::Data functorData(texture->getFormat()); + (*mFunctor)(0, &functorData); + } checkTextureUpdate(); - caches.bindIndicesBuffer(mIndexBufferID); + caches.bindIndicesBuffer(); if (!mDrawn) { // If returns true, a VBO was bound and we must @@ -427,7 +510,7 @@ void FontRenderer::issueDrawCommand() { first = false; } - glBindTexture(GL_TEXTURE_2D, texture->getTextureId()); + caches.bindTexture(texture->getTextureId()); texture->setLinearFiltering(mLinearFiltering, false); TextureVertex* mesh = texture->mesh(); @@ -441,6 +524,11 @@ void FontRenderer::issueDrawCommand() { texture->resetMesh(); } } +} + +void FontRenderer::issueDrawCommand() { + issueDrawCommand(mACacheTextures); + issueDrawCommand(mRGBACacheTextures); mDrawn = true; } @@ -532,13 +620,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; @@ -611,13 +704,13 @@ bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *t bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path, - float hOffset, float vOffset, Rect* bounds) { + float hOffset, float vOffset, Rect* bounds, Functor* functor) { if (!mCurrentFont) { ALOGE("No font set"); return false; } - initRender(clip, bounds, NULL); + initRender(clip, bounds, functor); mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset); finishRender(); @@ -633,49 +726,55 @@ 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 == 0) { + mRs = new RSC::RS(); + if (!mRs->init(RSC::RS_INIT_LOW_LATENCY | RSC::RS_INIT_SYNCHRONOUS)) { + 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 = RSC::ScriptIntrinsicBlur::create(mRs, mRsElement); + } - delete[] gaussian; - delete[] scratch; - return; - } + RSC::sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0); + RSC::sp<RSC::Allocation> ain = RSC::Allocation::createTyped(mRs, t, + RS_ALLOCATION_MIPMAP_NONE, RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, + *image); + RSC::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->setInput(ain); + mRsScript->forEach(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 { +static uint32_t calculateCacheSize(const Vector<CacheTexture*>& cacheTextures) { uint32_t size = 0; - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - CacheTexture* cacheTexture = mCacheTextures[i]; + for (uint32_t i = 0; i < cacheTextures.size(); i++) { + CacheTexture* cacheTexture = cacheTextures[i]; if (cacheTexture && cacheTexture->getPixelBuffer()) { size += cacheTexture->getPixelBuffer()->getSize(); } @@ -683,5 +782,19 @@ uint32_t FontRenderer::getCacheSize() const { return size; } +uint32_t FontRenderer::getCacheSize(GLenum format) const { + switch (format) { + case GL_ALPHA: { + return calculateCacheSize(mACacheTextures); + } + case GL_RGBA: { + return calculateCacheSize(mRGBACacheTextures); + } + default: { + return 0; + } + } +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index 307a1d9..aa7e776 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -17,8 +17,10 @@ #ifndef ANDROID_HWUI_FONT_RENDERER_H #define ANDROID_HWUI_FONT_RENDERER_H +#include <utils/Functor.h> #include <utils/LruCache.h> #include <utils/Vector.h> +#include <utils/StrongPointer.h> #include <SkPaint.h> @@ -32,19 +34,55 @@ #include "Matrix.h" #include "Properties.h" +#ifdef ANDROID_ENABLE_RENDERSCRIPT +#include "RenderScript.h" namespace RSC { class Element; class RS; class ScriptIntrinsicBlur; + class sp; } +#endif class Functor; namespace android { namespace uirenderer { +class OpenGLRenderer; + /////////////////////////////////////////////////////////////////////////////// -// Renderer +// TextSetupFunctor +/////////////////////////////////////////////////////////////////////////////// +class TextSetupFunctor: public Functor { +public: + struct Data { + Data(GLenum glyphFormat) : glyphFormat(glyphFormat) { + } + + GLenum glyphFormat; + }; + + TextSetupFunctor(OpenGLRenderer* renderer, float x, float y, bool pureTranslate, + int alpha, SkXfermode::Mode mode, SkPaint* paint): Functor(), + renderer(renderer), x(x), y(y), pureTranslate(pureTranslate), + alpha(alpha), mode(mode), paint(paint) { + } + ~TextSetupFunctor() { } + + status_t operator ()(int what, void* data); + + OpenGLRenderer* renderer; + float x; + float y; + bool pureTranslate; + int alpha; + SkXfermode::Mode mode; + SkPaint* paint; +}; + +/////////////////////////////////////////////////////////////////////////////// +// FontRenderer /////////////////////////////////////////////////////////////////////////////// class FontRenderer { @@ -52,6 +90,7 @@ public: FontRenderer(); ~FontRenderer(); + void flushLargeCaches(Vector<CacheTexture*>& cacheTextures); void flushLargeCaches(); void setGammaTable(const uint8_t* gammaTable) { @@ -70,7 +109,8 @@ public: // bounds is an out parameter bool renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex, - uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds); + uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds, + Functor* functor); struct DropShadow { DropShadow() { }; @@ -97,30 +137,29 @@ public: mLinearFiltering = linearFiltering; } - uint32_t getCacheSize() const; + uint32_t getCacheSize(GLenum format) const; private: friend class Font; - static const uint32_t gMaxNumberOfQuads = 2048; - const uint8_t* mGammaTable; void allocateTextureMemory(CacheTexture* cacheTexture); void deallocateTextureMemory(CacheTexture* cacheTexture); void initTextTexture(); - CacheTexture* createCacheTexture(int width, int height, bool allocate); + CacheTexture* createCacheTexture(int width, int height, GLenum format, bool allocate); void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, uint32_t *retOriginX, uint32_t *retOriginY, bool precaching); - CacheTexture* cacheBitmapInTexture(const SkGlyph& glyph, uint32_t* startX, uint32_t* startY); + CacheTexture* cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures, const SkGlyph& glyph, + uint32_t* startX, uint32_t* startY); void flushAllAndInvalidate(); - void initVertexArrayBuffers(); void checkInit(); void initRender(const Rect* clip, Rect* bounds, Functor* functor); void finishRender(); + void issueDrawCommand(Vector<CacheTexture*>& cacheTextures); void issueDrawCommand(); void appendMeshQuadNoClip(float x1, float y1, float u1, float v1, float x2, float y2, float u2, float v2, @@ -148,7 +187,8 @@ private: uint32_t mLargeCacheWidth; uint32_t mLargeCacheHeight; - Vector<CacheTexture*> mCacheTextures; + Vector<CacheTexture*> mACacheTextures; + Vector<CacheTexture*> mRGBACacheTextures; Font* mCurrentFont; LruCache<Font::FontDescription, Font*> mActiveFonts; @@ -157,8 +197,6 @@ private: bool mUploadTexture; - uint32_t mIndexBufferID; - Functor* mFunctor; const Rect* mClip; Rect* mBounds; @@ -168,10 +206,12 @@ private: bool mLinearFiltering; +#ifdef ANDROID_ENABLE_RENDERSCRIPT // RS constructs - sp<RSC::RS> mRs; - sp<const RSC::Element> mRsElement; - sp<RSC::ScriptIntrinsicBlur> mRsScript; + RSC::sp<RSC::RS> mRs; + RSC::sp<const RSC::Element> mRsElement; + RSC::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/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h index bbfa66d..46cfd04 100644 --- a/libs/hwui/GammaFontRenderer.h +++ b/libs/hwui/GammaFontRenderer.h @@ -35,7 +35,7 @@ public: virtual FontRenderer& getFontRenderer(const SkPaint* paint) = 0; virtual uint32_t getFontRendererCount() const = 0; - virtual uint32_t getFontRendererSize(uint32_t fontRenderer) const = 0; + virtual uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const = 0; virtual void describe(ProgramDescription& description, const SkPaint* paint) const = 0; virtual void setupProgram(ProgramDescription& description, Program* program) const = 0; @@ -81,8 +81,8 @@ public: return 1; } - uint32_t getFontRendererSize(uint32_t fontRenderer) const { - return mRenderer ? mRenderer->getCacheSize() : 0; + uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const { + return mRenderer ? mRenderer->getCacheSize(format) : 0; } void describe(ProgramDescription& description, const SkPaint* paint) const; @@ -128,8 +128,8 @@ public: return 1; } - uint32_t getFontRendererSize(uint32_t fontRenderer) const { - return mRenderer ? mRenderer->getCacheSize() : 0; + uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const { + return mRenderer ? mRenderer->getCacheSize(format) : 0; } void describe(ProgramDescription& description, const SkPaint* paint) const { @@ -162,13 +162,13 @@ public: return kGammaCount; } - uint32_t getFontRendererSize(uint32_t fontRenderer) const { + uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const { if (fontRenderer >= kGammaCount) return 0; FontRenderer* renderer = mRenderers[fontRenderer]; if (!renderer) return 0; - return renderer->getCacheSize(); + return renderer->getCacheSize(format); } void describe(ProgramDescription& description, const SkPaint* paint) const { diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index 507ed95..0916942 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -78,7 +78,7 @@ GradientCache::GradientCache(): mCache.setOnEntryRemovedListener(this); const Extensions& extensions = Extensions::getInstance(); - mUseFloatTexture = extensions.getMajorGlVersion() >= 3; + mUseFloatTexture = extensions.hasFloatTextures(); mHasNpot = extensions.hasNPot(); } @@ -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..bd371a3 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -28,9 +28,9 @@ 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; cacheable = true; dirty = false; @@ -47,16 +47,15 @@ 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(); delete[] mesh; - delete[] meshIndices; delete deferredList; } @@ -76,7 +75,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 +88,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 +119,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,21 +137,55 @@ 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::defer() { - if (!deferredList) { - deferredList = new DeferredDisplayList; +void Layer::bindTexture() const { + if (texture.id) { + caches.bindTexture(renderTarget, texture.id); } - DeferStateStruct deferredState(*deferredList, *renderer, - DisplayList::kReplayFlag_ClipChildren); +} + +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); + } +} + +void Layer::defer() { const float width = layer.getWidth(); const float height = layer.getHeight(); @@ -161,6 +194,14 @@ void Layer::defer() { dirtyRect.set(0, 0, width, height); } + if (deferredList) { + deferredList->reset(dirtyRect); + } else { + deferredList = new DeferredDisplayList(dirtyRect); + } + DeferStateStruct deferredState(*deferredList, *renderer, + DisplayList::kReplayFlag_ClipChildren); + renderer->initViewport(width, height); renderer->setupFrameState(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); @@ -170,8 +211,19 @@ void Layer::defer() { deferredUpdateScheduled = false; } -void Layer::flush() { +void Layer::cancelDefer() { + renderer = NULL; + displayList = NULL; + deferredUpdateScheduled = false; if (deferredList) { + delete deferredList; + deferredList = NULL; + } +} + +void Layer::flush() { + // renderer is checked as layer may be destroyed/put in layer cache with flush scheduled + if (deferredList && renderer) { renderer->setViewport(layer.getWidth(), layer.getHeight()); renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index 7186603..b70042f 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; @@ -275,6 +245,7 @@ struct Layer { } void defer(); + void cancelDefer(); void flush(); void render(); @@ -306,7 +277,6 @@ struct Layer { * If the layer can be rendered as a mesh, this is non-null. */ TextureVertex* mesh; - uint16_t* meshIndices; GLsizei meshElementCount; /** @@ -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/LayerCache.cpp b/libs/hwui/LayerCache.cpp index a0709af..6be0146 100644 --- a/libs/hwui/LayerCache.cpp +++ b/libs/hwui/LayerCache.cpp @@ -155,9 +155,7 @@ bool LayerCache::put(Layer* layer) { victim->layer.getHeight()); } - layer->deferredUpdateScheduled = false; - layer->renderer = NULL; - layer->displayList = NULL; + layer->cancelDefer(); LayerEntry entry(layer); diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index 3e55fff..f8076cc 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -130,10 +130,7 @@ void LayerRenderer::generateMesh() { if (mLayer->region.isRect() || mLayer->region.isEmpty()) { if (mLayer->mesh) { delete[] mLayer->mesh; - delete[] mLayer->meshIndices; - mLayer->mesh = NULL; - mLayer->meshIndices = NULL; mLayer->meshElementCount = 0; } @@ -154,17 +151,11 @@ void LayerRenderer::generateMesh() { if (mLayer->mesh && mLayer->meshElementCount < elementCount) { delete[] mLayer->mesh; - delete[] mLayer->meshIndices; - mLayer->mesh = NULL; - mLayer->meshIndices = NULL; } - bool rebuildIndices = false; if (!mLayer->mesh) { mLayer->mesh = new TextureVertex[count * 4]; - mLayer->meshIndices = new uint16_t[elementCount]; - rebuildIndices = true; } mLayer->meshElementCount = elementCount; @@ -173,7 +164,6 @@ void LayerRenderer::generateMesh() { const float height = mLayer->layer.getHeight(); TextureVertex* mesh = mLayer->mesh; - uint16_t* indices = mLayer->meshIndices; for (size_t i = 0; i < count; i++) { const android::Rect* r = &rects[i]; @@ -187,17 +177,6 @@ void LayerRenderer::generateMesh() { TextureVertex::set(mesh++, r->right, r->top, u2, v1); TextureVertex::set(mesh++, r->left, r->bottom, u1, v2); TextureVertex::set(mesh++, r->right, r->bottom, u2, v2); - - if (rebuildIndices) { - uint16_t quad = i * 4; - int index = i * 6; - indices[index ] = quad; // top-left - indices[index + 1] = quad + 1; // top-right - indices[index + 2] = quad + 2; // bottom-left - indices[index + 3] = quad + 2; // bottom-left - indices[index + 4] = quad + 1; // top-right - indices[index + 5] = quad + 3; // bottom-right - } } } @@ -436,7 +415,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 +446,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 +477,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.cpp b/libs/hwui/Matrix.cpp index 6a5ea51..ba22071 100644 --- a/libs/hwui/Matrix.cpp +++ b/libs/hwui/Matrix.cpp @@ -72,7 +72,7 @@ static bool isZero(float f) { return fabs(f) <= EPSILON; } -uint32_t Matrix4::getType() const { +uint8_t Matrix4::getType() const { if (mType & kTypeUnknown) { mType = kTypeIdentity; @@ -114,7 +114,7 @@ uint32_t Matrix4::getType() const { return mType; } -uint32_t Matrix4::getGeometryType() const { +uint8_t Matrix4::getGeometryType() const { return getType() & sGeometryMask; } @@ -122,12 +122,16 @@ bool Matrix4::rectToRect() const { return getType() & kTypeRectToRect; } +bool Matrix4::positiveScale() const { + return (data[kScaleX] > 0.0f && data[kScaleY] > 0.0f); +} + bool Matrix4::changesBounds() const { return getType() & (kTypeScale | kTypeAffine | kTypePerspective); } bool Matrix4::isPureTranslate() const { - return getGeometryType() == kTypeTranslate; + return getGeometryType() <= kTypeTranslate; } bool Matrix4::isSimple() const { diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h index 75e280c..b861ba4 100644 --- a/libs/hwui/Matrix.h +++ b/libs/hwui/Matrix.h @@ -26,6 +26,12 @@ namespace android { namespace uirenderer { +#define MATRIX_STRING "[%.2f %.2f %.2f] [%.2f %.2f %.2f] [%.2f %.2f %.2f]" +#define MATRIX_ARGS(m) \ + (m)->get(0), (m)->get(1), (m)->get(2), \ + (m)->get(3), (m)->get(4), (m)->get(5), \ + (m)->get(6), (m)->get(7), (m)->get(8) + /////////////////////////////////////////////////////////////////////////////// // Classes /////////////////////////////////////////////////////////////////////////////// @@ -118,7 +124,7 @@ public: void loadOrtho(float left, float right, float bottom, float top, float near, float far); - uint32_t getType() const; + uint8_t getType() const; void multiply(const Matrix4& v) { Matrix4 u; @@ -128,10 +134,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 + uint8_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) { @@ -160,6 +183,7 @@ public: bool isIdentity() const; bool isPerspective() const; bool rectToRect() const; + bool positiveScale() const; bool changesBounds() const; @@ -179,7 +203,7 @@ public: static const Matrix4& identity(); private: - mutable uint32_t mType; + mutable uint8_t mType; inline float get(int i, int j) const { return data[i * 4 + j]; @@ -189,7 +213,7 @@ private: data[i * 4 + j] = v; } - uint32_t getGeometryType() const; + uint8_t getGeometryType() const; }; // class Matrix4 diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 722cc63..4d76bed 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> @@ -34,6 +33,7 @@ #include "OpenGLRenderer.h" #include "DeferredDisplayList.h" #include "DisplayListRenderer.h" +#include "Fence.h" #include "PathTessellator.h" #include "Properties.h" #include "Vector.h" @@ -107,6 +107,15 @@ static const Blender gBlendsSwap[] = { }; /////////////////////////////////////////////////////////////////////////////// +// Functions +/////////////////////////////////////////////////////////////////////////////// + +template<typename T> +static inline T min(T a, T b) { + return a < b ? a : b; +} + +/////////////////////////////////////////////////////////////////////////////// // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// @@ -120,6 +129,7 @@ OpenGLRenderer::OpenGLRenderer(): mFirstSnapshot = new Snapshot; mFrameStarted = false; + mCountOverdraw = false; mScissorOptimizationDisabled = false; } @@ -222,6 +232,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 +264,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 +346,10 @@ void OpenGLRenderer::finish() { #endif } + if (mCountOverdraw) { + countOverdraw(); + } + mFrameStarted = false; } @@ -345,6 +360,7 @@ void OpenGLRenderer::interrupt() { mCaches.currentProgram = NULL; } } + mCaches.resetActiveTexture(); mCaches.unbindMeshBuffer(); mCaches.unbindIndicesBuffer(); mCaches.resetVertexPointers(); @@ -366,6 +382,7 @@ void OpenGLRenderer::resume() { dirtyClip(); mCaches.activeTexture(0); + mCaches.resetBoundTextures(); mCaches.blend = true; glEnable(GL_BLEND); @@ -432,13 +449,8 @@ status_t OpenGLRenderer::invokeFunctors(Rect& dirty) { status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone; - interrupt(); detachFunctor(functor); - mCaches.enableScissor(); - if (mDirtyClip) { - setScissorFromClip(); - } Rect clip(*mSnapshot->clipRect); clip.snapToPixelBoundaries(); @@ -459,7 +471,18 @@ 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; + bool dirtyClip = mDirtyClip; + // setup GL state for functor + if (mDirtyClip) { + setStencilFromClip(); // can issue draws, so must precede enableScissor()/interrupt() + } + if (mCaches.enableScissor() || dirtyClip) { + setScissorFromClip(); + } + interrupt(); + + // call functor immediately after GL state setup + status_t result = (*functor)(DrawGlInfo::kModeDraw, &info); if (result != DrawGlInfo::kStatusDone) { Rect localDirty(info.dirtyLeft, info.dirtyTop, info.dirtyRight, info.dirtyBottom); @@ -471,7 +494,7 @@ status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { } resume(); - return result; + return result | DrawGlInfo::kStatusDrew; } /////////////////////////////////////////////////////////////////////////////// @@ -512,18 +535,41 @@ void OpenGLRenderer::renderOverdraw() { mCaches.setScissor(clip->left, mFirstSnapshot->height - clip->bottom, clip->right - clip->left, clip->bottom - clip->top); + // 1x overdraw mCaches.stencil.enableDebugTest(2); - drawColor(0x2f0000ff, SkXfermode::kSrcOver_Mode); + drawColor(mCaches.getOverdrawColor(1), SkXfermode::kSrcOver_Mode); + + // 2x overdraw mCaches.stencil.enableDebugTest(3); - drawColor(0x2f00ff00, SkXfermode::kSrcOver_Mode); + drawColor(mCaches.getOverdrawColor(2), SkXfermode::kSrcOver_Mode); + + // 3x overdraw mCaches.stencil.enableDebugTest(4); - drawColor(0x3fff0000, SkXfermode::kSrcOver_Mode); + drawColor(mCaches.getOverdrawColor(3), SkXfermode::kSrcOver_Mode); + + // 4x overdraw and higher mCaches.stencil.enableDebugTest(4, true); - drawColor(0x7fff0000, SkXfermode::kSrcOver_Mode); + drawColor(mCaches.getOverdrawColor(4), SkXfermode::kSrcOver_Mode); + mCaches.stencil.disable(); } } +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 /////////////////////////////////////////////////////////////////////////////// @@ -531,6 +577,8 @@ void OpenGLRenderer::renderOverdraw() { bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) { if (layer->deferredUpdateScheduled && layer->renderer && layer->displayList && layer->displayList->isRenderable()) { + ATRACE_CALL(); + Rect& dirty = layer->dirtyRect; if (inFrame) { @@ -598,8 +646,11 @@ void OpenGLRenderer::flushLayers() { sprintf(layerName, "Layer #%d", i); startMark(layerName); + ATRACE_BEGIN("flushLayer"); Layer* layer = mLayerUpdates.itemAt(i); layer->flush(); + ATRACE_END(); + mCaches.resourceCache.decrementRefcount(layer); endMark(); @@ -628,6 +679,18 @@ void OpenGLRenderer::pushLayerUpdate(Layer* layer) { } } +void OpenGLRenderer::cancelLayerUpdate(Layer* layer) { + if (layer) { + for (int i = mLayerUpdates.size() - 1; i >= 0; i--) { + if (mLayerUpdates.itemAt(i) == layer) { + mLayerUpdates.removeAt(i); + mCaches.resourceCache.decrementRefcount(layer); + break; + } + } + } +} + void OpenGLRenderer::clearLayerUpdates() { size_t count = mLayerUpdates.size(); if (count > 0) { @@ -640,6 +703,14 @@ void OpenGLRenderer::clearLayerUpdates() { } } +void OpenGLRenderer::flushLayerUpdates() { + syncState(); + updateLayers(); + flushLayers(); + // Wait for all the layer updates to be executed + AutoFence fence; +} + /////////////////////////////////////////////////////////////////////////////// // State management /////////////////////////////////////////////////////////////////////////////// @@ -780,6 +851,7 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float if (!mSnapshot->isIgnored()) { mSnapshot->resetTransform(-bounds.left, -bounds.top, 0.0f); mSnapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom); + mSnapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight()); } } @@ -961,6 +1033,10 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) { const Rect& rect = layer->layer; const bool fboLayer = current->flags & Snapshot::kFlagIsFboLayer; + bool clipRequired = false; + quickRejectNoScissor(rect, &clipRequired); // safely ignore return, should never be rejected + mCaches.setScissorEnabled(mScissorOptimizationDisabled || clipRequired); + if (fboLayer) { endTiling(); @@ -1019,7 +1095,7 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) { } void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) { - float alpha = layer->getAlpha() / 255.0f * mSnapshot->alpha; + float alpha = getLayerAlpha(layer); setupDraw(); if (layer->getRenderTarget() == GL_TEXTURE_2D) { @@ -1055,8 +1131,6 @@ void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) { setupDrawMesh(&mMeshVertices[0].position[0], &mMeshVertices[0].texture[0]); glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); - - finishDrawTexture(); } void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) { @@ -1125,8 +1199,6 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { return; } - // TODO: See LayerRenderer.cpp::generateMesh() for important - // information about this implementation if (CC_LIKELY(!layer->region.isEmpty())) { size_t count; const android::Rect* rects; @@ -1149,7 +1221,7 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { // after we setup drawing in case we need to mess with the // stencil buffer in setupDraw() TextureVertex* mesh = mCaches.getRegionMesh(); - GLsizei numQuads = 0; + uint32_t numQuads = 0; setupDrawWithTexture(); setupDrawColor(alpha, alpha, alpha, alpha); @@ -1188,7 +1260,7 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { numQuads++; - if (numQuads >= REGION_MESH_QUAD_COUNT) { + if (numQuads >= gMaxNumberOfQuads) { DRAW_DOUBLE_STENCIL(glDrawElements(GL_TRIANGLES, numQuads * 6, GL_UNSIGNED_SHORT, NULL)); numQuads = 0; @@ -1201,8 +1273,6 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { GL_UNSIGNED_SHORT, NULL)); } - finishDrawTexture(); - #if DEBUG_LAYERS_AS_REGIONS drawRegionRects(layer->region); #endif @@ -1239,7 +1309,6 @@ void OpenGLRenderer::drawRegionRects(const Region& region) { void OpenGLRenderer::drawRegionRects(const SkRegion& region, int color, SkXfermode::Mode mode, bool dirty) { - int count = 0; Vector<float> rects; SkRegion::Iterator it(region); @@ -1249,11 +1318,10 @@ void OpenGLRenderer::drawRegionRects(const SkRegion& region, int color, rects.push(r.fTop); rects.push(r.fRight); rects.push(r.fBottom); - count += 4; it.next(); } - drawColorRects(rects.array(), count, color, mode, true, dirty, false); + drawColorRects(rects.array(), rects.size(), color, mode, true, dirty, false); } void OpenGLRenderer::dirtyLayer(const float left, const float top, @@ -1283,6 +1351,21 @@ void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) { } } +void OpenGLRenderer::drawIndexedQuads(Vertex* mesh, GLsizei quadsCount) { + GLsizei elementsCount = quadsCount * 6; + while (elementsCount > 0) { + GLsizei drawCount = min(elementsCount, (GLsizei) gMaxNumberOfQuads * 6); + + setupDrawIndexedVertices(&mesh[0].position[0]); + glDrawElements(GL_TRIANGLES, drawCount, GL_UNSIGNED_SHORT, NULL); + + elementsCount -= drawCount; + // Though there are 4 vertices in a quad, we use 6 indices per + // quad to draw with GL_TRIANGLES + mesh += (drawCount / 6) * 4; + } +} + void OpenGLRenderer::clearLayerRegions() { const size_t count = mLayers.size(); if (count == 0) return; @@ -1297,17 +1380,15 @@ void OpenGLRenderer::clearLayerRegions() { // is likely different so we need to disable clipping here bool scissorChanged = mCaches.disableScissor(); - Vertex mesh[count * 6]; + Vertex mesh[count * 4]; Vertex* vertex = mesh; for (uint32_t i = 0; i < count; i++) { Rect* bounds = mLayers.itemAt(i); - Vertex::set(vertex++, bounds->left, bounds->bottom); Vertex::set(vertex++, bounds->left, bounds->top); Vertex::set(vertex++, bounds->right, bounds->top); Vertex::set(vertex++, bounds->left, bounds->bottom); - Vertex::set(vertex++, bounds->right, bounds->top); Vertex::set(vertex++, bounds->right, bounds->bottom); delete bounds; @@ -1323,9 +1404,8 @@ void OpenGLRenderer::clearLayerRegions() { setupDrawProgram(); setupDrawPureColorUniforms(); setupDrawModelViewTranslate(0.0f, 0.0f, 0.0f, 0.0f, true); - setupDrawVertices(&mesh[0].position[0]); - glDrawArrays(GL_TRIANGLES, 0, count * 6); + drawIndexedQuads(&mesh[0], count); if (scissorChanged) mCaches.enableScissor(); } else { @@ -1348,11 +1428,30 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef // state has bounds initialized in local coordinates if (!state.mBounds.isEmpty()) { currentMatrix.mapRect(state.mBounds); - if (!state.mBounds.intersect(currentClip)) { + Rect clippedBounds(state.mBounds); + // NOTE: if we ever want to use this clipping info to drive whether the scissor + // is used, it should more closely duplicate the quickReject logic (in how it uses + // snapToPixelBoundaries) + + if(!clippedBounds.intersect(currentClip)) { // quick rejected return true; } + + state.mClipSideFlags = kClipSide_None; + if (!currentClip.contains(state.mBounds)) { + int& flags = state.mClipSideFlags; + // op partially clipped, so record which sides are clipped for clip-aware merging + if (currentClip.left > state.mBounds.left) flags |= kClipSide_Left; + if (currentClip.top > state.mBounds.top) flags |= kClipSide_Top; + if (currentClip.right < state.mBounds.right) flags |= kClipSide_Right; + if (currentClip.bottom < state.mBounds.bottom) flags |= kClipSide_Bottom; + } + state.mBounds.set(clippedBounds); } else { + // Empty bounds implies size unknown. Label op as conservatively clipped to disable + // overdraw avoidance (since we don't know what it overlaps) + state.mClipSideFlags = kClipSide_ConservativeFull; state.mBounds.set(currentClip); } } @@ -1376,14 +1475,27 @@ void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool mSnapshot->alpha = state.mAlpha; if (state.mClipValid && !skipClipRestore) { - mSnapshot->setClip(state.mClip.left, state.mClip.top, state.mClip.right, state.mClip.bottom); + mSnapshot->setClip(state.mClip.left, state.mClip.top, + state.mClip.right, state.mClip.bottom); dirtyClip(); } } -void OpenGLRenderer::setFullScreenClip() { - mSnapshot->setClip(0, 0, mWidth, mHeight); +/** + * Merged multidraw (such as in drawText and drawBitmaps rely on the fact that no clipping is done + * in the draw path. Instead, clipping is done ahead of time - either as a single clip rect (when at + * least one op is clipped), or disabled entirely (because no merged op is clipped) + * + * This method should be called when restoreDisplayState() won't be restoring the clip + */ +void OpenGLRenderer::setupMergedMultiDraw(const Rect* clipRect) { + if (clipRect != NULL) { + mSnapshot->setClip(clipRect->left, clipRect->top, clipRect->right, clipRect->bottom); + } else { + mSnapshot->setClip(0, 0, mWidth, mHeight); + } dirtyClip(); + mCaches.setScissorEnabled(clipRect != NULL || mScissorOptimizationDisabled); } /////////////////////////////////////////////////////////////////////////////// @@ -1391,7 +1503,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) { @@ -1514,65 +1626,49 @@ const Rect& OpenGLRenderer::getClipBounds() { return mSnapshot->getLocalClip(); } -bool OpenGLRenderer::quickRejectNoScissor(float left, float top, float right, float bottom) { - if (mSnapshot->isIgnored()) { +bool OpenGLRenderer::quickRejectNoScissor(float left, float top, float right, float bottom, + bool snapOut, bool* clipRequired) { + if (mSnapshot->isIgnored() || bottom <= top || right <= left) { return true; } Rect r(left, top, right, bottom); currentTransform().mapRect(r); - r.snapToPixelBoundaries(); + r.snapGeometryToPixelBoundaries(snapOut); Rect clipRect(*mSnapshot->clipRect); clipRect.snapToPixelBoundaries(); - return !clipRect.intersects(r); -} - -bool OpenGLRenderer::quickRejectNoScissor(float left, float top, float right, float bottom, - Rect& transformed, Rect& clip) { - if (mSnapshot->isIgnored()) { - return true; - } - - transformed.set(left, top, right, bottom); - currentTransform().mapRect(transformed); - transformed.snapToPixelBoundaries(); + if (!clipRect.intersects(r)) return true; - clip.set(*mSnapshot->clipRect); - clip.snapToPixelBoundaries(); - - return !clip.intersects(transformed); + if (clipRequired) *clipRequired = !clipRect.contains(r); + return false; } bool OpenGLRenderer::quickRejectPreStroke(float left, float top, float right, float bottom, SkPaint* paint) { + // AA geometry will likely have a ramp around it (not accounted for in local bounds). Snap out + // the final mapped rect to ensure correct clipping behavior for the ramp. + bool snapOut = paint->isAntiAlias(); + if (paint->getStyle() != SkPaint::kFill_Style) { float outset = paint->getStrokeWidth() * 0.5f; - return quickReject(left - outset, top - outset, right + outset, bottom + outset); + return quickReject(left - outset, top - outset, right + outset, bottom + outset, snapOut); } else { - return quickReject(left, top, right, bottom); + return quickReject(left, top, right, bottom, snapOut); } } -bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom) { - if (mSnapshot->isIgnored() || bottom <= top || right <= left) { +bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom, bool snapOut) { + bool clipRequired = false; + if (quickRejectNoScissor(left, top, right, bottom, snapOut, &clipRequired)) { return true; } - Rect r(left, top, right, bottom); - currentTransform().mapRect(r); - r.snapToPixelBoundaries(); - - Rect clipRect(*mSnapshot->clipRect); - clipRect.snapToPixelBoundaries(); - - bool rejected = !clipRect.intersects(r); - if (!isDeferred() && !rejected) { - mCaches.setScissorEnabled(mScissorOptimizationDisabled || !clipRect.contains(r)); + if (!isDeferred()) { + mCaches.setScissorEnabled(mScissorOptimizationDisabled || clipRequired); } - - return rejected; + return false; } void OpenGLRenderer::debugClip() { @@ -1595,7 +1691,7 @@ bool OpenGLRenderer::clipRect(float left, float top, float right, float bottom, SkPath path; path.addRect(left, top, right, bottom); - return clipPath(&path, op); + return OpenGLRenderer::clipPath(&path, op); } bool OpenGLRenderer::clipPath(SkPath* path, SkRegion::Op op) { @@ -1606,11 +1702,15 @@ bool OpenGLRenderer::clipPath(SkPath* path, SkRegion::Op op) { path->transform(transform, &transformed); SkRegion clip; - if (!mSnapshot->clipRegion->isEmpty()) { - clip.setRegion(*mSnapshot->clipRegion); + if (!mSnapshot->previous->clipRegion->isEmpty()) { + clip.setRegion(*mSnapshot->previous->clipRegion); } else { - Rect* bounds = mSnapshot->clipRect; - clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom); + if (mSnapshot->previous == mFirstSnapshot) { + clip.setRect(0, 0, mWidth, mHeight); + } else { + Rect* bounds = mSnapshot->previous->clipRect; + clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom); + } } SkRegion region; @@ -1665,6 +1765,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) { @@ -1690,11 +1792,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; @@ -1809,11 +1906,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); @@ -1882,7 +1974,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(); @@ -1913,21 +2005,28 @@ 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); } } -void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) { +void OpenGLRenderer::setupDrawIndexedVertices(GLvoid* vertices) { bool force = mCaches.unbindMeshBuffer(); + mCaches.bindIndicesBuffer(); mCaches.bindPositionVertexPointer(force, vertices, gVertexStride); - mCaches.unbindIndicesBuffer(); -} - -void OpenGLRenderer::finishDrawTexture() { } /////////////////////////////////////////////////////////////////////////////// @@ -1947,7 +2046,8 @@ status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList, Rect& dirty, return status | replayStruct.mDrawGlStatus; } - DeferredDisplayList deferredList; + bool avoidOverdraw = !mCaches.debugOverdraw && !mCountOverdraw; // shh, don't tell devs! + DeferredDisplayList deferredList(*(mSnapshot->clipRect), avoidOverdraw); DeferStateStruct deferStruct(deferredList, *this, replayFlags); displayList->defer(deferStruct, 0); @@ -1989,20 +2089,24 @@ 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) { - - // merged draw operations don't need scissor, but clip should still be valid - mCaches.setScissorEnabled(mScissorOptimizationDisabled); - +/** + * Important note: this method is intended to draw batches of bitmaps and + * will not set the scissor enable or dirty the current layer, if any. + * The caller is responsible for properly dirtying the current layer. + */ +status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, AssetAtlas::Entry* entry, int bitmapCount, + TextureVertex* vertices, bool pureTranslate, const Rect& bounds, SkPaint* paint) { 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); int alpha; @@ -2010,7 +2114,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(pureTranslate ? GL_NEAREST : FILTER(paint), true); const float x = (int) floorf(bounds.left + 0.5f); const float y = (int) floorf(bounds.top + 0.5f); @@ -2019,12 +2123,12 @@ status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureV drawAlpha8TextureMesh(x, y, x + bounds.getWidth(), y + bounds.getHeight(), texture->id, paint != NULL, color, alpha, mode, &vertices[0].position[0], &vertices[0].texture[0], - GL_TRIANGLES, bitmapCount * 6, true, true); + GL_TRIANGLES, bitmapCount * 6, true, true, false); } else { drawTextureMesh(x, y, x + bounds.getWidth(), y + bounds.getHeight(), texture->id, alpha / 255.0f, mode, texture->blend, &vertices[0].position[0], &vertices[0].texture[0], - GL_TRIANGLES, bitmapCount * 6, false, true, 0, true); + GL_TRIANGLES, bitmapCount * 6, false, true, 0, true, false); } return DrawGlInfo::kStatusDrew; @@ -2039,7 +2143,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); @@ -2062,7 +2166,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); @@ -2107,6 +2211,9 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes return DrawGlInfo::kStatusDone; } + // TODO: use quickReject on bounds from vertices + mCaches.enableScissor(); + float left = FLT_MAX; float top = FLT_MAX; float right = FLT_MIN; @@ -2125,6 +2232,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; @@ -2134,6 +2245,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; @@ -2163,11 +2276,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); @@ -2199,8 +2313,6 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes glDrawArrays(GL_TRIANGLES, 0, count); - finishDrawTexture(); - int slot = mCaches.currentProgram->getAttrib("colors"); if (slot >= 0) { glDisableVertexAttribArray(slot); @@ -2220,17 +2332,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); @@ -2301,37 +2415,38 @@ 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); + if (quickReject(left, top, right, bottom)) { + return DrawGlInfo::kStatusDone; + } + + AssetAtlas::Entry* entry = mCaches.assetAtlas.getEntry(bitmap); + const Patch* mesh = mCaches.patchCache.get(entry, bitmap->width(), bitmap->height(), + right - left, bottom - top, patch); - return drawPatch(bitmap, xDivs, yDivs, colors, width, height, numColors, - left, top, right, bottom, alpha, mode); + return drawPatch(bitmap, mesh, entry, left, top, right, bottom, paint); } -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) { +status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const Patch* mesh, AssetAtlas::Entry* entry, + float left, float top, float right, float bottom, SkPaint* paint) { if (quickReject(left, top, right, bottom)) { return DrawGlInfo::kStatusDone; } - alpha *= mSnapshot->alpha; - - const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(), - right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors); - if (CC_LIKELY(mesh && mesh->verticesCount > 0)) { 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); + int alpha; + SkXfermode::Mode mode; + getAlphaAndMode(paint, &alpha, &mode); + const bool pureTranslate = currentTransform().isPureTranslate(); // Mark the current layer dirty where we are going to draw the patch if (hasLayer() && mesh->hasEmptyQuads) { @@ -2355,24 +2470,52 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const 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); } } return DrawGlInfo::kStatusDrew; } +/** + * Important note: this method is intended to draw batches of 9-patch objects and + * will not set the scissor enable or dirty the current layer, if any. + * The caller is responsible for properly dirtying the current layer. + */ +status_t OpenGLRenderer::drawPatches(SkBitmap* bitmap, AssetAtlas::Entry* entry, + TextureVertex* vertices, uint32_t indexCount, SkPaint* paint) { + mCaches.activeTexture(0); + 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); + + int alpha; + SkXfermode::Mode mode; + getAlphaAndMode(paint, &alpha, &mode); + + drawIndexedTextureMesh(0.0f, 0.0f, 1.0f, 1.0f, texture->id, alpha / 255.0f, + mode, texture->blend, &vertices[0].position[0], &vertices[0].texture[0], + GL_TRIANGLES, indexCount, false, true, 0, true, false); + + return DrawGlInfo::kStatusDrew; +} + status_t OpenGLRenderer::drawVertexBuffer(const VertexBuffer& vertexBuffer, SkPaint* paint, bool useOffset) { - if (!vertexBuffer.getSize()) { + if (!vertexBuffer.getVertexCount()) { // no vertices to draw return DrawGlInfo::kStatusDone; } @@ -2410,7 +2553,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); @@ -2473,65 +2616,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 - - // 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; - - 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); + if (mSnapshot->isIgnored() || count < 2) return DrawGlInfo::kStatusDone; - for (int i = 0; i < count; i += 2) { - TextureVertex::set(vertex++, points[i], points[i + 1], 0.0f, 0.0f); - generatedVerticesCount++; + count &= ~0x1; // round down to nearest two - 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) { @@ -2747,48 +2847,6 @@ bool OpenGLRenderer::canSkipText(const SkPaint* paint) const { return alpha == 0.0f && getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode; } -class TextSetupFunctor: public Functor { -public: - TextSetupFunctor(OpenGLRenderer& renderer, float x, float y, bool pureTranslate, - int alpha, SkXfermode::Mode mode, SkPaint* paint): Functor(), - renderer(renderer), x(x), y(y), pureTranslate(pureTranslate), - alpha(alpha), mode(mode), paint(paint) { - } - ~TextSetupFunctor() { } - - status_t operator ()(int what, void* data) { - renderer.setupDraw(); - renderer.setupDrawTextGamma(paint); - renderer.setupDrawDirtyRegionsDisabled(); - renderer.setupDrawWithTexture(true); - renderer.setupDrawAlpha8Color(paint->getColor(), alpha); - renderer.setupDrawColorFilter(); - renderer.setupDrawShader(); - renderer.setupDrawBlending(true, mode); - renderer.setupDrawProgram(); - renderer.setupDrawModelView(x, y, x, y, pureTranslate, true); - // Calling setupDrawTexture with the name 0 will enable the - // uv attributes and increase the texture unit count - // texture binding will be performed by the font renderer as - // needed - renderer.setupDrawTexture(0); - renderer.setupDrawPureColorUniforms(); - renderer.setupDrawColorFilterUniforms(); - renderer.setupDrawShaderUniforms(pureTranslate); - renderer.setupDrawTextGammaUniforms(); - - return NO_ERROR; - } - - OpenGLRenderer& renderer; - float x; - float y; - bool pureTranslate; - int alpha; - SkXfermode::Mode mode; - SkPaint* paint; -}; - status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count, const float* positions, SkPaint* paint) { if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) { @@ -2800,6 +2858,8 @@ status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count return DrawGlInfo::kStatusDone; } + mCaches.enableScissor(); + float x = 0.0f; float y = 0.0f; const bool pureTranslate = currentTransform().isPureTranslate(); @@ -2832,7 +2892,7 @@ status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count const bool hasActiveLayer = hasLayer(); - TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint); + TextSetupFunctor functor(this, x, y, pureTranslate, alpha, mode, paint); if (fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y, positions, hasActiveLayer ? &bounds : NULL, &functor)) { if (hasActiveLayer) { @@ -2862,36 +2922,17 @@ 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 && - (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint))) { - 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)) { + // The checks for corner-case ignorable text and quick rejection is only done for immediate + // drawing as ops from DeferredDisplayList are already filtered for these + if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint) || + quickReject(bounds)) { return DrawGlInfo::kStatusDone; } - } else { - // merged draw operations don't need scissor, but clip should still be valid - mCaches.setScissorEnabled(mScissorOptimizationDisabled); } const float oldX = x; @@ -2939,10 +2980,10 @@ 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); + TextSetupFunctor functor(this, x, y, pureTranslate, alpha, mode, paint); // don't call issuedrawcommand, do it at end of batch bool forceFinish = (drawOpMode != kDrawOpMode_Defer); @@ -2950,20 +2991,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; } @@ -2974,6 +3015,9 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co return DrawGlInfo::kStatusDone; } + // TODO: avoid scissor by calculating maximum bounds using path bounds + font metrics + mCaches.enableScissor(); + FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint); fontRenderer.setFont(paint, mat4::identity()); fontRenderer.setTextureFiltering(true); @@ -2981,26 +3025,7 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co int alpha; SkXfermode::Mode mode; getAlphaAndMode(paint, &alpha, &mode); - - setupDraw(); - setupDrawTextGamma(paint); - setupDrawDirtyRegionsDisabled(); - setupDrawWithTexture(true); - setupDrawAlpha8Color(paint->getColor(), alpha); - setupDrawColorFilter(); - setupDrawShader(); - setupDrawBlending(true, mode); - setupDrawProgram(); - setupDrawModelView(0.0f, 0.0f, 0.0f, 0.0f, false, true); - // Calling setupDrawTexture with the name 0 will enable the - // uv attributes and increase the texture unit count - // texture binding will be performed by the font renderer as - // needed - setupDrawTexture(0); - setupDrawPureColorUniforms(); - setupDrawColorFilterUniforms(); - setupDrawShaderUniforms(false); - setupDrawTextGammaUniforms(); + TextSetupFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint); const Rect* clip = &mSnapshot->getLocalClip(); Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); @@ -3008,7 +3033,7 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co const bool hasActiveLayer = hasLayer(); if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path, - hOffset, vOffset, hasActiveLayer ? &bounds : NULL)) { + hOffset, vOffset, hasActiveLayer ? &bounds : NULL, &functor)) { if (hasActiveLayer) { currentTransform().mapRect(bounds); dirtyLayerUnchecked(bounds, getRegion()); @@ -3049,10 +3074,9 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { } } - Rect transformed; - Rect clip; + bool clipRequired = false; const bool rejected = quickRejectNoScissor(x, y, - x + layer->layer.getWidth(), y + layer->layer.getHeight(), transformed, clip); + x + layer->layer.getWidth(), y + layer->layer.getHeight(), false, &clipRequired); if (rejected) { if (transform && !transform->isIdentity()) { @@ -3063,7 +3087,7 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { updateLayer(layer, true); - mCaches.setScissorEnabled(mScissorOptimizationDisabled || !clip.contains(transformed)); + mCaches.setScissorEnabled(mScissorOptimizationDisabled || clipRequired); mCaches.activeTexture(0); if (CC_LIKELY(!layer->region.isEmpty())) { @@ -3096,13 +3120,22 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { setupDrawModelViewTranslate(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight()); } - setupDrawMesh(&layer->mesh[0].position[0], &layer->mesh[0].texture[0]); - DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, - glDrawElements(GL_TRIANGLES, layer->meshElementCount, - GL_UNSIGNED_SHORT, layer->meshIndices)); + TextureVertex* mesh = &layer->mesh[0]; + GLsizei elementsCount = layer->meshElementCount; + + while (elementsCount > 0) { + GLsizei drawCount = min(elementsCount, (GLsizei) gMaxNumberOfQuads * 6); - finishDrawTexture(); + setupDrawMeshIndices(&mesh[0].position[0], &mesh[0].texture[0]); + DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, + glDrawElements(GL_TRIANGLES, drawCount, GL_UNSIGNED_SHORT, NULL)); + + elementsCount -= drawCount; + // Though there are 4 vertices in a quad, we use 6 indices per + // quad to draw with GL_TRIANGLES + mesh += (drawCount / 6) * 4; + } #if DEBUG_LAYERS_AS_REGIONS drawRegionRects(layer->region); @@ -3137,7 +3170,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); } } @@ -3205,6 +3238,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,8 +3271,6 @@ void OpenGLRenderer::drawPathTexture(const PathTexture* texture, setupDrawMesh(NULL, (GLvoid*) gMeshTextureOffset); glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); - - finishDrawTexture(); } // Same values used by Skia @@ -3239,17 +3278,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(); @@ -3315,8 +3349,7 @@ status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color float right = FLT_MIN; float bottom = FLT_MIN; - int vertexCount = 0; - Vertex mesh[count * 6]; + Vertex mesh[count]; Vertex* vertex = mesh; for (int index = 0; index < count; index += 4) { @@ -3325,15 +3358,11 @@ status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color float r = rects[index + 2]; float b = rects[index + 3]; - Vertex::set(vertex++, l, b); Vertex::set(vertex++, l, t); Vertex::set(vertex++, r, t); Vertex::set(vertex++, l, b); - Vertex::set(vertex++, r, t); Vertex::set(vertex++, r, b); - vertexCount += 6; - left = fminf(left, l); top = fminf(top, t); right = fmaxf(right, r); @@ -3356,13 +3385,12 @@ status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color setupDrawColorUniforms(); setupDrawShaderUniforms(); setupDrawColorFilterUniforms(); - setupDrawVertices((GLvoid*) &mesh[0].position[0]); if (dirty && hasLayer()) { dirtyLayer(left, top, right, bottom, currentTransform()); } - glDrawArrays(GL_TRIANGLES, 0, vertexCount); + drawIndexedQuads(&mesh[0], count / 4); return DrawGlInfo::kStatusDrew; } @@ -3398,19 +3426,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); } } @@ -3443,8 +3487,31 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b setupDrawMesh(vertices, texCoords, vbo); glDrawArrays(drawMode, 0, elementsCount); +} + +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) { - finishDrawTexture(); + 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); } void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, float bottom, @@ -3474,12 +3541,23 @@ void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, f setupDrawMesh(vertices, texCoords); glDrawArrays(drawMode, 0, elementsCount); - - finishDrawTexture(); } 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..9afb7ad 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,12 +45,21 @@ #include "Vertex.h" #include "SkiaShader.h" #include "SkiaColorFilter.h" +#include "UvMapper.h" #include "Caches.h" namespace android { namespace uirenderer { struct DrawModifiers { + DrawModifiers() { + reset(); + } + + void reset() { + memset(this, 0, sizeof(DrawModifiers)); + } + SkiaShader* mShader; SkiaColorFilter* mColorFilter; float mOverrideLayerAlpha; @@ -77,21 +88,21 @@ enum DrawOpMode { kDrawOpMode_Flush }; -struct DeferredDisplayState { - Rect mBounds; // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped. - - // the below are set and used by the OpenGLRenderer at record and deferred playback - bool mClipValid; - Rect mClip; - mat4 mMatrix; - DrawModifiers mDrawModifiers; - float mAlpha; +enum ClipSideFlags { + kClipSide_None = 0x0, + kClipSide_Left = 0x1, + kClipSide_Top = 0x2, + kClipSide_Right = 0x4, + kClipSide_Bottom = 0x8, + kClipSide_Full = 0xF, + kClipSide_ConservativeFull = 0x1F }; /////////////////////////////////////////////////////////////////////////////// // Renderer /////////////////////////////////////////////////////////////////////////////// +class DeferredDisplayState; class DisplayList; class TextSetupFunctor; class VertexBuffer; @@ -188,13 +199,23 @@ 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); virtual status_t callDrawGLFunction(Functor* functor, Rect& dirty); ANDROID_API void pushLayerUpdate(Layer* layer); + ANDROID_API void cancelLayerUpdate(Layer* layer); ANDROID_API void clearLayerUpdates(); + ANDROID_API void flushLayerUpdates(); ANDROID_API int getSaveCount() const; virtual int save(int flags); @@ -228,8 +249,32 @@ public: virtual void concatMatrix(SkMatrix* matrix); ANDROID_API const Rect& getClipBounds(); - ANDROID_API bool quickReject(float left, float top, float right, float bottom); - bool quickRejectNoScissor(float left, float top, float right, float bottom); + + /** + * Performs a quick reject but adjust the bounds to account for stroke width if necessary, + * and handling snapOut for AA geometry. + */ + bool quickRejectPreStroke(float left, float top, float right, float bottom, SkPaint* paint); + + /** + * Returns false and sets scissor based upon bounds if drawing won't be clipped out + */ + bool quickReject(float left, float top, float right, float bottom, bool snapOut = false); + bool quickReject(const Rect& bounds) { + return quickReject(bounds.left, bounds.top, bounds.right, bounds.bottom); + } + + /** + * Same as quickReject, without the scissor, instead returning clipRequired through pointer. + * clipRequired will be only set if not rejected + */ + ANDROID_API bool quickRejectNoScissor(float left, float top, float right, float bottom, + bool snapOut = false, bool* clipRequired = NULL); + bool quickRejectNoScissor(const Rect& bounds, bool* clipRequired = NULL) { + return quickRejectNoScissor(bounds.left, bounds.top, bounds.right, bounds.bottom, + clipRequired); + } + virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op); virtual bool clipPath(SkPath* path, SkRegion::Op op); virtual bool clipRegion(SkRegion* region, SkRegion::Op op); @@ -239,8 +284,8 @@ public: virtual void outputDisplayList(DisplayList* displayList); virtual status_t drawLayer(Layer* layer, float x, float y); virtual status_t drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint); - status_t drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureVertex* vertices, - const Rect& bounds, SkPaint* paint); + status_t drawBitmaps(SkBitmap* bitmap, AssetAtlas::Entry* entry, int bitmapCount, + TextureVertex* vertices, bool pureTranslate, 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,12 +293,12 @@ 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, + status_t drawPatches(SkBitmap* bitmap, AssetAtlas::Entry* entry, + TextureVertex* vertices, uint32_t indexCount, SkPaint* paint); + 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 Patch* mesh, AssetAtlas::Entry* entry, 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, - 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); virtual status_t drawRoundRect(float left, float top, float right, float bottom, @@ -270,7 +315,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); @@ -291,9 +336,15 @@ public: SkPaint* filterPaint(SkPaint* paint); + /** + * Store the current display state (most importantly, the current clip and transform), and + * additionally map the state's bounds from local to window coordinates. + * + * Returns true if quick-rejected + */ bool storeDisplayState(DeferredDisplayState& state, int stateDeferFlags); void restoreDisplayState(const DeferredDisplayState& state, bool skipClipRestore = false); - void setFullScreenClip(); + void setupMergedMultiDraw(const Rect* clipRect); const DrawModifiers& getDrawModifiers() { return mDrawModifiers; } void setDrawModifiers(const DrawModifiers& drawModifiers) { mDrawModifiers = drawModifiers; } @@ -311,6 +362,9 @@ public: return mSnapshot->clipRegion->isEmpty(); } + int getViewportWidth() { return getSnapshot()->viewport.getWidth(); } + int getViewportHeight() { return getSnapshot()->viewport.getHeight(); } + /** * Scales the alpha on the current snapshot. This alpha value will be modulated * with other alpha values when drawing primitives. @@ -356,7 +410,7 @@ public: return getXfermode(paint->getXfermode()); } - static inline int getAlphaDirect(SkPaint* paint) { + static inline int getAlphaDirect(const SkPaint* paint) { if (!paint) return 255; return paint->getAlpha(); } @@ -579,18 +633,6 @@ private: void setStencilFromClip(); /** - * Performs a quick reject but does not affect the scissor. Returns - * the transformed rect to test and the current clip. - */ - bool quickRejectNoScissor(float left, float top, float right, float bottom, - Rect& transformed, Rect& clip); - - /** - * Performs a quick reject but adjust the bounds to account for stroke width if necessary - */ - bool quickRejectPreStroke(float left, float top, float right, float bottom, SkPaint* paint); - - /** * Given the local bounds of the layer, calculates ... */ void calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool fboLayer); @@ -798,22 +840,35 @@ 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, bool ignoreTransform, bool ignoreScale = false, bool dirty = true); /** + * Draws the specified list of vertices as quads using indexed GL_TRIANGLES. + * If the number of vertices to draw exceeds the number of indices we have + * pre-allocated, this method will generate several glDrawElements() calls. + */ + void drawIndexedQuads(Vertex* mesh, GLsizei quadsCount); + + /** * Draws text underline and strike-through if needed. * * @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 +923,7 @@ private: * prior to calling this method. */ inline void bindTexture(GLuint texture) { - glBindTexture(GL_TEXTURE_2D, texture); + mCaches.bindTexture(texture); } /** @@ -876,7 +931,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 +966,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 +983,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,9 +996,8 @@ 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 setupDrawVertices(GLvoid* vertices); - void finishDrawTexture(); + void setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords, GLuint vbo = 0); + void setupDrawIndexedVertices(GLvoid* vertices); void accountForClear(SkXfermode::Mode mode); bool updateLayer(Layer* layer, bool inFrame); @@ -973,6 +1025,7 @@ private: void debugOverdraw(bool enable, bool clear); void renderOverdraw(); + void countOverdraw(); /** * Should be invoked every time the glScissor is modified. @@ -985,6 +1038,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 +1074,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,12 +1117,19 @@ 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; friend class DisplayListRenderer; friend class Layer; friend class TextSetupFunctor; + friend class DrawBitmapOp; + friend class DrawPatchOp; }; // class OpenGLRenderer diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp index 45c619e..9b023f9 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,58 @@ 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(): vertices(NULL), 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 width, float height, const Res_png_9patch* patch) { + UvMapper mapper; + return createMesh(bitmapWidth, bitmapHeight, width, height, 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 width, float height, const UvMapper& mapper, const Res_png_9patch* patch) { + if (vertices) return vertices; - bool matches = true; + int8_t emptyQuads = 0; + mColors = patch->colors; - 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; + const int8_t numColors = patch->numColors; + if (uint8_t(numColors) < sizeof(uint32_t) * 4) { + for (int8_t i = 0; i < numColors; i++) { + if (mColors[i] == 0x0) { + emptyQuads++; + } + } } - 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* tempVertices = new TextureVertex[maxVertices]; + TextureVertex* vertex = tempVertices; - // 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,29 +93,28 @@ 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; - const float xStretch = fmaxf(right - left - fixed, 0.0f); + const float xStretch = fmaxf(width - fixed, 0.0f); stretchX = xStretch / xStretchTex; - rescaleX = fixed == 0.0f ? 0.0f : fminf(fmaxf(right - left, 0.0f) / fixed, 1.0f); + rescaleX = fixed == 0.0f ? 0.0f : fminf(fmaxf(width, 0.0f) / fixed, 1.0f); } 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; - const float yStretch = fmaxf(bottom - top - fixed, 0.0f); + const float yStretch = fmaxf(height - fixed, 0.0f); stretchY = yStretch / yStretchTex; - rescaleY = fixed == 0.0f ? 0.0f : fminf(fmaxf(bottom - top, 0.0f) / fixed, 1.0f); + rescaleY = fixed == 0.0f ? 0.0f : fminf(fmaxf(height, 0.0f) / fixed, 1.0f); } - TextureVertex* vertex = mVertices; uint32_t quadCount = 0; float previousStepY = 0.0f; @@ -155,8 +123,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 +140,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, + width, bitmapWidth, quadCount); } y1 = y2; @@ -188,34 +151,25 @@ 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); + y2 = height; + generateRow(xDivs, xCount, vertex, y1, y2, v1, 1.0f, stretchX, rescaleX, + width, 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(); + if (verticesCount == maxVertices) { + vertices = tempVertices; + } else { + vertices = new TextureVertex[verticesCount]; + memcpy(vertices, tempVertices, verticesCount * sizeof(TextureVertex)); + delete[] tempVertices; } - 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 +177,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 +192,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 +203,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); } } @@ -275,11 +218,11 @@ void Patch::generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, f if (y2 < 0.0f) y2 = 0.0f; // Skip degenerate and transparent (empty) quads - if (((mColorKey >> oldQuadCount) & 0x1) || x1 >= x2 || y1 >= y2) { + if ((mColors[oldQuadCount] == 0) || x1 >= x2 || y1 >= y2) { #if DEBUG_PATCHES_EMPTY_VERTICES PATCH_LOGD(" quad %d (empty)", oldQuadCount); - PATCH_LOGD(" left, top = %.2f, %.2f\t\tu1, v1 = %.4f, %.4f", x1, y1, u1, v1); - PATCH_LOGD(" right, bottom = %.2f, %.2f\t\tu2, v2 = %.4f, %.4f", x2, y2, u2, v2); + PATCH_LOGD(" left, top = %.2f, %.2f\t\tu1, v1 = %.8f, %.8f", x1, y1, u1, v1); + PATCH_LOGD(" right, bottom = %.2f, %.2f\t\tu2, v2 = %.8f, %.8f", x2, y2, u2, v2); #endif return; } @@ -290,23 +233,20 @@ 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); - PATCH_LOGD(" left, top = %.2f, %.2f\t\tu1, v1 = %.4f, %.4f", x1, y1, u1, v1); - PATCH_LOGD(" right, bottom = %.2f, %.2f\t\tu2, v2 = %.4f, %.4f", x2, y2, u2, v2); + PATCH_LOGD(" left, top = %.2f, %.2f\t\tu1, v1 = %.8f, %.8f", x1, y1, u1, v1); + PATCH_LOGD(" right, bottom = %.2f, %.2f\t\tu2, v2 = %.8f, %.8f", x2, y2, u2, v2); #endif } diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h index ee7bf70..763a785 100644 --- a/libs/hwui/Patch.h +++ b/libs/hwui/Patch.h @@ -23,62 +23,51 @@ #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); + /** + * Returns the size of this patch's mesh in bytes. + */ + uint32_t getSize() const; - 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); - - GLuint meshBuffer; + TextureVertex* vertices; 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 width, float height, const Res_png_9patch* patch); + TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight, + float width, float height, 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* mColors; + UvMapper mUvMapper; }; // struct Patch }; // namespace uirenderer diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp index 62e38d3..dc0d98c 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,111 +30,243 @@ 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), mFreeBlocks(NULL), 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(); } +void PatchCache::init(Caches& caches) { + bool created = false; + if (!mMeshBuffer) { + glGenBuffers(1, &mMeshBuffer); + created = true; + } + + caches.bindMeshBuffer(mMeshBuffer); + caches.resetVertexPointers(); + + if (created) { + createVertexBuffer(); + } +} + /////////////////////////////////////////////////////////////////////////////// // 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; +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); +} - if (lhs.pixelWidth < rhs.pixelWidth) return -1; - if (lhs.pixelWidth > rhs.pixelWidth) return +1; +int PatchCache::PatchDescription::compare(const PatchCache::PatchDescription& lhs, + const PatchCache::PatchDescription& rhs) { + return memcmp(&lhs, &rhs, sizeof(PatchDescription)); +} - if (lhs.pixelHeight < rhs.pixelHeight) return -1; - if (lhs.pixelHeight > rhs.pixelHeight) return +1; +void PatchCache::clear() { + clearCache(); - deltaInt = lhs.xCount - rhs.xCount; - if (deltaInt != 0) return deltaInt; + if (mMeshBuffer) { + Caches::getInstance().unbindMeshBuffer(); + glDeleteBuffers(1, &mMeshBuffer); + mMeshBuffer = 0; + mSize = 0; + } +} - deltaInt = lhs.yCount - rhs.yCount; - if (deltaInt != 0) return deltaInt; +void PatchCache::clearCache() { + LruCache<PatchDescription, Patch*>::Iterator i(mCache); + while (i.next()) { + delete i.value(); + } + mCache.clear(); - deltaInt = lhs.emptyCount - rhs.emptyCount; - if (deltaInt != 0) return deltaInt; + BufferBlock* block = mFreeBlocks; + while (block) { + BufferBlock* next = block->next; + delete block; + block = next; + } + mFreeBlocks = NULL; +} - deltaInt = lhs.colorKey - rhs.colorKey; - if (deltaInt != 0) return deltaInt; +void PatchCache::remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch) { + LruCache<PatchDescription, Patch*>::Iterator i(mCache); + while (i.next()) { + const PatchDescription& key = i.key(); + if (key.getPatch() == patch) { + patchesToRemove.push(patch_pair_t(&key, i.value())); + } + } +} - return 0; +void PatchCache::removeDeferred(Res_png_9patch* patch) { + Mutex::Autolock _l(mLock); + mGarbage.push(patch); } -void PatchCache::clear() { - size_t count = mCache.size(); - for (size_t i = 0; i < count; i++) { - delete mCache.valueAt(i); +void PatchCache::clearGarbage() { + Vector<patch_pair_t> patchesToRemove; + + { // scope for the mutex + Mutex::Autolock _l(mLock); + size_t count = mGarbage.size(); + for (size_t i = 0; i < count; i++) { + remove(patchesToRemove, mGarbage[i]); + } + mGarbage.clear(); } - mCache.clear(); + + // TODO: We could sort patchesToRemove by offset to merge + // adjacent free blocks + for (size_t i = 0; i < patchesToRemove.size(); i++) { + const patch_pair_t& pair = patchesToRemove[i]; + + // Add a new free block to the list + const Patch* patch = pair.getSecond(); + BufferBlock* block = new BufferBlock(patch->offset, patch->getSize()); + block->next = mFreeBlocks; + mFreeBlocks = block; + + mSize -= patch->getSize(); + + mCache.remove(*pair.getFirst()); + } + +#if DEBUG_PATCHES + if (patchesToRemove.size() > 0) { + dumpFreeBlocks("Removed garbage"); + } +#endif +} + +void PatchCache::createVertexBuffer() { + glBufferData(GL_ARRAY_BUFFER, mMaxSize, NULL, GL_DYNAMIC_DRAW); + mSize = 0; + mFreeBlocks = new BufferBlock(0, mMaxSize); + mGenerationId++; } -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) { +/** + * Sets the mesh's offsets and copies its associated vertices into + * the mesh buffer (VBO). + */ +void PatchCache::setupMesh(Patch* newMesh, TextureVertex* vertices) { + // This call ensures the VBO exists and that it is bound + init(Caches::getInstance()); - int8_t transparentQuads = 0; - uint32_t colorKey = 0; + // If we're running out of space, let's clear the entire cache + uint32_t size = newMesh->getSize(); + if (mSize + size > mMaxSize) { + clearCache(); + createVertexBuffer(); + } - if (uint8_t(numColors) < sizeof(uint32_t) * 4) { - for (int8_t i = 0; i < numColors; i++) { - if (colors[i] == 0x0) { - transparentQuads++; - colorKey |= 0x1 << i; - } + // Find a block where we can fit the mesh + BufferBlock* previous = NULL; + BufferBlock* block = mFreeBlocks; + while (block) { + // The mesh fits + if (block->size >= size) { + break; } + previous = block; + block = block->next; } - // If the 9patch is made of only transparent quads - if (transparentQuads == int8_t((width + 1) * (height + 1))) { - return NULL; + // We have enough space left in the buffer, but it's + // too fragmented, let's clear the cache + if (!block) { + clearCache(); + createVertexBuffer(); + previous = NULL; + block = mFreeBlocks; } - const PatchDescription description(bitmapWidth, bitmapHeight, - pixelWidth, pixelHeight, width, height, transparentQuads, colorKey); + // Copy the 9patch mesh in the VBO + newMesh->offset = (GLintptr) (block->offset); + newMesh->textureOffset = newMesh->offset + gMeshTextureOffset; + glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices); - ssize_t index = mCache.indexOfKey(description); - Patch* mesh = NULL; - if (index >= 0) { - mesh = mCache.valueAt(index); + // Remove the block since we've used it entirely + if (block->size == size) { + if (previous) { + previous->next = block->next; + } else { + mFreeBlocks = block->next; + } + } else { + // Resize the block now that it's occupied + block->offset += size; + block->size -= size; } + mSize += size; +} + +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) { + + const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch); + const Patch* mesh = mCache.get(description); + 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); + Patch* newMesh = new Patch(); + TextureVertex* vertices; + + if (entry) { + // An atlas entry has a UV mapper + vertices = newMesh->createMesh(bitmapWidth, bitmapHeight, + pixelWidth, pixelHeight, entry->uvMapper, patch); + } else { + vertices = newMesh->createMesh(bitmapWidth, bitmapHeight, + pixelWidth, pixelHeight, patch); } - 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); + if (vertices) { + setupMesh(newMesh, vertices); + } + +#if DEBUG_PATCHES + dumpFreeBlocks("Adding patch"); +#endif + + mCache.put(description, newMesh); + return newMesh; } return mesh; } +#if DEBUG_PATCHES +void PatchCache::dumpFreeBlocks(const char* prefix) { + String8 dump; + BufferBlock* block = mFreeBlocks; + while (block) { + dump.appendFormat("->(%d, %d)", block->offset, block->size); + block = block->next; + } + ALOGD("%s: Free blocks%s", prefix, dump.string()); +} +#endif + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h index 0822cba..9f2c9a5 100644 --- a/libs/hwui/PatchCache.h +++ b/libs/hwui/PatchCache.h @@ -17,10 +17,16 @@ #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" +#include "utils/Pair.h" namespace android { namespace uirenderer { @@ -40,45 +46,64 @@ 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. + * Removes the entries associated with the specified 9-patch. This is meant + * to be called from threads that are not the EGL context thread (GC thread + * on the VM side for instance.) */ + void removeDeferred(Res_png_9patch* patch); + + /** + * Process deferred removals. + */ + void clearGarbage(); + + +private: 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; + + const Res_png_9patch* getPatch() const { return mPatch; } + static int compare(const PatchDescription& lhs, const PatchDescription& rhs); bool operator==(const PatchDescription& other) const { @@ -99,21 +124,63 @@ 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; + /** + * A buffer block represents an empty range in the mesh buffer + * that can be used to store vertices. + * + * The patch cache maintains a linked-list of buffer blocks + * to track available regions of memory in the VBO. + */ + struct BufferBlock { + BufferBlock(uint32_t offset, uint32_t size): offset(offset), size(size), next(NULL) { + } + + uint32_t offset; + uint32_t size; + + BufferBlock* next; + }; // struct BufferBlock + + typedef Pair<const PatchDescription*, Patch*> patch_pair_t; + + void clearCache(); + void createVertexBuffer(); + + void setupMesh(Patch* newMesh, TextureVertex* vertices); + + void remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch); + +#if DEBUG_PATCHES + void dumpFreeBlocks(const char* prefix); +#endif + + uint32_t mMaxSize; + uint32_t mSize; + + LruCache<PatchDescription, Patch*> mCache; + + GLuint mMeshBuffer; + // First available free block inside the mesh buffer + BufferBlock* mFreeBlocks; + + uint32_t mGenerationId; + // Garbage tracking, required to handle GC events on the VM side + Vector<Res_png_9patch*> mGarbage; + mutable Mutex mLock; }; // class PatchCache }; // namespace uirenderer diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index fdb10e2..5df6408 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; @@ -214,7 +214,22 @@ void PathCache::operator()(PathDescription& entry, PathTexture*& texture) { void PathCache::removeTexture(PathTexture* texture) { if (texture) { const uint32_t size = texture->width * texture->height; - mSize -= size; + + // If there is a pending task we must wait for it to return + // before attempting our cleanup + const sp<Task<SkBitmap*> >& task = texture->task(); + if (task != NULL) { + SkBitmap* bitmap = task->getResult(); + texture->clearTask(); + } else { + // If there is a pending task, the path was not added + // to the cache and the size wasn't increased + if (size > mSize) { + ALOGE("Removing path texture of size %d will leave " + "the cache in an inconsistent state", size); + } + mSize -= size; + } PATH_LOGD("PathCache::delete name, size, mSize = %d, %d, %d", texture->id, size, mSize); @@ -223,7 +238,7 @@ void PathCache::removeTexture(PathTexture* texture) { } if (texture->id) { - glDeleteTextures(1, &texture->id); + Caches::getInstance().deleteTexture(texture->id); } delete texture; } @@ -283,6 +298,11 @@ void PathCache::generateTexture(const PathDescription& entry, SkBitmap* bitmap, mCache.put(entry, texture); } } else { + // It's okay to add a texture that's bigger than the cache since + // we'll trim the cache later when addToCache is set to false + if (!addToCache) { + mSize += size; + } texture->cleanup = true; } } @@ -300,7 +320,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); @@ -350,8 +370,7 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) { // Paths /////////////////////////////////////////////////////////////////////////////// -void PathCache::remove(const path_pair_t& pair) { - Vector<PathDescription> pathsToRemove; +void PathCache::remove(Vector<PathDescription>& pathsToRemove, const path_pair_t& pair) { LruCache<PathDescription, PathTexture*>::Iterator i(mCache); while (i.next()) { @@ -362,10 +381,6 @@ void PathCache::remove(const path_pair_t& pair) { pathsToRemove.push(key); } } - - for (size_t i = 0; i < pathsToRemove.size(); i++) { - mCache.remove(pathsToRemove.itemAt(i)); - } } void PathCache::removeDeferred(SkPath* path) { @@ -374,12 +389,20 @@ void PathCache::removeDeferred(SkPath* path) { } void PathCache::clearGarbage() { - Mutex::Autolock l(mLock); - size_t count = mGarbage.size(); - for (size_t i = 0; i < count; i++) { - remove(mGarbage.itemAt(i)); + Vector<PathDescription> pathsToRemove; + + { // scope for the mutex + Mutex::Autolock l(mLock); + size_t count = mGarbage.size(); + for (size_t i = 0; i < count; i++) { + remove(pathsToRemove, mGarbage.itemAt(i)); + } + mGarbage.clear(); + } + + for (size_t i = 0; i < pathsToRemove.size(); i++) { + mCache.remove(pathsToRemove.itemAt(i)); } - mGarbage.clear(); } /** diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h index dd1f996..16d20a8 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() { @@ -269,7 +269,7 @@ private: * Removes an entry. * The pair must define first=path, second=sourcePath */ - void remove(const path_pair_t& pair); + void remove(Vector<PathDescription>& pathsToRemove, const path_pair_t& pair); /** * Ensures there is enough space in the cache for a texture of the specified 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..36e89c6 100644 --- a/libs/hwui/PixelBuffer.cpp +++ b/libs/hwui/PixelBuffer.cpp @@ -19,6 +19,7 @@ #include <utils/Log.h> #include "Caches.h" +#include "Debug.h" #include "Extensions.h" #include "PixelBuffer.h" #include "Properties.h" @@ -113,6 +114,14 @@ uint8_t* GpuPixelBuffer::map(AccessMode mode) { if (mAccessMode == kAccessMode_None) { mCaches.bindPixelBuffer(mBuffer); mMappedPointer = (uint8_t*) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, getSize(), mode); +#if DEBUG_OPENGL + if (!mMappedPointer) { + GLenum status = GL_NO_ERROR; + while ((status = glGetError()) != GL_NO_ERROR) { + ALOGE("Could not map GPU pixel buffer: 0x%x", status); + } + } +#endif mAccessMode = mode; } @@ -123,7 +132,10 @@ void GpuPixelBuffer::unmap() { if (mAccessMode != kAccessMode_None) { if (mMappedPointer) { mCaches.bindPixelBuffer(mBuffer); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + GLboolean status = glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + if (status == GL_FALSE) { + ALOGE("Corrupted GPU pixel buffer"); + } } mAccessMode = kAccessMode_None; mMappedPointer = NULL; @@ -147,14 +159,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/PixelBuffer.h b/libs/hwui/PixelBuffer.h index 32d5417..9725a61 100644 --- a/libs/hwui/PixelBuffer.h +++ b/libs/hwui/PixelBuffer.h @@ -112,13 +112,25 @@ public: virtual uint8_t* getMappedPointer() const = 0; /** - * Upload the specified rectangle of this pixe buffer as a + * Upload the specified rectangle of this pixel buffer as a * GL_TEXTURE_2D texture. Calling this method will trigger * an unmap() if necessary. */ virtual void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) = 0; /** + * Upload the specified rectangle of this pixel buffer as a + * GL_TEXTURE_2D texture. Calling this method will trigger + * an unmap() if necessary. + * + * This is a convenience function provided to save callers the + * trouble of computing the offset parameter. + */ + void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height) { + upload(x, y, width, height, getOffset(x, y)); + } + + /** * Returns the width of the render buffer in pixels. */ uint32_t getWidth() const { @@ -140,6 +152,13 @@ public: } /** + * Returns the offset of a pixel in this pixel buffer, in bytes. + */ + uint32_t getOffset(uint32_t x, uint32_t y) const { + return (y * mWidth + x) * formatSize(mFormat); + } + + /** * Returns the number of bytes per pixel in the specified format. * * Supported formats: diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp index 14a2376..7814a01 100644 --- a/libs/hwui/Program.cpp +++ b/libs/hwui/Program.cpp @@ -15,8 +15,12 @@ */ #define LOG_TAG "OpenGLRenderer" +#define ATRACE_TAG ATRACE_TAG_VIEW + +#include <utils/Trace.h> #include "Program.h" +#include "Vertex.h" namespace android { namespace uirenderer { @@ -25,7 +29,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 +53,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 +92,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 +140,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 +163,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(Vertex::gGeometryFudgeFactor, Vertex::gGeometryFudgeFactor); + 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..20b8f2f 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -70,10 +70,23 @@ enum DebugLevel { #define PROPERTY_DEBUG_LAYERS_UPDATES "debug.hwui.show_layers_updates" /** - * Used to enable/disable overdraw debugging. The accepted values are - * "true" and "false". The default value is "false". + * Used to enable/disable overdraw debugging. + * + * The accepted values are + * "show", to show overdraw + * "show_deuteranomaly", to show overdraw if you suffer from Deuteranomaly + * "count", to show an overdraw counter + * "false", to disable overdraw debugging + * + * The default value is "false". + */ +#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.overdraw" + +/** + * Used to enable/disable PerfHUD ES profiling. The accepted values + * are "true" and "false". The default value is "false". */ -#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.show_overdraw" +#define PROPERTY_DEBUG_NV_PROFILING "debug.hwui.nv_profiling" /** * Used to enable/disable non-rectangular clipping debugging. @@ -123,9 +136,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 +146,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 +192,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 +209,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/Query.h b/libs/hwui/Query.h new file mode 100644 index 0000000..e25b16b --- /dev/null +++ b/libs/hwui/Query.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_QUERY_H +#define ANDROID_HWUI_QUERY_H + +#include <GLES3/gl3.h> + +#include "Extensions.h" + +namespace android { +namespace uirenderer { + +/** + * A Query instance can be used to perform occlusion queries. If the device + * does not support occlusion queries, the result of a query will always be + * 0 and the result will always be marked available. + * + * To run an occlusion query successfully, you must start end end the query: + * + * Query query; + * query.begin(); + * // execute OpenGL calls + * query.end(); + * GLuint result = query.getResult(); + */ +class Query { +public: + /** + * Possible query targets. + */ + enum Target { + /** + * Indicates if any sample passed the depth & stencil tests. + */ + kTargetSamples = GL_ANY_SAMPLES_PASSED, + /** + * Indicates if any sample passed the depth & stencil tests. + * The implementation may choose to use a less precise version + * of the test, potentially resulting in false positives. + */ + kTargetConservativeSamples = GL_ANY_SAMPLES_PASSED_CONSERVATIVE, + }; + + /** + * Creates a new query with the specified target. The default + * target is kTargetSamples (of GL_ANY_SAMPLES_PASSED in OpenGL.) + */ + Query(Target target = kTargetSamples): mActive(false), mTarget(target), + mCanQuery(Extensions::getInstance().hasOcclusionQueries()), + mQuery(0) { + } + + ~Query() { + if (mQuery) { + glDeleteQueries(1, &mQuery); + } + } + + /** + * Begins the query. If the query has already begun or if the device + * does not support occlusion queries, calling this method as no effect. + * After calling this method successfully, the query is marked active. + */ + void begin() { + if (!mActive && mCanQuery) { + if (!mQuery) { + glGenQueries(1, &mQuery); + } + + glBeginQuery(mTarget, mQuery); + mActive = true; + } + } + + /** + * Ends the query. If the query has already begun or if the device + * does not support occlusion queries, calling this method as no effect. + * After calling this method successfully, the query is marked inactive. + */ + void end() { + if (mQuery && mActive) { + glEndQuery(mTarget); + mActive = false; + } + } + + /** + * Returns true if the query is active, false otherwise. + */ + bool isActive() { + return mActive; + } + + /** + * Returns true if the result of the query is available, + * false otherwise. Calling getResult() before the result + * is available may result in the calling thread being blocked. + * If the device does not support queries, this method always + * returns true. + */ + bool isResultAvailable() { + if (!mQuery) return true; + + GLuint result; + glGetQueryObjectuiv(mQuery, GL_QUERY_RESULT_AVAILABLE, &result); + return result == GL_TRUE; + } + + /** + * Returns the result of the query. If the device does not + * support queries this method will return 0. + * + * Calling this method implicitely calls end() if the query + * is currently active. + */ + GLuint getResult() { + if (!mQuery) return 0; + + end(); + + GLuint result; + glGetQueryObjectuiv(mQuery, GL_QUERY_RESULT, &result); + return result; + } + + +private: + bool mActive; + GLenum mTarget; + bool mCanQuery; + GLuint mQuery; + +}; // class Query + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_QUERY_H diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index f50ac3c..dabd8d4 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -21,9 +21,15 @@ #include <utils/Log.h> +#include "Vertex.h" + namespace android { namespace uirenderer { +#define RECT_STRING "%7.2f %7.2f %7.2f %7.2f" +#define RECT_ARGS(r) \ + (r).left, (r).top, (r).right, (r).bottom + /////////////////////////////////////////////////////////////////////////////// // Structs /////////////////////////////////////////////////////////////////////////////// @@ -125,11 +131,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); } @@ -166,6 +172,40 @@ public: bottom += delta; } + /** + * Similar to snapToPixelBoundaries, but estimates bounds conservatively to handle GL rounding + * errors. + * + * This function should be used whenever estimating the damage rect of geometry already mapped + * into layer space. + */ + void snapGeometryToPixelBoundaries(bool snapOut) { + if (snapOut) { + /* For AA geometry with a ramp perimeter, don't snap by rounding - AA geometry will have + * a 0.5 pixel perimeter not accounted for in its bounds. Instead, snap by + * conservatively rounding out the bounds with floor/ceil. + * + * In order to avoid changing integer bounds with floor/ceil due to rounding errors + * inset the bounds first by the fudge factor. Very small fraction-of-a-pixel errors + * from this inset will only incur similarly small errors in output, due to transparency + * in extreme outside of the geometry. + */ + left = floorf(left + Vertex::gGeometryFudgeFactor); + top = floorf(top + Vertex::gGeometryFudgeFactor); + right = ceilf(right - Vertex::gGeometryFudgeFactor); + bottom = ceilf(bottom - Vertex::gGeometryFudgeFactor); + } else { + /* For other geometry, we do the regular rounding in order to snap, but also outset the + * bounds by a fudge factor. This ensures that ambiguous geometry (e.g. a non-AA Rect + * with top left at (0.5, 0.5)) will err on the side of a larger damage rect. + */ + left = floorf(left + 0.5f - Vertex::gGeometryFudgeFactor); + top = floorf(top + 0.5f - Vertex::gGeometryFudgeFactor); + right = floorf(right + 0.5f + Vertex::gGeometryFudgeFactor); + bottom = floorf(bottom + 0.5f + Vertex::gGeometryFudgeFactor); + } + } + void snapToPixelBoundaries() { left = floorf(left + 0.5f); top = floorf(top + 0.5f); diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp index 347bd78..3f77021 100644 --- a/libs/hwui/ResourceCache.cpp +++ b/libs/hwui/ResourceCache.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "OpenGLRenderer" + #include <SkPixelRef.h> #include "ResourceCache.h" #include "Caches.h" @@ -60,7 +62,7 @@ void ResourceCache::incrementRefcount(void* resource, ResourceType resourceType) } void ResourceCache::incrementRefcount(SkBitmap* bitmapResource) { - SkSafeRef(bitmapResource->pixelRef()); + bitmapResource->pixelRef()->globalRef(); SkSafeRef(bitmapResource->getColorTable()); incrementRefcount((void*) bitmapResource, kBitmap); } @@ -79,6 +81,10 @@ void ResourceCache::incrementRefcount(SkiaColorFilter* filterResource) { incrementRefcount((void*) filterResource, kColorFilter); } +void ResourceCache::incrementRefcount(Res_png_9patch* patchResource) { + incrementRefcount((void*) patchResource, kNinePatch); +} + void ResourceCache::incrementRefcount(Layer* layerResource) { incrementRefcount((void*) layerResource, kLayer); } @@ -94,7 +100,7 @@ void ResourceCache::incrementRefcountLocked(void* resource, ResourceType resourc } void ResourceCache::incrementRefcountLocked(SkBitmap* bitmapResource) { - SkSafeRef(bitmapResource->pixelRef()); + bitmapResource->pixelRef()->globalRef(); SkSafeRef(bitmapResource->getColorTable()); incrementRefcountLocked((void*) bitmapResource, kBitmap); } @@ -113,6 +119,10 @@ void ResourceCache::incrementRefcountLocked(SkiaColorFilter* filterResource) { incrementRefcountLocked((void*) filterResource, kColorFilter); } +void ResourceCache::incrementRefcountLocked(Res_png_9patch* patchResource) { + incrementRefcountLocked((void*) patchResource, kNinePatch); +} + void ResourceCache::incrementRefcountLocked(Layer* layerResource) { incrementRefcountLocked((void*) layerResource, kLayer); } @@ -123,7 +133,7 @@ void ResourceCache::decrementRefcount(void* resource) { } void ResourceCache::decrementRefcount(SkBitmap* bitmapResource) { - SkSafeUnref(bitmapResource->pixelRef()); + bitmapResource->pixelRef()->globalUnref(); SkSafeUnref(bitmapResource->getColorTable()); decrementRefcount((void*) bitmapResource); } @@ -142,6 +152,10 @@ void ResourceCache::decrementRefcount(SkiaColorFilter* filterResource) { decrementRefcount((void*) filterResource); } +void ResourceCache::decrementRefcount(Res_png_9patch* patchResource) { + decrementRefcount((void*) patchResource); +} + void ResourceCache::decrementRefcount(Layer* layerResource) { decrementRefcount((void*) layerResource); } @@ -160,7 +174,7 @@ void ResourceCache::decrementRefcountLocked(void* resource) { } void ResourceCache::decrementRefcountLocked(SkBitmap* bitmapResource) { - SkSafeUnref(bitmapResource->pixelRef()); + bitmapResource->pixelRef()->globalUnref(); SkSafeUnref(bitmapResource->getColorTable()); decrementRefcountLocked((void*) bitmapResource); } @@ -179,6 +193,10 @@ void ResourceCache::decrementRefcountLocked(SkiaColorFilter* filterResource) { decrementRefcountLocked((void*) filterResource); } +void ResourceCache::decrementRefcountLocked(Res_png_9patch* patchResource) { + decrementRefcountLocked((void*) patchResource); +} + void ResourceCache::decrementRefcountLocked(Layer* layerResource) { decrementRefcountLocked((void*) layerResource); } @@ -265,6 +283,30 @@ void ResourceCache::destructorLocked(SkiaColorFilter* resource) { } } +void ResourceCache::destructor(Res_png_9patch* resource) { + Mutex::Autolock _l(mLock); + destructorLocked(resource); +} + +void ResourceCache::destructorLocked(Res_png_9patch* resource) { + ssize_t index = mCache->indexOfKey(resource); + ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL; + if (ref == NULL) { + if (Caches::hasInstance()) { + Caches::getInstance().patchCache.removeDeferred(resource); + } + // If we're not tracking this resource, just delete it + // A Res_png_9patch is actually an array of byte that's larger + // than sizeof(Res_png_9patch). It must be freed as an array. + delete[] (int8_t*) resource; + return; + } + ref->destroyed = true; + if (ref->refCount == 0) { + deleteResourceReferenceLocked(resource, ref); + } +} + /** * Return value indicates whether resource was actually recycled, which happens when RefCnt * reaches 0. @@ -335,6 +377,16 @@ void ResourceCache::deleteResourceReferenceLocked(void* resource, ResourceRefere delete filter; } break; + case kNinePatch: { + if (Caches::hasInstance()) { + Caches::getInstance().patchCache.removeDeferred((Res_png_9patch*) resource); + } + // A Res_png_9patch is actually an array of byte that's larger + // than sizeof(Res_png_9patch). It must be freed as an array. + int8_t* patch = (int8_t*) resource; + delete[] patch; + } + break; case kLayer: { Layer* layer = (Layer*) resource; Caches::getInstance().deleteLayerDeferred(layer); diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h index ab493e5..ea0c1b5 100644 --- a/libs/hwui/ResourceCache.h +++ b/libs/hwui/ResourceCache.h @@ -22,7 +22,11 @@ #include <SkBitmap.h> #include <SkiaColorFilter.h> #include <SkiaShader.h> + #include <utils/KeyedVector.h> + +#include <androidfw/ResourceTypes.h> + #include "Layer.h" namespace android { @@ -35,6 +39,7 @@ enum ResourceType { kBitmap, kShader, kColorFilter, + kNinePatch, kPath, kLayer }; @@ -69,35 +74,41 @@ public: void incrementRefcount(SkBitmap* resource); void incrementRefcount(SkiaShader* resource); void incrementRefcount(SkiaColorFilter* resource); + void incrementRefcount(Res_png_9patch* resource); void incrementRefcount(Layer* resource); void incrementRefcountLocked(SkPath* resource); void incrementRefcountLocked(SkBitmap* resource); void incrementRefcountLocked(SkiaShader* resource); void incrementRefcountLocked(SkiaColorFilter* resource); + void incrementRefcountLocked(Res_png_9patch* resource); void incrementRefcountLocked(Layer* resource); void decrementRefcount(SkBitmap* resource); void decrementRefcount(SkPath* resource); void decrementRefcount(SkiaShader* resource); void decrementRefcount(SkiaColorFilter* resource); + void decrementRefcount(Res_png_9patch* resource); void decrementRefcount(Layer* resource); void decrementRefcountLocked(SkBitmap* resource); void decrementRefcountLocked(SkPath* resource); void decrementRefcountLocked(SkiaShader* resource); void decrementRefcountLocked(SkiaColorFilter* resource); + void decrementRefcountLocked(Res_png_9patch* resource); void decrementRefcountLocked(Layer* resource); void destructor(SkPath* resource); void destructor(SkBitmap* resource); void destructor(SkiaShader* resource); void destructor(SkiaColorFilter* resource); + void destructor(Res_png_9patch* resource); void destructorLocked(SkPath* resource); void destructorLocked(SkBitmap* resource); void destructorLocked(SkiaShader* resource); void destructorLocked(SkiaColorFilter* resource); + void destructorLocked(Res_png_9patch* resource); bool recycle(SkBitmap* resource); bool recycleLocked(SkBitmap* resource); 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/Stencil.cpp b/libs/hwui/Stencil.cpp index ba2e6f2..2764523 100644 --- a/libs/hwui/Stencil.cpp +++ b/libs/hwui/Stencil.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "Debug.h" #include "Extensions.h" #include "Properties.h" #include "Stencil.h" 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..ed0a79a 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,25 +235,25 @@ 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: glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(), texture->height, - GL_UNSIGNED_BYTE, bitmap->getPixels()); + uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(), + texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels()); texture->blend = true; break; case SkBitmap::kRGB_565_Config: glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel()); - uploadToTexture(resize, GL_RGB, bitmap->rowBytesAsPixels(), texture->height, - GL_UNSIGNED_SHORT_5_6_5, bitmap->getPixels()); + uploadToTexture(resize, GL_RGB, bitmap->rowBytesAsPixels(), + texture->width, texture->height, GL_UNSIGNED_SHORT_5_6_5, bitmap->getPixels()); texture->blend = false; break; case SkBitmap::kARGB_8888_Config: glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel()); - uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(), texture->height, - GL_UNSIGNED_BYTE, bitmap->getPixels()); + uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(), + texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels()); // Do this after calling getPixels() to make sure Skia's deferred // decoding happened texture->blend = !bitmap->isOpaque(); @@ -293,17 +293,28 @@ void TextureCache::uploadLoFiTexture(bool resize, SkBitmap* bitmap, SkCanvas canvas(rgbaBitmap); canvas.drawBitmap(*bitmap, 0.0f, 0.0f, NULL); - uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), height, + uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), width, height, GL_UNSIGNED_BYTE, rgbaBitmap.getPixels()); } -void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei width, GLsizei height, - GLenum type, const GLvoid * data) { +void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei stride, + GLsizei width, GLsizei height, GLenum type, const GLvoid * data) { + // TODO: With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer + // if the stride doesn't match the width + const bool useStride = stride != width && Extensions::getInstance().hasUnpackRowLength(); + if (useStride) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); + } + if (resize) { glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data); } else { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data); } + + if (useStride) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + } } }; // namespace uirenderer diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h index 80bb22e..57fc19a 100644 --- a/libs/hwui/TextureCache.h +++ b/libs/hwui/TextureCache.h @@ -125,8 +125,8 @@ private: void generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate = false); void uploadLoFiTexture(bool resize, SkBitmap* bitmap, uint32_t width, uint32_t height); - void uploadToTexture(bool resize, GLenum format, GLsizei width, GLsizei height, - GLenum type, const GLvoid * data); + void uploadToTexture(bool resize, GLenum format, GLsizei stride, + GLsizei width, GLsizei height, GLenum type, const GLvoid * data); void init(); 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..790d4fc 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 { @@ -24,12 +26,30 @@ namespace uirenderer { * Simple structure to describe a vertex with a position and a texture. */ struct Vertex { + /** + * Fudge-factor used to disambiguate geometry pixel positioning. + * + * Used to offset lines and points to avoid ambiguous intersection with pixel centers (see + * Program::set()), and used to make geometry damage rect calculation conservative (see + * Rect::snapGeometryToPixelBoundaries()) + */ + static const float gGeometryFudgeFactor = 0.0656f; + float position[2]; static inline void set(Vertex* vertex, float x, float y) { 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 +101,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..d5f38b5 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" @@ -107,17 +108,18 @@ CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) // CacheTexture /////////////////////////////////////////////////////////////////////////////// -CacheTexture::CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount) : - mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), +CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount) : + mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), mFormat(format), 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); // OpenGL ES 3.0+ lets us specify the row length for unpack operations such // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture. // With OpenGL ES 2.0 we have to upload entire stripes instead. - mHasES3 = Extensions::getInstance().getMajorGlVersion() >= 3; + mHasUnpackRowLength = Extensions::getInstance().hasUnpackRowLength(); } CacheTexture::~CacheTexture() { @@ -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); } @@ -180,17 +182,17 @@ void CacheTexture::allocateMesh() { void CacheTexture::allocateTexture() { if (!mTexture) { - mTexture = PixelBuffer::create(GL_ALPHA, mWidth, mHeight); + mTexture = PixelBuffer::create(mFormat, mWidth, mHeight); } 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, - GL_ALPHA, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0, + mFormat, GL_UNSIGNED_BYTE, 0); const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); @@ -204,22 +206,21 @@ void CacheTexture::allocateTexture() { bool CacheTexture::upload() { const Rect& dirtyRect = mDirtyRect; - uint32_t x = mHasES3 ? dirtyRect.left : 0; + uint32_t x = mHasUnpackRowLength ? dirtyRect.left : 0; uint32_t y = dirtyRect.top; - uint32_t width = mHasES3 ? dirtyRect.getWidth() : mWidth; + uint32_t width = mHasUnpackRowLength ? dirtyRect.getWidth() : mWidth; uint32_t height = dirtyRect.getHeight(); // The unpack row length only needs to be specified when a new // texture is bound - if (mHasES3) { + if (mHasUnpackRowLength) { glPixelStorei(GL_UNPACK_ROW_LENGTH, mWidth); } - mTexture->upload(x, y, width, height, y * mWidth + x); - + mTexture->upload(x, y, width, height); setDirty(false); - return mHasES3; + return mHasUnpackRowLength; } void CacheTexture::setDirty(bool dirty) { @@ -230,6 +231,32 @@ void CacheTexture::setDirty(bool dirty) { } bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) { + switch (glyph.fMaskFormat) { + case SkMask::kA8_Format: + case SkMask::kBW_Format: + if (mFormat != GL_ALPHA) { +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: texture format %x is inappropriate for monochromatic glyphs", + mFormat); +#endif + return false; + } + break; + case SkMask::kARGB32_Format: + if (mFormat != GL_RGBA) { +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: texture format %x is inappropriate for colour glyphs", mFormat); +#endif + return false; + } + break; + default: +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: unknown glyph format %x encountered", glyph.fMaskFormat); +#endif + return false; + } + if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) { return false; } diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h index ddcc836..61b38f8 100644 --- a/libs/hwui/font/CacheTexture.h +++ b/libs/hwui/font/CacheTexture.h @@ -24,13 +24,14 @@ #include <utils/Log.h> #include "FontUtil.h" +#include "../PixelBuffer.h" #include "../Rect.h" #include "../Vertex.h" namespace android { namespace uirenderer { -class PixelBuffer; +class Caches; /** * CacheBlock is a node in a linked list of current free space areas in a CacheTexture. @@ -73,7 +74,7 @@ struct CacheBlock { class CacheTexture { public: - CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount); + CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount); ~CacheTexture(); void reset(); @@ -99,6 +100,14 @@ public: return mHeight; } + inline GLenum getFormat() const { + return mFormat; + } + + inline uint32_t getOffset(uint16_t x, uint16_t y) const { + return (y * mWidth + x) * PixelBuffer::formatSize(mFormat); + } + inline const Rect* getDirtyRect() const { return &mDirtyRect; } @@ -150,9 +159,9 @@ public: float x3, float y3, float u3, float v3, float x4, float y4, float u4, float v4) { TextureVertex* mesh = mMesh + mCurrentQuad * 4; - TextureVertex::set(mesh++, x1, y1, u1, v1); TextureVertex::set(mesh++, x2, y2, u2, v2); TextureVertex::set(mesh++, x3, y3, u3, v3); + TextureVertex::set(mesh++, x1, y1, u1, v1); TextureVertex::set(mesh++, x4, y4, u4, v4); mCurrentQuad++; } @@ -172,15 +181,17 @@ private: GLuint mTextureId; uint16_t mWidth; uint16_t mHeight; + GLenum mFormat; bool mLinearFiltering; bool mDirty; uint16_t mNumGlyphs; TextureVertex* mMesh; uint32_t mCurrentQuad; uint32_t mMaxQuadCount; + Caches& mCaches; CacheBlock* mCacheBlocks; + bool mHasUnpackRowLength; Rect mDirtyRect; - bool mHasES3; }; }; // namespace uirenderer diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp index 011cfc1..18983d8 100644 --- a/libs/hwui/font/Font.cpp +++ b/libs/hwui/font/Font.cpp @@ -55,6 +55,7 @@ Font::FontDescription::FontDescription(const SkPaint* paint, const mat4& matrix) mStyle = paint->getStyle(); mStrokeWidth = paint->getStrokeWidth(); mAntiAliasing = paint->isAntiAlias(); + mHinting = paint->getHinting(); mLookupTransform.reset(); mLookupTransform[SkMatrix::kMScaleX] = roundf(fmaxf(1.0f, matrix[mat4::kScaleX])); mLookupTransform[SkMatrix::kMScaleY] = roundf(fmaxf(1.0f, matrix[mat4::kScaleY])); @@ -80,6 +81,7 @@ hash_t Font::FontDescription::hash() const { hash = JenkinsHashMix(hash, android::hash_type(mStyle)); hash = JenkinsHashMix(hash, android::hash_type(mStrokeWidth)); hash = JenkinsHashMix(hash, int(mAntiAliasing)); + hash = JenkinsHashMix(hash, android::hash_type(mHinting)); hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleX])); hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleY])); return JenkinsHashWhiten(hash); @@ -111,6 +113,9 @@ int Font::FontDescription::compare(const Font::FontDescription& lhs, deltaInt = int(lhs.mAntiAliasing) - int(rhs.mAntiAliasing); if (deltaInt != 0) return deltaInt; + deltaInt = int(lhs.mHinting) - int(rhs.mHinting); + if (deltaInt != 0) return deltaInt; + if (lhs.mLookupTransform[SkMatrix::kMScaleX] < rhs.mLookupTransform[SkMatrix::kMScaleX]) return -1; if (lhs.mLookupTransform[SkMatrix::kMScaleX] > diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h index 52cca1c..9e7ec2d 100644 --- a/libs/hwui/font/Font.h +++ b/libs/hwui/font/Font.h @@ -69,6 +69,7 @@ public: uint8_t mStyle; float mStrokeWidth; bool mAntiAliasing; + uint8_t mHinting; SkMatrix mLookupTransform; SkMatrix mInverseLookupTransform; }; diff --git a/libs/hwui/font/FontUtil.h b/libs/hwui/font/FontUtil.h index f758666..cdcb23c 100644 --- a/libs/hwui/font/FontUtil.h +++ b/libs/hwui/font/FontUtil.h @@ -31,6 +31,9 @@ #define DEFAULT_TEXT_LARGE_CACHE_HEIGHT 512 #define TEXTURE_BORDER_SIZE 1 +#if TEXTURE_BORDER_SIZE != 1 +# error TEXTURE_BORDER_SIZE other than 1 is not currently supported +#endif #define CACHE_BLOCK_ROUNDING_SIZE 4 diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp index c8bfd9c..189895c 100644 --- a/libs/hwui/thread/TaskManager.cpp +++ b/libs/hwui/thread/TaskManager.cpp @@ -29,7 +29,7 @@ namespace uirenderer { TaskManager::TaskManager() { // Get the number of available CPUs. This value does not change over time. - int cpuCount = sysconf(_SC_NPROCESSORS_ONLN); + int cpuCount = sysconf(_SC_NPROCESSORS_CONF); for (int i = 0; i < cpuCount / 2; i++) { String8 name; 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; |