diff options
-rw-r--r-- | libs/hwui/Android.mk | 1 | ||||
-rw-r--r-- | libs/hwui/Caches.cpp | 23 | ||||
-rw-r--r-- | libs/hwui/Caches.h | 11 | ||||
-rw-r--r-- | libs/hwui/DeferredDisplayList.cpp | 3 | ||||
-rw-r--r-- | libs/hwui/FontRenderer.cpp | 93 | ||||
-rw-r--r-- | libs/hwui/FontRenderer.h | 12 | ||||
-rw-r--r-- | libs/hwui/GammaFontRenderer.cpp | 20 | ||||
-rw-r--r-- | libs/hwui/GammaFontRenderer.h | 8 | ||||
-rw-r--r-- | libs/hwui/OpenGLRenderer.cpp | 3 | ||||
-rw-r--r-- | libs/hwui/PixelBuffer.cpp | 163 | ||||
-rw-r--r-- | libs/hwui/PixelBuffer.h | 180 | ||||
-rw-r--r-- | libs/hwui/Properties.h | 50 | ||||
-rw-r--r-- | libs/hwui/TextDropShadowCache.cpp | 4 | ||||
-rw-r--r-- | libs/hwui/font/CacheTexture.cpp | 50 | ||||
-rw-r--r-- | libs/hwui/font/CacheTexture.h | 31 | ||||
-rw-r--r-- | libs/hwui/font/Font.cpp | 37 |
16 files changed, 576 insertions, 113 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 7b59bf24576f..a630ea11a3dd 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -30,6 +30,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) PatchCache.cpp \ PathCache.cpp \ PathTessellator.cpp \ + PixelBuffer.cpp \ Program.cpp \ ProgramCache.cpp \ RenderBufferCache.cpp \ diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index 57d1a4f27a55..a381a6829c24 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -70,6 +70,7 @@ void Caches::init() { mCurrentPositionPointer = this; mCurrentPositionStride = 0; mCurrentTexCoordsPointer = this; + mCurrentPixelBuffer = 0; mTexCoordsArrayEnabled = false; @@ -366,6 +367,28 @@ bool Caches::unbindIndicesBuffer() { } /////////////////////////////////////////////////////////////////////////////// +// PBO +/////////////////////////////////////////////////////////////////////////////// + +bool Caches::bindPixelBuffer(const GLuint buffer) { + if (mCurrentPixelBuffer != buffer) { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer); + mCurrentPixelBuffer = buffer; + return true; + } + return false; +} + +bool Caches::unbindPixelBuffer() { + if (mCurrentPixelBuffer) { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + mCurrentPixelBuffer = 0; + return true; + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////// // Meshes and textures /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 63836c1c7dea..91b938b5c989 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -176,6 +176,16 @@ public: bool unbindIndicesBuffer(); /** + * Binds the specified buffer as the current GL unpack pixel buffer. + */ + bool bindPixelBuffer(const GLuint buffer); + + /** + * Resets the current unpack pixel buffer to 0 (default value.) + */ + bool unbindPixelBuffer(); + + /** * Binds an attrib to the specified float vertex pointer. * Assumes a stride of gMeshStride and a size of 2. */ @@ -307,6 +317,7 @@ private: GLuint mCurrentBuffer; GLuint mCurrentIndicesBuffer; + GLuint mCurrentPixelBuffer; void* mCurrentPositionPointer; GLsizei mCurrentPositionStride; void* mCurrentTexCoordsPointer; diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp index fe51bf956d8c..d5007e12ebf9 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -21,6 +21,7 @@ #include <utils/Trace.h> +#include "Caches.h" #include "Debug.h" #include "DisplayListOp.h" #include "OpenGLRenderer.h" @@ -377,6 +378,8 @@ static status_t replayBatchList(Vector<DrawOpBatch*>& batchList, status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) { ATRACE_NAME("flush drawing commands"); + Caches::getInstance().fontRenderer->endPrecaching(); + status_t status = DrawGlInfo::kStatusDone; if (isEmpty()) return status; // nothing to flush diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 44dc731c6be7..6894ef916570 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -33,6 +33,7 @@ #include "Debug.h" #include "Extensions.h" #include "FontRenderer.h" +#include "PixelBuffer.h" #include "Rect.h" namespace android { @@ -133,26 +134,13 @@ void FontRenderer::flushAllAndInvalidate() { for (uint32_t i = 0; i < mCacheTextures.size(); i++) { mCacheTextures[i]->init(); } - -#if DEBUG_FONT_RENDERER - uint16_t totalGlyphs = 0; - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - totalGlyphs += mCacheTextures[i]->getGlyphCount(); - // Erase caches, just as a debugging facility - if (mCacheTextures[i]->getTexture()) { - memset(mCacheTextures[i]->getTexture(), 0, - mCacheTextures[i]->getWidth() * mCacheTextures[i]->getHeight()); - } - } - ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs); -#endif } void FontRenderer::flushLargeCaches() { // Start from 1; don't deallocate smallest/default texture for (uint32_t i = 1; i < mCacheTextures.size(); i++) { CacheTexture* cacheTexture = mCacheTextures[i]; - if (cacheTexture->getTexture()) { + if (cacheTexture->getPixelBuffer()) { cacheTexture->init(); LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); while (it.next()) { @@ -226,7 +214,7 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp uint32_t cacheWidth = cacheTexture->getWidth(); - if (!cacheTexture->getTexture()) { + if (!cacheTexture->getPixelBuffer()) { Caches::getInstance().activeTexture(0); // Large-glyph texture memory is allocated only as needed cacheTexture->allocateTexture(); @@ -239,7 +227,7 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp // or anti-aliased (8 bits per pixel) SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat); - uint8_t* cacheBuffer = cacheTexture->getTexture(); + 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 @@ -377,56 +365,36 @@ void FontRenderer::checkTextureUpdate() { Caches& caches = Caches::getInstance(); GLuint lastTextureId = 0; - // OpenGL ES 3.0+ lets us specify the row length for unpack operations such - // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture. - // With OpenGL ES 2.0 we have to upload entire stripes instead. - const bool hasUnpackRowLength = Extensions::getInstance().getMajorGlVersion() >= 3; + bool resetPixelStore = false; glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Iterate over all the cache textures and see which ones need to be updated for (uint32_t i = 0; i < mCacheTextures.size(); i++) { CacheTexture* cacheTexture = mCacheTextures[i]; - if (cacheTexture->isDirty() && cacheTexture->getTexture()) { - const Rect* dirtyRect = cacheTexture->getDirtyRect(); - uint32_t x = hasUnpackRowLength ? dirtyRect->left : 0; - uint32_t y = dirtyRect->top; - uint32_t width = cacheTexture->getWidth(); - uint32_t height = dirtyRect->getHeight(); - void* textureData = cacheTexture->getTexture() + y * width + x; - + if (cacheTexture->isDirty() && cacheTexture->getPixelBuffer()) { if (cacheTexture->getTextureId() != lastTextureId) { lastTextureId = cacheTexture->getTextureId(); caches.activeTexture(0); glBindTexture(GL_TEXTURE_2D, lastTextureId); - - // The unpack row length only needs to be specified when a new - // texture is bound - if (hasUnpackRowLength) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, width); - } } - // If we can upload a sub-rectangle, use the dirty rect width - // instead of the width of the entire texture - if (hasUnpackRowLength) { - width = dirtyRect->getWidth(); + if (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 - - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, - GL_ALPHA, GL_UNSIGNED_BYTE, textureData); - - cacheTexture->setDirty(false); } } + // Unbind any PBO we might have used to update textures + caches.unbindPixelBuffer(); + // Reset to default unpack row length to avoid affecting texture // uploads in other parts of the renderer - if (hasUnpackRowLength) { + if (resetPixelStore) { glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } @@ -539,13 +507,14 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const ch uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) { checkInit(); + DropShadow image; + image.width = 0; + image.height = 0; + image.image = NULL; + image.penX = 0; + image.penY = 0; + if (!mCurrentFont) { - DropShadow image; - image.width = 0; - image.height = 0; - image.image = NULL; - image.penX = 0; - image.penY = 0; return image; } @@ -559,6 +528,11 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const ch uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius; uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius; + uint32_t maxSize = Caches::getInstance().maxTextureSize; + if (paddedWidth > maxSize || paddedHeight > maxSize) { + return image; + } + // Align buffers for renderscript usage if (paddedWidth & (RS_CPU_ALLOCATION_ALIGNMENT - 1)) { paddedWidth += RS_CPU_ALLOCATION_ALIGNMENT - paddedWidth % RS_CPU_ALLOCATION_ALIGNMENT; @@ -578,10 +552,12 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const ch mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY, Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions); + // Unbind any PBO we might have used + Caches::getInstance().unbindPixelBuffer(); + blurImage(&dataBuffer, paddedWidth, paddedHeight, radius); } - DropShadow image; image.width = paddedWidth; image.height = paddedHeight; image.image = dataBuffer; @@ -612,6 +588,10 @@ void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, con font->precache(paint, text, numGlyphs); } +void FontRenderer::endPrecaching() { + checkTextureUpdate(); +} + bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, const float* positions, Rect* bounds, Functor* functor) { @@ -690,5 +670,16 @@ void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int *image = outImage; } +uint32_t FontRenderer::getCacheSize() const { + uint32_t size = 0; + for (uint32_t i = 0; i < mCacheTextures.size(); i++) { + CacheTexture* cacheTexture = mCacheTextures[i]; + if (cacheTexture && cacheTexture->getPixelBuffer()) { + size += cacheTexture->getPixelBuffer()->getSize(); + } + } + return size; +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index 1da3b6cb3433..348b7e3b2b0a 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -61,6 +61,7 @@ public: void setFont(SkPaint* paint, const mat4& matrix); void precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix); + void endPrecaching(); // bounds is an out parameter bool renderPosText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex, @@ -95,16 +96,7 @@ public: mLinearFiltering = linearFiltering; } - uint32_t getCacheSize() const { - uint32_t size = 0; - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - CacheTexture* cacheTexture = mCacheTextures[i]; - if (cacheTexture && cacheTexture->getTexture()) { - size += cacheTexture->getWidth() * cacheTexture->getHeight(); - } - } - return size; - } + uint32_t getCacheSize() const; private: friend class Font; diff --git a/libs/hwui/GammaFontRenderer.cpp b/libs/hwui/GammaFontRenderer.cpp index bd0a4b39092b..06d2aad12f2a 100644 --- a/libs/hwui/GammaFontRenderer.cpp +++ b/libs/hwui/GammaFontRenderer.cpp @@ -129,6 +129,12 @@ void ShaderGammaFontRenderer::setupProgram(ProgramDescription& description, } } +void ShaderGammaFontRenderer::endPrecaching() { + if (mRenderer) { + mRenderer->endPrecaching(); + } +} + /////////////////////////////////////////////////////////////////////////////// // Lookup-based renderer /////////////////////////////////////////////////////////////////////////////// @@ -146,6 +152,12 @@ LookupGammaFontRenderer::LookupGammaFontRenderer(): GammaFontRenderer() { mRenderer = NULL; } +void LookupGammaFontRenderer::endPrecaching() { + if (mRenderer) { + mRenderer->endPrecaching(); + } +} + /////////////////////////////////////////////////////////////////////////////// // Lookup-based renderer, using 3 different correction tables /////////////////////////////////////////////////////////////////////////////// @@ -177,6 +189,14 @@ Lookup3GammaFontRenderer::~Lookup3GammaFontRenderer() { } } +void Lookup3GammaFontRenderer::endPrecaching() { + for (int i = 0; i < kGammaCount; i++) { + if (mRenderers[i]) { + mRenderers[i]->endPrecaching(); + } + } +} + void Lookup3GammaFontRenderer::clear() { for (int i = 0; i < kGammaCount; i++) { delete mRenderers[i]; diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h index 5c1860ea5c8e..bbfa66d37fa7 100644 --- a/libs/hwui/GammaFontRenderer.h +++ b/libs/hwui/GammaFontRenderer.h @@ -40,6 +40,8 @@ public: virtual void describe(ProgramDescription& description, const SkPaint* paint) const = 0; virtual void setupProgram(ProgramDescription& description, Program* program) const = 0; + virtual void endPrecaching() = 0; + static GammaFontRenderer* createRenderer(); protected: @@ -86,6 +88,8 @@ public: void describe(ProgramDescription& description, const SkPaint* paint) const; void setupProgram(ProgramDescription& description, Program* program) const; + void endPrecaching(); + private: ShaderGammaFontRenderer(bool multiGamma); @@ -134,6 +138,8 @@ public: void setupProgram(ProgramDescription& description, Program* program) const { } + void endPrecaching(); + private: LookupGammaFontRenderer(); @@ -171,6 +177,8 @@ public: void setupProgram(ProgramDescription& description, Program* program) const { } + void endPrecaching(); + private: Lookup3GammaFontRenderer(); diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index e18d92223ed4..dcd1eb834fcb 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -2644,6 +2644,9 @@ void OpenGLRenderer::drawTextShadow(SkPaint* paint, const char* text, int bytesC mCaches.dropShadowCache.setFontRenderer(fontRenderer); const ShadowTexture* shadow = mCaches.dropShadowCache.get( paint, text, bytesCount, count, mDrawModifiers.mShadowRadius, positions); + // If the drop shadow exceeds the max texture size or couldn't be + // allocated, skip drawing + if (!shadow) return; const AutoTexture autoCleanup(shadow); const float sx = x - shadow->left + mDrawModifiers.mShadowDx; diff --git a/libs/hwui/PixelBuffer.cpp b/libs/hwui/PixelBuffer.cpp new file mode 100644 index 000000000000..8280370a33d0 --- /dev/null +++ b/libs/hwui/PixelBuffer.cpp @@ -0,0 +1,163 @@ +/* + * 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 "Extensions.h" +#include "PixelBuffer.h" +#include "Properties.h" + +namespace android { +namespace uirenderer { + +/////////////////////////////////////////////////////////////////////////////// +// CPU pixel buffer +/////////////////////////////////////////////////////////////////////////////// + +class CpuPixelBuffer: public PixelBuffer { +public: + CpuPixelBuffer(GLenum format, uint32_t width, uint32_t height); + ~CpuPixelBuffer(); + + uint8_t* map(AccessMode mode = kAccessMode_ReadWrite); + void unmap(); + + uint8_t* getMappedPointer() const; + + void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset); + +private: + uint8_t* mBuffer; +}; + +CpuPixelBuffer::CpuPixelBuffer(GLenum format, uint32_t width, uint32_t height): + PixelBuffer(format, width, height) { + mBuffer = new uint8_t[width * height * formatSize(format)]; +} + +CpuPixelBuffer::~CpuPixelBuffer() { + delete[] mBuffer; +} + +uint8_t* CpuPixelBuffer::map(AccessMode mode) { + if (mAccessMode == kAccessMode_None) { + mAccessMode = mode; + } + return mBuffer; +} + +void CpuPixelBuffer::unmap() { + mAccessMode = kAccessMode_None; +} + +uint8_t* CpuPixelBuffer::getMappedPointer() const { + return mAccessMode == kAccessMode_None ? NULL : mBuffer; +} + +void CpuPixelBuffer::upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) { + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, + mFormat, GL_UNSIGNED_BYTE, mBuffer + offset); +} + +/////////////////////////////////////////////////////////////////////////////// +// GPU pixel buffer +/////////////////////////////////////////////////////////////////////////////// + +class GpuPixelBuffer: public PixelBuffer { +public: + GpuPixelBuffer(GLenum format, uint32_t width, uint32_t height); + ~GpuPixelBuffer(); + + uint8_t* map(AccessMode mode = kAccessMode_ReadWrite); + void unmap(); + + uint8_t* getMappedPointer() const; + + void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset); + +private: + GLuint mBuffer; + uint8_t* mMappedPointer; + Caches& mCaches; +}; + +GpuPixelBuffer::GpuPixelBuffer(GLenum format, uint32_t width, uint32_t height): + PixelBuffer(format, width, height), mMappedPointer(0), mCaches(Caches::getInstance()) { + glGenBuffers(1, &mBuffer); + mCaches.bindPixelBuffer(mBuffer); + glBufferData(GL_PIXEL_UNPACK_BUFFER, getSize(), NULL, GL_DYNAMIC_DRAW); + mCaches.unbindPixelBuffer(); +} + +GpuPixelBuffer::~GpuPixelBuffer() { + glDeleteBuffers(1, &mBuffer); +} + +uint8_t* GpuPixelBuffer::map(AccessMode mode) { + if (mAccessMode == kAccessMode_None) { + mCaches.bindPixelBuffer(mBuffer); + mMappedPointer = (uint8_t*) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, getSize(), mode); + mAccessMode = mode; + } + + return mMappedPointer; +} + +void GpuPixelBuffer::unmap() { + if (mAccessMode != kAccessMode_None) { + if (mMappedPointer) { + mCaches.bindPixelBuffer(mBuffer); + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + } + mAccessMode = kAccessMode_None; + mMappedPointer = NULL; + } +} + +uint8_t* GpuPixelBuffer::getMappedPointer() const { + return mMappedPointer; +} + +void GpuPixelBuffer::upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) { + // If the buffer is not mapped, unmap() will not bind it + mCaches.bindPixelBuffer(mBuffer); + unmap(); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, mFormat, + GL_UNSIGNED_BYTE, (void*) offset); +} + +/////////////////////////////////////////////////////////////////////////////// +// Factory +/////////////////////////////////////////////////////////////////////////////// + +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); + } + } + } + return new CpuPixelBuffer(format, width, height); +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/PixelBuffer.h b/libs/hwui/PixelBuffer.h new file mode 100644 index 000000000000..32d541776a82 --- /dev/null +++ b/libs/hwui/PixelBuffer.h @@ -0,0 +1,180 @@ +/* + * 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_PIXEL_BUFFER_H +#define ANDROID_HWUI_PIXEL_BUFFER_H + +#include <GLES3/gl3.h> + +namespace android { +namespace uirenderer { + +/** + * Represents a pixel buffer. A pixel buffer will be backed either by a + * PBO on OpenGL ES 3.0 and higher or by an array of uint8_t on other + * versions. If the buffer is backed by a PBO it will of type + * GL_PIXEL_UNPACK_BUFFER. + * + * To read from or write into a PixelBuffer you must first map the + * buffer using the map(AccessMode) method. This method returns a + * pointer to the beginning of the buffer. + * + * Before the buffer can be used by the GPU, for instance to upload + * a texture, you must first unmap the buffer. To do so, call the + * unmap() method. + * + * Mapping and unmapping a PixelBuffer can have the side effect of + * changing the currently active GL_PIXEL_UNPACK_BUFFER. It is + * therefore recommended to call Caches::unbindPixelbuffer() after + * using a PixelBuffer to upload to a texture. + */ +class PixelBuffer { +public: + enum BufferType { + kBufferType_Auto, + kBufferType_CPU + }; + + enum AccessMode { + kAccessMode_None = 0, + kAccessMode_Read = GL_MAP_READ_BIT, + kAccessMode_Write = GL_MAP_WRITE_BIT, + kAccessMode_ReadWrite = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT + }; + + /** + * Creates a new PixelBuffer object with the specified format and + * dimensions. The buffer is immediately allocated. + * + * The buffer type specifies how the buffer should be allocated. + * By default this method will automatically choose whether to allocate + * a CPU or GPU buffer. + */ + static PixelBuffer* create(GLenum format, uint32_t width, uint32_t height, + BufferType type = kBufferType_Auto); + + virtual ~PixelBuffer() { + } + + /** + * Returns the format of this render buffer. + */ + GLenum getFormat() const { + return mFormat; + } + + /** + * Maps this before with the specified access mode. This method + * returns a pointer to the region of memory where the buffer was + * mapped. + * + * If the buffer is already mapped when this method is invoked, + * this method will return the previously mapped pointer. The + * access mode can only be changed by calling unmap() first. + * + * The specified access mode cannot be kAccessMode_None. + */ + virtual uint8_t* map(AccessMode mode = kAccessMode_ReadWrite) = 0; + + /** + * Unmaps this buffer, if needed. After the buffer is unmapped, + * the pointer previously returned by map() becomes invalid and + * should not be used. After calling this method, getMappedPointer() + * will always return NULL. + */ + virtual void unmap() = 0; + + /** + * Returns the current access mode for this buffer. If the buffer + * is not mapped, this method returns kAccessMode_None. + */ + AccessMode getAccessMode() const { + return mAccessMode; + } + + /** + * Returns the currently mapped pointer. Returns NULL if the buffer + * is not mapped. + */ + virtual uint8_t* getMappedPointer() const = 0; + + /** + * Upload the specified rectangle of this pixe 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; + + /** + * Returns the width of the render buffer in pixels. + */ + uint32_t getWidth() const { + return mWidth; + } + + /** + * Returns the height of the render buffer in pixels. + */ + uint32_t getHeight() const { + return mHeight; + } + + /** + * Returns the size of this pixel buffer in bytes. + */ + uint32_t getSize() const { + return mWidth * mHeight * formatSize(mFormat); + } + + /** + * Returns the number of bytes per pixel in the specified format. + * + * Supported formats: + * GL_ALPHA + * GL_RGBA + */ + static uint32_t formatSize(GLenum format) { + switch (format) { + case GL_ALPHA: + return 1; + case GL_RGBA: + return 4; + } + return 0; + } + +protected: + /** + * Creates a new render buffer in the specified format and dimensions. + * The format must be GL_ALPHA or GL_RGBA. + */ + PixelBuffer(GLenum format, uint32_t width, uint32_t height): + mFormat(format), mWidth(width), mHeight(height), mAccessMode(kAccessMode_None) { + } + + GLenum mFormat; + + uint32_t mWidth; + uint32_t mHeight; + + AccessMode mAccessMode; + +}; // class PixelBuffer + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_PIXEL_BUFFER_H diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index e4b4f3c4fb8d..6eea00cd6c54 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -25,6 +25,10 @@ * the OpenGLRenderer. */ +/////////////////////////////////////////////////////////////////////////////// +// Compile-time properties +/////////////////////////////////////////////////////////////////////////////// + // If turned on, text is interpreted as glyphs instead of UTF-16 #define RENDER_TEXT_AS_GLYPHS 1 @@ -39,6 +43,10 @@ // to properly implement overdraw debugging #define STENCIL_BUFFER_SIZE 8 +/////////////////////////////////////////////////////////////////////////////// +// Debug properties +/////////////////////////////////////////////////////////////////////////////// + /** * Debug level for app developers. The value is a numeric value defined * by the DebugLevel enum below. @@ -82,6 +90,23 @@ enum DebugLevel { #define PROPERTY_DEBUG_STENCIL_CLIP "debug.hwui.show_non_rect_clip" /** + * Disables draw operation deferral if set to "true", forcing draw + * commands to be issued to OpenGL in order, and processed in sequence + * with state-manipulation canvas commands. + */ +#define PROPERTY_DISABLE_DRAW_DEFER "debug.hwui.disable_draw_defer" + +/** + * Used to disable draw operation reordering when deferring draw operations + * Has no effect if PROPERTY_DISABLE_DRAW_DEFER is set to "true" + */ +#define PROPERTY_DISABLE_DRAW_REORDER "debug.hwui.disable_draw_reorder" + +/////////////////////////////////////////////////////////////////////////////// +// Runtime configuration properties +/////////////////////////////////////////////////////////////////////////////// + +/** * Used to enable/disable scissor optimization. The accepted values are * "true" and "false". The default value is "false". * @@ -97,17 +122,10 @@ enum DebugLevel { #define PROPERTY_DISABLE_SCISSOR_OPTIMIZATION "ro.hwui.disable_scissor_opt" /** - * Disables draw operation deferral if set to "true", forcing draw - * commands to be issued to OpenGL in order, and processed in sequence - * with state-manipulation canvas commands. - */ -#define PROPERTY_DISABLE_DRAW_DEFER "debug.hwui.disable_draw_defer" - -/** - * Used to disable draw operation reordering when deferring draw operations - * Has no effect if PROPERTY_DISABLE_DRAW_DEFER is set to "true" + * Indicates whether PBOs can be used to back pixel buffers. + * Accepted values are "true" and "false". */ -#define PROPERTY_DISABLE_DRAW_REORDER "debug.hwui.disable_draw_reorder" +#define PROPERTY_ENABLE_GPU_PIXEL_BUFFERS "hwui.use_gpu_pixel_buffers" // These properties are defined in mega-bytes #define PROPERTY_TEXTURE_CACHE_SIZE "ro.hwui.texture_cache_size" @@ -152,8 +170,9 @@ enum DebugLevel { // Lumincance threshold above which white gamma correction is applied. Range: [0..255] #define PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD "hwui.text_gamma.white_threshold" -// Converts a number of mega-bytes into bytes -#define MB(s) s * 1024 * 1024 +/////////////////////////////////////////////////////////////////////////////// +// Default property values +/////////////////////////////////////////////////////////////////////////////// #define DEFAULT_TEXTURE_CACHE_SIZE 24.0f #define DEFAULT_LAYER_CACHE_SIZE 16.0f @@ -170,6 +189,13 @@ enum DebugLevel { #define DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD 64 #define DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD 192 +/////////////////////////////////////////////////////////////////////////////// +// Misc +/////////////////////////////////////////////////////////////////////////////// + +// Converts a number of mega-bytes into bytes +#define MB(s) s * 1024 * 1024 + static DebugLevel readDebugLevel() { char property[PROPERTY_VALUE_MAX]; if (property_get(PROPERTY_DEBUG, property, NULL) > 0) { diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp index f1f35bd1711c..6976eaa3d70e 100644 --- a/libs/hwui/TextDropShadowCache.cpp +++ b/libs/hwui/TextDropShadowCache.cpp @@ -178,6 +178,10 @@ ShadowTexture* TextDropShadowCache::get(SkPaint* paint, const char* text, uint32 FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, text, 0, len, numGlyphs, radius, positions); + if (!shadow.image) { + return NULL; + } + texture = new ShadowTexture; texture->left = shadow.penX; texture->top = shadow.penY; diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp index 577f46389fdb..6c5267d338a7 100644 --- a/libs/hwui/font/CacheTexture.cpp +++ b/libs/hwui/font/CacheTexture.cpp @@ -18,6 +18,8 @@ #include "CacheTexture.h" #include "../Debug.h" +#include "../Extensions.h" +#include "../PixelBuffer.h" namespace android { namespace uirenderer { @@ -111,6 +113,11 @@ CacheTexture::CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCoun mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount) { 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; } CacheTexture::~CacheTexture() { @@ -143,7 +150,7 @@ void CacheTexture::releaseMesh() { void CacheTexture::releaseTexture() { if (mTexture) { - delete[] mTexture; + delete mTexture; mTexture = NULL; } if (mTextureId) { @@ -154,6 +161,17 @@ void CacheTexture::releaseTexture() { mCurrentQuad = 0; } +void CacheTexture::setLinearFiltering(bool linearFiltering, bool bind) { + if (linearFiltering != mLinearFiltering) { + mLinearFiltering = linearFiltering; + + const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST; + if (bind) glBindTexture(GL_TEXTURE_2D, getTextureId()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); + } +} + void CacheTexture::allocateMesh() { if (!mMesh) { mMesh = new TextureVertex[mMaxQuadCount * 4]; @@ -162,7 +180,7 @@ void CacheTexture::allocateMesh() { void CacheTexture::allocateTexture() { if (!mTexture) { - mTexture = new uint8_t[mWidth * mHeight]; + mTexture = PixelBuffer::create(GL_ALPHA, mWidth, mHeight); } if (!mTextureId) { @@ -183,6 +201,34 @@ void CacheTexture::allocateTexture() { } } +bool CacheTexture::upload() { + const Rect& dirtyRect = mDirtyRect; + + uint32_t x = mHasES3 ? dirtyRect.left : 0; + uint32_t y = dirtyRect.top; + uint32_t width = mHasES3 ? 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) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, mWidth); + } + + mTexture->upload(x, y, width, height, y * mWidth + x); + + setDirty(false); + + return mHasES3; +} + +void CacheTexture::setDirty(bool dirty) { + mDirty = dirty; + if (!dirty) { + mDirtyRect.setEmpty(); + } +} + bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) { if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) { return false; diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h index e7fb4746492a..ddcc836419a9 100644 --- a/libs/hwui/font/CacheTexture.h +++ b/libs/hwui/font/CacheTexture.h @@ -30,6 +30,8 @@ namespace android { namespace uirenderer { +class PixelBuffer; + /** * CacheBlock is a node in a linked list of current free space areas in a CacheTexture. * Using CacheBlocks enables us to pack the cache from top to bottom as well as left to right. @@ -83,6 +85,10 @@ public: void allocateTexture(); void allocateMesh(); + // Returns true if glPixelStorei(GL_UNPACK_ROW_LENGTH) must be reset + // This method will also call setDirty(false) + bool upload(); + bool fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY); inline uint16_t getWidth() const { @@ -97,7 +103,7 @@ public: return &mDirtyRect; } - inline uint8_t* getTexture() const { + inline PixelBuffer* getPixelBuffer() const { return mTexture; } @@ -110,13 +116,6 @@ public: return mDirty; } - inline void setDirty(bool dirty) { - mDirty = dirty; - if (!dirty) { - mDirtyRect.setEmpty(); - } - } - inline bool getLinearFiltering() const { return mLinearFiltering; } @@ -124,16 +123,7 @@ public: /** * This method assumes that the proper texture unit is active. */ - void setLinearFiltering(bool linearFiltering, bool bind = true) { - if (linearFiltering != mLinearFiltering) { - mLinearFiltering = linearFiltering; - - const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST; - if (bind) glBindTexture(GL_TEXTURE_2D, getTextureId()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); - } - } + void setLinearFiltering(bool linearFiltering, bool bind = true); inline uint16_t getGlyphCount() const { return mNumGlyphs; @@ -176,7 +166,9 @@ public: } private: - uint8_t* mTexture; + void setDirty(bool dirty); + + PixelBuffer* mTexture; GLuint mTextureId; uint16_t mWidth; uint16_t mHeight; @@ -188,6 +180,7 @@ private: uint32_t mMaxQuadCount; CacheBlock* mCacheBlocks; Rect mDirtyRect; + bool mHasES3; }; }; // namespace uirenderer diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp index 02c1aa191e5c..011cfc14ac5c 100644 --- a/libs/hwui/font/Font.cpp +++ b/libs/hwui/font/Font.cpp @@ -25,11 +25,12 @@ #include <SkGlyph.h> #include <SkUtils.h> -#include "Debug.h" #include "FontUtil.h" #include "Font.h" -#include "FontRenderer.h" -#include "Properties.h" +#include "../Debug.h" +#include "../FontRenderer.h" +#include "../PixelBuffer.h" +#include "../Properties.h" namespace android { namespace uirenderer { @@ -200,25 +201,23 @@ void Font::drawCachedGlyphTransformed(CachedGlyphInfo* glyph, int x, int y, p[3].x(), p[3].y(), u1, v1, glyph->mCacheTexture); } -void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, - uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { - int nPenX = x + glyph->mBitmapLeft; - int nPenY = y + glyph->mBitmapTop; - - uint32_t endX = glyph->mStartX + glyph->mBitmapWidth; - uint32_t endY = glyph->mStartY + glyph->mBitmapHeight; +void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, + uint32_t bitmapWidth, uint32_t bitmapHeight, Rect* bounds, const float* pos) { + int dstX = x + glyph->mBitmapLeft; + int dstY = y + glyph->mBitmapTop; CacheTexture* cacheTexture = glyph->mCacheTexture; + uint32_t cacheWidth = cacheTexture->getWidth(); - const uint8_t* cacheBuffer = cacheTexture->getTexture(); - - uint32_t cacheX = 0, cacheY = 0; - int32_t bX = 0, bY = 0; - for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) { - for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) { - uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX]; - bitmap[bY * bitmapW + bX] = tempCol; - } + uint32_t startY = glyph->mStartY * cacheWidth; + uint32_t endY = startY + (glyph->mBitmapHeight * cacheWidth); + + PixelBuffer* pixelBuffer = cacheTexture->getPixelBuffer(); + const uint8_t* cacheBuffer = pixelBuffer->map(); + + for (uint32_t cacheY = startY, bitmapY = dstY * bitmapWidth; cacheY < endY; + cacheY += cacheWidth, bitmapY += bitmapWidth) { + memcpy(&bitmap[bitmapY + dstX], &cacheBuffer[cacheY + glyph->mStartX], glyph->mBitmapWidth); } } |