diff options
Diffstat (limited to 'libs/hwui')
33 files changed, 1438 insertions, 419 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 06e658d..a630ea1 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 \ @@ -58,7 +59,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DGL_GLEXT_PROTOTYPES LOCAL_MODULE_CLASS := SHARED_LIBRARIES - LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2 libskia libui libRS libRScpp + LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libGLESv2 libskia libui libRS libRScpp LOCAL_MODULE := libhwui LOCAL_MODULE_TAGS := optional diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index 57d1a4f..a381a68 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 63836c1..91b938b 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/Debug.h b/libs/hwui/Debug.h index 46beb94..790c4f4 100644 --- a/libs/hwui/Debug.h +++ b/libs/hwui/Debug.h @@ -84,6 +84,9 @@ // Turn on to insert an event marker for each display list op #define DEBUG_DISPLAY_LIST_OPS_AS_EVENTS 0 +// Turn on to highlight drawing batches and merged batches with different colors +#define DEBUG_MERGE_BEHAVIOR 0 + #if DEBUG_INIT #define INIT_LOGD(...) ALOGD(__VA_ARGS__) #else diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp index fe51bf9..f0084f2 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -21,7 +21,9 @@ #include <utils/Trace.h> +#include "Caches.h" #include "Debug.h" +#include "DeferredDisplayList.h" #include "DisplayListOp.h" #include "OpenGLRenderer.h" @@ -37,15 +39,27 @@ namespace uirenderer { // Depth of the save stack at the beginning of batch playback at flush time #define FLUSH_SAVE_STACK_DEPTH 2 +#define DEBUG_COLOR_BARRIER 0x1f000000 +#define DEBUG_COLOR_MERGEDBATCH 0x5f7f7fff +#define DEBUG_COLOR_MERGEDBATCH_SOLO 0x5f7fff7f + ///////////////////////////////////////////////////////////////////////////////// // Operation Batches ///////////////////////////////////////////////////////////////////////////////// -class DrawOpBatch { +class Batch { public: - DrawOpBatch() { mOps.clear(); } + virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) = 0; + virtual ~Batch() {} +}; + +class DrawBatch : public Batch { +public: + DrawBatch(int batchId, mergeid_t mergeId) : mBatchId(batchId), mMergeId(mergeId) { + mOps.clear(); + } - virtual ~DrawOpBatch() { mOps.clear(); } + virtual ~DrawBatch() { mOps.clear(); } void add(DrawOp* op) { // NOTE: ignore empty bounds special case, since we don't merge across those ops @@ -53,7 +67,7 @@ public: mOps.add(op); } - virtual bool intersects(Rect& rect) { + bool intersects(Rect& rect) { if (!rect.intersects(mBounds)) return false; for (unsigned int i = 0; i < mOps.size(); i++) { @@ -70,8 +84,9 @@ public: return false; } - virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) { - DEFER_LOGD("replaying draw batch %p", this); + 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()); status_t status = DrawGlInfo::kStatusDone; DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance(); @@ -83,31 +98,127 @@ public: #if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS renderer.eventMark(op->name()); #endif - status |= op->applyDraw(renderer, dirty, 0); + status |= op->applyDraw(renderer, dirty); logBuffer.writeCommand(0, op->name()); + +#if DEBUG_MERGE_BEHAVIOR + Rect& bounds = mOps[i]->state.mBounds; + int batchColor = 0x1f000000; + if (getBatchId() & 0x1) batchColor |= 0x0000ff; + if (getBatchId() & 0x2) batchColor |= 0x00ff00; + if (getBatchId() & 0x4) batchColor |= 0xff0000; + renderer.drawScreenSpaceColorRect(bounds.left, bounds.top, bounds.right, bounds.bottom, + batchColor); +#endif } return status; } + inline int getBatchId() const { return mBatchId; } + inline mergeid_t getMergeId() const { return mMergeId; } inline int count() const { return mOps.size(); } -private: + +protected: Vector<DrawOp*> mOps; Rect mBounds; +private: + int mBatchId; + mergeid_t mMergeId; }; -class StateOpBatch : public DrawOpBatch { +// compare alphas approximately, with a small margin +#define NEQ_FALPHA(lhs, rhs) \ + fabs((float)lhs - (float)rhs) > 0.001f + +class MergingDrawBatch : public DrawBatch { public: - // creates a single operation batch - StateOpBatch(StateOp* op) : mOp(op) {} + MergingDrawBatch(int batchId, mergeid_t mergeId) : DrawBatch(batchId, mergeId) {} - bool intersects(Rect& rect) { - // if something checks for intersection, it's trying to go backwards across a state op, - // something not currently supported - state ops are always barriers - CRASH(); - return false; + /* + * 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 + * + * 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 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; + } + + const DeferredDisplayState& lhs = op->state; + const DeferredDisplayState& rhs = mOps[0]->state; + + if (NEQ_FALPHA(lhs.mAlpha, rhs.mAlpha)) 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->getPaintAlpha() != mOps[0]->getPaintAlpha()) return false; + + /* Draw Modifiers compatibility check + * + * Shadows are ignored, as only text uses them, and in that case they are drawn + * per-DrawTextOp, before the unified text draw. Because of this, it's always safe to merge + * text UNLESS a later draw's shadow should overlays a previous draw's text. This is covered + * above with the intersection check. + * + * OverrideLayerAlpha is also ignored, as it's only used for drawing layers, which are never + * merged. + * + * These ignore cases prevent us from simply memcmp'ing the drawModifiers + */ + + const DrawModifiers& lhsMod = lhs.mDrawModifiers; + const DrawModifiers& rhsMod = rhs.mDrawModifiers; + if (lhsMod.mShader != rhsMod.mShader) return false; + if (lhsMod.mColorFilter != rhsMod.mColorFilter) return false; + + // Draw filter testing expects bit fields to be clear if filter not set. + if (lhsMod.mHasDrawFilter != rhsMod.mHasDrawFilter) return false; + if (lhsMod.mPaintFilterClearBits != rhsMod.mPaintFilterClearBits) return false; + if (lhsMod.mPaintFilterSetBits != rhsMod.mPaintFilterSetBits) return false; + + return true; } - virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) { + 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()); + if (mOps.size() == 1) { + return DrawBatch::replay(renderer, dirty, false); + } + + DrawOp* op = mOps[0]; + status_t status = op->multiDraw(renderer, dirty, mOps, mBounds); + DisplayListLogBuffer& buffer = DisplayListLogBuffer::getInstance(); + buffer.writeCommand(0, "multiDraw"); + buffer.writeCommand(1, op->name()); + +#if DEBUG_MERGE_BEHAVIOR + renderer.drawScreenSpaceColorRect(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom, + DEBUG_COLOR_MERGEDBATCH); +#endif + return status; + } +}; + +class StateOpBatch : public Batch { +public: + // creates a single operation batch + StateOpBatch(StateOp* op) : mOp(op) {} + + virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) { DEFER_LOGD("replaying state op batch %p", this); renderer.restoreDisplayState(mOp->state); @@ -123,18 +234,11 @@ private: const StateOp* mOp; }; -class RestoreToCountBatch : public DrawOpBatch { +class RestoreToCountBatch : public Batch { public: RestoreToCountBatch(StateOp* op, int restoreCount) : mOp(op), mRestoreCount(restoreCount) {} - bool intersects(Rect& rect) { - // if something checks for intersection, it's trying to go backwards across a state op, - // something not currently supported - state ops are always barriers - CRASH(); - return false; - } - - virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) { + virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) { DEFER_LOGD("batch %p restoring to count %d", this, mRestoreCount); renderer.restoreDisplayState(mOp->state); @@ -154,14 +258,30 @@ private: const int mRestoreCount; }; +#if DEBUG_MERGE_BEHAVIOR +class BarrierDebugBatch : public Batch { + virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) { + renderer.drawScreenSpaceColorRect(0, 0, 10000, 10000, DEBUG_COLOR_BARRIER); + return DrawGlInfo::kStatusDrew; + } +}; +#endif + ///////////////////////////////////////////////////////////////////////////////// // DeferredDisplayList ///////////////////////////////////////////////////////////////////////////////// void DeferredDisplayList::resetBatchingState() { for (int i = 0; i < kOpBatch_Count; i++) { - mBatchIndices[i] = -1; + mBatchLookup[i] = NULL; + mMergingBatches[i].clear(); } +#if DEBUG_MERGE_BEHAVIOR + if (mBatches.size() != 0) { + mBatches.add(new BarrierDebugBatch()); + } +#endif + mEarliestBatchIndex = mBatches.size(); } void DeferredDisplayList::clear() { @@ -173,6 +293,7 @@ void DeferredDisplayList::clear() { } mBatches.clear(); mSaveStack.clear(); + mEarliestBatchIndex = 0; } ///////////////////////////////////////////////////////////////////////////////// @@ -281,28 +402,35 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { return; // quick rejected } - op->onDrawOpDeferred(renderer); + int batchId = kOpBatch_None; + mergeid_t mergeId = (mergeid_t) -1; + bool mergeable = op->onDefer(renderer, &batchId, &mergeId); + + // complex clip has a complex set of expectations on the renderer state - for now, avoid taking + // the merge path in those cases + mergeable &= !recordingComplexClip(); if (CC_UNLIKELY(renderer.getCaches().drawReorderDisabled)) { // TODO: elegant way to reuse batches? - DrawOpBatch* b = new DrawOpBatch(); + DrawBatch* b = new DrawBatch(batchId, mergeId); b->add(op); mBatches.add(b); return; } - // disallowReorder isn't set, so find the latest batch of the new op's type, and try to merge - // the new op into it - DrawOpBatch* targetBatch = NULL; - int batchId = op->getBatchId(); + // find the latest batch of the new op's type, and try to merge the new op into it + DrawBatch* targetBatch = NULL; + // insertion point of a new batch, will hopefully be immediately after similar batch + // (eventually, should be similar shader) + int insertBatchIndex = mBatches.size(); if (!mBatches.isEmpty()) { if (op->state.mBounds.isEmpty()) { // don't know the bounds for op, so add to last batch and start from scratch on next op - mBatches.top()->add(op); - for (int i = 0; i < kOpBatch_Count; i++) { - mBatchIndices[i] = -1; - } + DrawBatch* b = new DrawBatch(batchId, mergeId); + b->add(op); + mBatches.add(b); + resetBatchingState(); #if DEBUG_DEFER DEFER_LOGD("Warning: Encountered op with empty bounds, resetting batches"); op->output(2); @@ -310,13 +438,36 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { return; } - if (batchId >= 0 && mBatchIndices[batchId] != -1) { - int targetIndex = mBatchIndices[batchId]; - targetBatch = mBatches[targetIndex]; + if (mergeable) { + // Try to merge with any existing batch with same mergeId. + if (mMergingBatches[batchId].get(mergeId, targetBatch)) { + if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op)) { + targetBatch = NULL; + } + } + } else { + // join with similar, non-merging batch + targetBatch = (DrawBatch*)mBatchLookup[batchId]; + } + + if (targetBatch || mergeable) { // iterate back toward target to see if anything drawn since should overlap the new op - for (int i = mBatches.size() - 1; i > targetIndex; i--) { - DrawOpBatch* overBatch = mBatches[i]; + // if no target, merging ops still interate to find similar batch to insert after + for (int i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) { + DrawBatch* overBatch = (DrawBatch*)mBatches[i]; + + if (overBatch == targetBatch) break; + + // TODO: also consider shader shared between batch types + if (batchId == overBatch->getBatchId()) { + insertBatchIndex = i + 1; + if (!targetBatch) break; // found insert position, quit + } + if (overBatch->intersects(op->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", @@ -328,13 +479,21 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { } } } + if (!targetBatch) { - targetBatch = new DrawOpBatch(); - mBatches.add(targetBatch); - if (batchId >= 0) { - mBatchIndices[batchId] = mBatches.size() - 1; + if (mergeable) { + targetBatch = new MergingDrawBatch(batchId, mergeId); + mMergingBatches[batchId].put(mergeId, targetBatch); + } else { + targetBatch = new DrawBatch(batchId, mergeId); + mBatchLookup[batchId] = targetBatch; + DEFER_LOGD("creating Batch %p, bid %x, at %d", + targetBatch, batchId, insertBatchIndex); } + + mBatches.insertAt(targetBatch, insertBatchIndex); } + targetBatch->add(op); } @@ -362,21 +521,21 @@ void DeferredDisplayList::storeRestoreToCountBarrier(OpenGLRenderer& renderer, S // Replay / flush ///////////////////////////////////////////////////////////////////////////////// -static status_t replayBatchList(Vector<DrawOpBatch*>& batchList, +static status_t replayBatchList(const Vector<Batch*>& batchList, OpenGLRenderer& renderer, Rect& dirty) { status_t status = DrawGlInfo::kStatusDone; - int opCount = 0; for (unsigned int i = 0; i < batchList.size(); i++) { - status |= batchList[i]->replay(renderer, dirty); - opCount += batchList[i]->count(); + status |= batchList[i]->replay(renderer, dirty, i); } - DEFER_LOGD("--flushed, drew %d batches (total %d ops)", batchList.size(), opCount); + DEFER_LOGD("--flushed, drew %d batches", batchList.size()); return status; } 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 @@ -397,7 +556,6 @@ status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) { renderer.setDrawModifiers(restoreDrawModifiers); DEFER_LOGD("--flush complete, returning %x", status); - clear(); return status; } diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h index 3e450da..9782c1c 100644 --- a/libs/hwui/DeferredDisplayList.h +++ b/libs/hwui/DeferredDisplayList.h @@ -22,6 +22,9 @@ #include "Matrix.h" #include "Rect.h" +#include "utils/TinyHashMap.h" + +class SkBitmap; namespace android { namespace uirenderer { @@ -31,16 +34,21 @@ class DrawOp; class SaveOp; class SaveLayerOp; class StateOp; -class DrawOpBatch; class OpenGLRenderer; +class Batch; +class DrawBatch; +class MergingDrawBatch; + +typedef void* mergeid_t; + class DeferredDisplayList { public: DeferredDisplayList() { clear(); } ~DeferredDisplayList() { clear(); } enum OpBatchId { - kOpBatch_None = -1, // Don't batch + kOpBatch_None = 0, // Don't batch kOpBatch_Bitmap, kOpBatch_Patch, kOpBatch_AlphaVertices, @@ -52,8 +60,6 @@ public: kOpBatch_Count, // Add other batch ids before this }; - void clear(); - bool isEmpty() { return mBatches.isEmpty(); } /** @@ -80,6 +86,8 @@ private: */ void resetBatchingState(); + void clear(); + void storeStateOpBarrier(OpenGLRenderer& renderer, StateOp* op); void storeRestoreToCountBarrier(OpenGLRenderer& renderer, StateOp* op, int newSaveCount); @@ -96,8 +104,20 @@ private: Vector<int> mSaveStack; int mComplexClipStackStart; - Vector<DrawOpBatch*> mBatches; - int mBatchIndices[kOpBatch_Count]; + Vector<Batch*> mBatches; + + // Maps batch ids to the most recent *non-merging* batch of that id + Batch* mBatchLookup[kOpBatch_Count]; + + // Points to the index after the most recent barrier + int mEarliestBatchIndex; + + /** + * 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]; }; }; // namespace uirenderer diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index 36c95f9..26abec2 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -485,7 +485,7 @@ void DisplayList::iterate(OpenGLRenderer& renderer, T& handler, const int level) #if DEBUG_DISPLAY_LIST Rect* clipRect = renderer.getClipRect(); - DISPLAY_LIST_LOGD("%*sStart display list (%p, %s), clipRect: %.0f, %.f, %.0f, %.0f", + DISPLAY_LIST_LOGD("%*sStart display list (%p, %s), clipRect: %.0f, %.0f, %.0f, %.0f", level * 2, "", this, mName.string(), clipRect->left, clipRect->top, clipRect->right, clipRect->bottom); #endif diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index a5dee9f..ad7edb1 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -121,6 +121,7 @@ public: }; class DrawOp : public DisplayListOp { +friend class MergingDrawBatch; public: DrawOp(SkPaint* paint) : mPaint(paint), mQuickRejected(false) {} @@ -145,12 +146,41 @@ public: return; } - replayStruct.mDrawGlStatus |= applyDraw(replayStruct.mRenderer, replayStruct.mDirty, level); + replayStruct.mDrawGlStatus |= applyDraw(replayStruct.mRenderer, replayStruct.mDirty); } - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) = 0; + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) = 0; - virtual void onDrawOpDeferred(OpenGLRenderer& renderer) { + /** + * Draw multiple instances of an operation, must be overidden for operations that merge + * + * Currently guarantees certain similarities between ops (see MergingDrawBatch::canMergeWith), + * and pure translation transformations. Other guarantees of similarity should be enforced by + * reducing which operations are tagged as mergeable. + */ + virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty, + const Vector<DrawOp*>& 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); + } + 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. + * + * 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) + */ + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + return false; } // returns true if bounds exist @@ -160,12 +190,11 @@ public: void setQuickRejected(bool quickRejected) { mQuickRejected = quickRejected; } bool getQuickRejected() { return mQuickRejected; } - /** Batching disabled by default, turned on for individual ops */ - virtual DeferredDisplayList::OpBatchId getBatchId() { - return DeferredDisplayList::kOpBatch_None; + inline int getPaintAlpha() { + return OpenGLRenderer::getAlphaDirect(mPaint); } - float strokeWidthOutset() { + inline float strokeWidthOutset() { float width = mPaint->getStrokeWidth(); if (width == 0) return 0.5f; // account for hairline return width * 0.5f; @@ -207,6 +236,14 @@ public: return true; } + bool mergeAllowed() { + // 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 }; @@ -686,20 +723,58 @@ public: paint), mBitmap(bitmap) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawBitmap(mBitmap, mLocalBounds.left, mLocalBounds.top, getPaint(renderer)); } +#define SET_TEXTURE(ptr, posRect, offsetRect, texCoordsRect, xDim, yDim) \ + TextureVertex::set(ptr++, posRect.xDim - offsetRect.left, posRect.yDim - offsetRect.top, \ + texCoordsRect.xDim, texCoordsRect.yDim) + + 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 + 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 float width = mBitmap->width(); + const float height = mBitmap->height(); + for (unsigned int i = 0; i < ops.size(); i++) { + const Rect& opBounds = ops[i]->state.mBounds; + SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, top); + SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, top); + SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, bottom); + + SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, bottom); + SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, top); + SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, bottom); + } + + return renderer.drawBitmaps(mBitmap, ops.size(), &vertices[0], bounds, mPaint); + } + virtual void output(int level, uint32_t logFlags) { OP_LOG("Draw bitmap %p at %f %f", mBitmap, mLocalBounds.left, mLocalBounds.top); } virtual const char* name() { return "DrawBitmap"; } - virtual DeferredDisplayList::OpBatchId getBatchId() { - return DeferredDisplayList::kOpBatch_Bitmap; + + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + *batchId = DeferredDisplayList::kOpBatch_Bitmap; + *mergeId = (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); } + const SkBitmap* bitmap() { return mBitmap; } protected: SkBitmap* mBitmap; }; @@ -713,7 +788,7 @@ public: transform.mapRect(mLocalBounds); } - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawBitmap(mBitmap, mMatrix, getPaint(renderer)); } @@ -721,9 +796,11 @@ public: OP_LOG("Draw bitmap %p matrix " MATRIX_STRING, mBitmap, MATRIX_ARGS(mMatrix)); } - virtual const char* name() { return "DrawBitmap"; } - virtual DeferredDisplayList::OpBatchId getBatchId() { - return DeferredDisplayList::kOpBatch_Bitmap; + virtual const char* name() { return "DrawBitmapMatrix"; } + + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + *batchId = DeferredDisplayList::kOpBatch_Bitmap; + return false; } private: @@ -738,7 +815,7 @@ public: : DrawBoundedOp(dstLeft, dstTop, dstRight, dstBottom, paint), mBitmap(bitmap), mSrc(srcLeft, srcTop, srcRight, srcBottom) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawBitmap(mBitmap, mSrc.left, mSrc.top, mSrc.right, mSrc.bottom, mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer)); @@ -750,8 +827,10 @@ public: } virtual const char* name() { return "DrawBitmapRect"; } - virtual DeferredDisplayList::OpBatchId getBatchId() { - return DeferredDisplayList::kOpBatch_Bitmap; + + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + *batchId = DeferredDisplayList::kOpBatch_Bitmap; + return false; } private: @@ -764,7 +843,7 @@ public: DrawBitmapDataOp(SkBitmap* bitmap, float left, float top, SkPaint* paint) : DrawBitmapOp(bitmap, left, top, paint) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawBitmapData(mBitmap, mLocalBounds.left, mLocalBounds.top, getPaint(renderer)); } @@ -774,8 +853,10 @@ public: } virtual const char* name() { return "DrawBitmapData"; } - virtual DeferredDisplayList::OpBatchId getBatchId() { - return DeferredDisplayList::kOpBatch_Bitmap; + + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + *batchId = DeferredDisplayList::kOpBatch_Bitmap; + return false; } }; @@ -787,7 +868,7 @@ public: mBitmap(bitmap), mMeshWidth(meshWidth), mMeshHeight(meshHeight), mVertices(vertices), mColors(colors) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawBitmapMesh(mBitmap, mMeshWidth, mMeshHeight, mVertices, mColors, getPaint(renderer)); } @@ -797,8 +878,10 @@ public: } virtual const char* name() { return "DrawBitmapMesh"; } - virtual DeferredDisplayList::OpBatchId getBatchId() { - return DeferredDisplayList::kOpBatch_Bitmap; + + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + *batchId = DeferredDisplayList::kOpBatch_Bitmap; + return false; } private: @@ -820,7 +903,7 @@ public: mColors(colors), mxDivsCount(width), myDivsCount(height), mNumColors(numColors), mAlpha(alpha), mMode(mode) {}; - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + 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, @@ -833,8 +916,11 @@ public: } virtual const char* name() { return "DrawPatch"; } - virtual DeferredDisplayList::OpBatchId getBatchId() { - return DeferredDisplayList::kOpBatch_Patch; + + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + *batchId = DeferredDisplayList::kOpBatch_Patch; + *mergeId = (mergeid_t)mBitmap; + return true; } private: @@ -854,7 +940,7 @@ public: DrawColorOp(int color, SkXfermode::Mode mode) : DrawOp(0), mColor(color), mMode(mode) {}; - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawColor(mColor, mMode); } @@ -882,13 +968,15 @@ public: return true; } - virtual DeferredDisplayList::OpBatchId getBatchId() { + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { if (mPaint->getPathEffect()) { - return DeferredDisplayList::kOpBatch_AlphaMaskTexture; + *batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture; + } else { + *batchId = mPaint->isAntiAlias() ? + DeferredDisplayList::kOpBatch_AlphaVertices : + DeferredDisplayList::kOpBatch_Vertices; } - return mPaint->isAntiAlias() ? - DeferredDisplayList::kOpBatch_AlphaVertices : - DeferredDisplayList::kOpBatch_Vertices; + return false; } }; @@ -897,7 +985,7 @@ public: DrawRectOp(float left, float top, float right, float bottom, SkPaint* paint) : DrawStrokableOp(left, top, right, bottom, paint) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawRect(mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer)); } @@ -915,7 +1003,7 @@ public: : DrawBoundedOp(rects, count, paint), mRects(rects), mCount(count) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawRects(mRects, mCount, getPaint(renderer)); } @@ -925,8 +1013,9 @@ public: virtual const char* name() { return "DrawRects"; } - virtual DeferredDisplayList::OpBatchId getBatchId() { - return DeferredDisplayList::kOpBatch_Vertices; + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + *batchId = DeferredDisplayList::kOpBatch_Vertices; + return false; } private: @@ -940,7 +1029,7 @@ public: float rx, float ry, SkPaint* paint) : DrawStrokableOp(left, top, right, bottom, paint), mRx(rx), mRy(ry) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawRoundRect(mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom, mRx, mRy, getPaint(renderer)); } @@ -962,7 +1051,7 @@ public: : DrawStrokableOp(x - radius, y - radius, x + radius, y + radius, paint), mX(x), mY(y), mRadius(radius) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawCircle(mX, mY, mRadius, getPaint(renderer)); } @@ -983,7 +1072,7 @@ public: DrawOvalOp(float left, float top, float right, float bottom, SkPaint* paint) : DrawStrokableOp(left, top, right, bottom, paint) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawOval(mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer)); } @@ -1002,7 +1091,7 @@ public: : DrawStrokableOp(left, top, right, bottom, paint), mStartAngle(startAngle), mSweepAngle(sweepAngle), mUseCenter(useCenter) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawArc(mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom, mStartAngle, mSweepAngle, mUseCenter, getPaint(renderer)); @@ -1033,13 +1122,16 @@ public: mLocalBounds.set(left, top, left + width, top + height); } - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawPath(mPath, getPaint(renderer)); } - virtual void onDrawOpDeferred(OpenGLRenderer& renderer) { + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { SkPaint* paint = getPaint(renderer); renderer.getCaches().pathCache.precache(mPath, paint); + + *batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture; + return false; } virtual void output(int level, uint32_t logFlags) { @@ -1048,9 +1140,6 @@ public: virtual const char* name() { return "DrawPath"; } - virtual DeferredDisplayList::OpBatchId getBatchId() { - return DeferredDisplayList::kOpBatch_AlphaMaskTexture; - } private: SkPath* mPath; }; @@ -1063,7 +1152,7 @@ public: mLocalBounds.outset(strokeWidthOutset()); } - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawLines(mPoints, mCount, getPaint(renderer)); } @@ -1073,10 +1162,11 @@ public: virtual const char* name() { return "DrawLines"; } - virtual DeferredDisplayList::OpBatchId getBatchId() { - return mPaint->isAntiAlias() ? + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + *batchId = mPaint->isAntiAlias() ? DeferredDisplayList::kOpBatch_AlphaVertices : DeferredDisplayList::kOpBatch_Vertices; + return false; } protected: @@ -1089,7 +1179,7 @@ public: DrawPointsOp(float* points, int count, SkPaint* paint) : DrawLinesOp(points, count, paint) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawPoints(mPoints, mCount, getPaint(renderer)); } @@ -1109,17 +1199,18 @@ public: OP_LOG("Draw some text, %d bytes", mBytesCount); } - virtual void onDrawOpDeferred(OpenGLRenderer& renderer) { + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { SkPaint* paint = getPaint(renderer); FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint); fontRenderer.precache(paint, mText, mCount, mat4::identity()); - } - virtual DeferredDisplayList::OpBatchId getBatchId() { - return mPaint->getColor() == 0xff000000 ? + *batchId = mPaint->getColor() == 0xff000000 ? DeferredDisplayList::kOpBatch_Text : DeferredDisplayList::kOpBatch_ColorText; + + return false; } + protected: const char* mText; int mBytesCount; @@ -1135,7 +1226,7 @@ public: /* TODO: inherit from DrawBounded and init mLocalBounds */ } - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawTextOnPath(mText, mBytesCount, mCount, mPath, mHOffset, mVOffset, getPaint(renderer)); } @@ -1156,7 +1247,7 @@ public: /* TODO: inherit from DrawBounded and init mLocalBounds */ } - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawPosText(mText, mBytesCount, mCount, mPositions, getPaint(renderer)); } @@ -1189,12 +1280,7 @@ public: memset(&mPrecacheTransform.data[0], 0xff, 16 * sizeof(float)); } - /* - * When this method is invoked the state field is initialized to have the - * final rendering state. We can thus use it to process data as it will be - * used at draw time. - */ - virtual void onDrawOpDeferred(OpenGLRenderer& renderer) { + virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { SkPaint* paint = getPaint(renderer); FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint); const mat4& transform = renderer.findBestFontTransform(state.mMatrix); @@ -1202,25 +1288,44 @@ public: fontRenderer.precache(paint, mText, mCount, transform); mPrecacheTransform = transform; } + *batchId = mPaint->getColor() == 0xff000000 ? + DeferredDisplayList::kOpBatch_Text : + DeferredDisplayList::kOpBatch_ColorText; + + *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; } - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawText(mText, mBytesCount, mCount, mX, mY, mPositions, getPaint(renderer), mLength); } + virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty, + const Vector<DrawOp*>& 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++) { + DrawOpMode drawOpMode = (i == ops.size() - 1) ? kDrawOpMode_Flush : kDrawOpMode_Defer; + renderer.restoreDisplayState(ops[i]->state, true); // restore all but the clip + + DrawTextOp& op = *((DrawTextOp*)ops[i]); + status |= renderer.drawText(op.mText, op.mBytesCount, op.mCount, op.mX, op.mY, + op.mPositions, op.getPaint(renderer), op.mLength, drawOpMode); + } + return status; + } + virtual void output(int level, uint32_t logFlags) { OP_LOG("Draw Text of count %d, bytes %d", mCount, mBytesCount); } virtual const char* name() { return "DrawText"; } - virtual DeferredDisplayList::OpBatchId getBatchId() { - return mPaint->getColor() == 0xff000000 ? - DeferredDisplayList::kOpBatch_Text : - DeferredDisplayList::kOpBatch_ColorText; - } - private: const char* mText; int mBytesCount; @@ -1241,7 +1346,7 @@ public: DrawFunctorOp(Functor* functor) : DrawOp(0), mFunctor(functor) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { renderer.startMark("GL functor"); status_t ret = renderer.callDrawGLFunction(mFunctor, dirty); renderer.endMark(); @@ -1269,14 +1374,14 @@ public: mDisplayList->defer(deferStruct, level + 1); } } -virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level) { + virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level) { if (mDisplayList && mDisplayList->isRenderable()) { mDisplayList->replay(replayStruct, level + 1); } } // NOT USED since replay() is overridden - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return DrawGlInfo::kStatusDone; } @@ -1299,7 +1404,7 @@ public: DrawLayerOp(Layer* layer, float x, float y) : DrawOp(0), mLayer(layer), mX(x), mY(y) {} - virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level) { + virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawLayer(mLayer, mX, mY); } diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 0b8f7e6..876c38a 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -276,6 +276,15 @@ status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float srcLeft, float bitmap = refBitmap(bitmap); paint = refPaint(paint); + if (srcLeft == 0 && srcTop == 0 && + srcRight == bitmap->width() && srcBottom == bitmap->height() && + (srcBottom - srcTop == dstBottom - dstTop) && + (srcRight - srcLeft == dstRight - dstLeft)) { + // transform simple rect to rect drawing case into position bitmap ops, since they merge + addDrawOp(new (alloc()) DrawBitmapOp(bitmap, dstLeft, dstTop, paint)); + return DrawGlInfo::kStatusDone; + } + addDrawOp(new (alloc()) DrawBitmapRectOp(bitmap, srcLeft, srcTop, srcRight, srcBottom, dstLeft, dstTop, dstRight, dstBottom, paint)); @@ -413,7 +422,9 @@ 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) { + float x, float y, const float* positions, SkPaint* paint, + float length, DrawOpMode drawOpMode) { + if (!text || count <= 0) return DrawGlInfo::kStatusDone; if (length < 0.0f) length = paint->measureText(text, bytesCount); diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index 19f7eb6..75abad6 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -121,8 +121,9 @@ public: float hOffset, float vOffset, SkPaint* paint); 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); + virtual status_t drawText(const char* text, int bytesCount, int count, float x, float y, + const float* positions, SkPaint* paint, float length, DrawOpMode drawOpMode); + virtual status_t drawRects(const float* rects, int count, SkPaint* paint); virtual void resetShader(); diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp index e80b325..19b3849 100644 --- a/libs/hwui/Dither.cpp +++ b/libs/hwui/Dither.cpp @@ -21,38 +21,50 @@ namespace android { namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -// Must be a power of two -#define DITHER_KERNEL_SIZE 4 - -/////////////////////////////////////////////////////////////////////////////// // Lifecycle /////////////////////////////////////////////////////////////////////////////// void Dither::bindDitherTexture() { if (!mInitialized) { - const uint8_t pattern[] = { - 0, 8, 2, 10, - 12, 4, 14, 6, - 3, 11, 1, 9, - 15, 7, 13, 5 - }; + bool useFloatTexture = Extensions::getInstance().getMajorGlVersion() >= 3; glGenTextures(1, &mDitherTexture); glBindTexture(GL_TEXTURE_2D, mDitherTexture); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0, - GL_ALPHA, GL_UNSIGNED_BYTE, &pattern); + if (useFloatTexture) { + // We use a R16F texture, let's remap the alpha channel to the + // red channel to avoid changing the shader sampling code on GL ES 3.0+ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED); + + float dither = 1.0f / (255.0f * DITHER_KERNEL_SIZE * DITHER_KERNEL_SIZE); + const GLfloat pattern[] = { + 0 * dither, 8 * dither, 2 * dither, 10 * dither, + 12 * dither, 4 * dither, 14 * dither, 6 * dither, + 3 * dither, 11 * dither, 1 * dither, 9 * dither, + 15 * dither, 7 * dither, 13 * dither, 5 * dither + }; + + glPixelStorei(GL_UNPACK_ALIGNMENT, sizeof(GLfloat)); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R16F, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0, + GL_RED, GL_FLOAT, &pattern); + } else { + const uint8_t pattern[] = { + 0, 8, 2, 10, + 12, 4, 14, 6, + 3, 11, 1, 9, + 15, 7, 13, 5 + }; + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0, + GL_ALPHA, GL_UNSIGNED_BYTE, &pattern); + } mInitialized = true; } else { @@ -63,6 +75,7 @@ void Dither::bindDitherTexture() { void Dither::clear() { if (mInitialized) { glDeleteTextures(1, &mDitherTexture); + mInitialized = false; } } @@ -76,10 +89,7 @@ void Dither::setupProgram(Program* program, GLuint* textureUnit) { bindDitherTexture(); - float ditherSize = 1.0f / DITHER_KERNEL_SIZE; glUniform1i(program->getUniform("ditherSampler"), textureSlot); - glUniform1f(program->getUniform("ditherSize"), ditherSize); - glUniform1f(program->getUniform("ditherSizeSquared"), ditherSize * ditherSize); } }; // namespace uirenderer diff --git a/libs/hwui/Dither.h b/libs/hwui/Dither.h index 34cf9bf..4d1f921 100644 --- a/libs/hwui/Dither.h +++ b/libs/hwui/Dither.h @@ -17,13 +17,23 @@ #ifndef ANDROID_HWUI_DITHER_H #define ANDROID_HWUI_DITHER_H -#include <GLES2/gl2.h> +#include <GLES3/gl3.h> #include "Program.h" namespace android { namespace uirenderer { +/////////////////////////////////////////////////////////////////////////////// +// Defines +/////////////////////////////////////////////////////////////////////////////// + +// Must be a power of two +#define DITHER_KERNEL_SIZE 4 +// These must not use the .0f notation as they are used from GLSL +#define DITHER_KERNEL_SIZE_INV (1.0 / 4.0) +#define DITHER_KERNEL_SIZE_INV_SQUARE (1.0 / 16.0) + /** * Handles dithering for programs. */ diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 26c7e5d..543cfa2 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -31,7 +31,9 @@ #include "Caches.h" #include "Debug.h" +#include "Extensions.h" #include "FontRenderer.h" +#include "PixelBuffer.h" #include "Rect.h" namespace android { @@ -55,7 +57,6 @@ FontRenderer::FontRenderer() : mGammaTable = NULL; mInitialized = false; - mMaxNumberOfQuads = 1024; mCurrentCacheTexture = NULL; @@ -132,26 +133,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()) { @@ -225,7 +213,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(); @@ -238,7 +226,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 @@ -304,7 +292,7 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp } CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) { - CacheTexture* cacheTexture = new CacheTexture(width, height, mMaxNumberOfQuads); + CacheTexture* cacheTexture = new CacheTexture(width, height, gMaxNumberOfQuads); if (allocate) { Caches::getInstance().activeTexture(0); @@ -331,12 +319,12 @@ void FontRenderer::initTextTexture() { // Avoid having to reallocate memory and render quad by quad void FontRenderer::initVertexArrayBuffers() { - uint32_t numIndices = mMaxNumberOfQuads * 6; + 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 < mMaxNumberOfQuads; i++) { + for (uint32_t i = 0; i < gMaxNumberOfQuads; i++) { int i6 = i * 6; int i4 = i * 4; @@ -375,34 +363,40 @@ void FontRenderer::checkTextureUpdate() { Caches& caches = Caches::getInstance(); GLuint lastTextureId = 0; + + bool resetPixelStore = false; + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + // Iterate over all the cache textures and see which ones need to be updated for (uint32_t i = 0; i < mCacheTextures.size(); i++) { CacheTexture* cacheTexture = mCacheTextures[i]; - if (cacheTexture->isDirty() && cacheTexture->getTexture()) { - // Can't copy inner rect; glTexSubimage expects pointer to deal with entire buffer - // of data. So expand the dirty rect to the encompassing horizontal stripe. - const Rect* dirtyRect = cacheTexture->getDirtyRect(); - uint32_t x = 0; - uint32_t y = dirtyRect->top; - uint32_t width = cacheTexture->getWidth(); - uint32_t height = dirtyRect->getHeight(); - void* textureData = cacheTexture->getTexture() + y * width; - + 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 - 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 (resetPixelStore) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + } + mUploadTexture = false; } @@ -512,13 +506,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; } @@ -532,6 +527,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; @@ -551,10 +551,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; @@ -585,9 +587,13 @@ 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) { + const float* positions, Rect* bounds, Functor* functor, bool forceFinish) { if (!mCurrentFont) { ALOGE("No font set"); return false; @@ -595,7 +601,10 @@ bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *t initRender(clip, bounds, functor); mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions); - finishRender(); + + if (forceFinish) { + finishRender(); + } return mDrawn; } @@ -663,5 +672,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 1da3b6c..307a1d9 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -61,11 +61,13 @@ 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, uint32_t len, int numGlyphs, int x, int y, const float* positions, Rect* bounds, - Functor* functor); + Functor* functor, bool forceFinish = true); + // 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); @@ -95,20 +97,13 @@ 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; + static const uint32_t gMaxNumberOfQuads = 2048; + const uint8_t* mGammaTable; void allocateTextureMemory(CacheTexture* cacheTexture); @@ -162,7 +157,6 @@ private: bool mUploadTexture; - uint32_t mMaxNumberOfQuads; uint32_t mIndexBufferID; Functor* mFunctor; diff --git a/libs/hwui/GammaFontRenderer.cpp b/libs/hwui/GammaFontRenderer.cpp index bd0a4b3..06d2aad 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 5c1860e..bbfa66d 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/GradientCache.cpp b/libs/hwui/GradientCache.cpp index d681609..507ed95 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -27,13 +27,6 @@ namespace android { namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -#define GRADIENT_TEXTURE_HEIGHT 2 -#define GRADIENT_BYTES_PER_PIXEL 4 - -/////////////////////////////////////////////////////////////////////////////// // Functions /////////////////////////////////////////////////////////////////////////////// @@ -83,6 +76,10 @@ GradientCache::GradientCache(): glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); mCache.setOnEntryRemovedListener(this); + + const Extensions& extensions = Extensions::getInstance(); + mUseFloatTexture = extensions.getMajorGlVersion() >= 3; + mHasNpot = extensions.hasNPot(); } GradientCache::GradientCache(uint32_t maxByteSize): @@ -120,7 +117,7 @@ void GradientCache::setMaxSize(uint32_t maxSize) { void GradientCache::operator()(GradientCacheEntry& shader, Texture*& texture) { if (texture) { - const uint32_t size = texture->width * texture->height * GRADIENT_BYTES_PER_PIXEL; + const uint32_t size = texture->width * texture->height * bytesPerPixel(); mSize -= size; glDeleteTextures(1, &texture->id); @@ -151,8 +148,11 @@ void GradientCache::getGradientInfo(const uint32_t* colors, const int count, GradientInfo& info) { uint32_t width = 256 * (count - 1); - if (!Extensions::getInstance().hasNPot()) { - width = 1 << (31 - __builtin_clz(width)); + // If the npot extension is not supported we cannot use non-clamp + // wrap modes. We therefore find the nearest largest power of 2 + // unless width is already a power of 2 + if (!mHasNpot && (width & (width - 1)) != 0) { + width = 1 << (32 - __builtin_clz(width)); } bool hasAlpha = false; @@ -175,12 +175,12 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient, Texture* texture = new Texture; texture->width = info.width; - texture->height = GRADIENT_TEXTURE_HEIGHT; + texture->height = 2; texture->blend = info.hasAlpha; texture->generation = 1; // Asume the cache is always big enough - const uint32_t size = texture->width * texture->height * GRADIENT_BYTES_PER_PIXEL; + const uint32_t size = texture->width * texture->height * bytesPerPixel(); while (getSize() + size > mMaxSize) { mCache.removeOldest(); } @@ -193,69 +193,110 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient, return texture; } +size_t GradientCache::bytesPerPixel() const { + // We use 4 channels (RGBA) + return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t)); +} + +void GradientCache::splitToBytes(uint32_t inColor, GradientColor& outColor) const { + outColor.r = (inColor >> 16) & 0xff; + outColor.g = (inColor >> 8) & 0xff; + outColor.b = (inColor >> 0) & 0xff; + outColor.a = (inColor >> 24) & 0xff; +} + +void GradientCache::splitToFloats(uint32_t inColor, GradientColor& outColor) const { + outColor.r = ((inColor >> 16) & 0xff) / 255.0f; + outColor.g = ((inColor >> 8) & 0xff) / 255.0f; + outColor.b = ((inColor >> 0) & 0xff) / 255.0f; + outColor.a = ((inColor >> 24) & 0xff) / 255.0f; +} + +void GradientCache::mixBytes(GradientColor& start, GradientColor& end, float amount, + uint8_t*& dst) const { + float oppAmount = 1.0f - amount; + const float alpha = start.a * oppAmount + end.a * amount; + const float a = alpha / 255.0f; + + *dst++ = uint8_t(a * (start.r * oppAmount + end.r * amount)); + *dst++ = uint8_t(a * (start.g * oppAmount + end.g * amount)); + *dst++ = uint8_t(a * (start.b * oppAmount + end.b * amount)); + *dst++ = uint8_t(alpha); +} + +void GradientCache::mixFloats(GradientColor& start, GradientColor& end, float amount, + uint8_t*& dst) const { + float oppAmount = 1.0f - amount; + const float a = start.a * oppAmount + end.a * amount; + + float* d = (float*) dst; + *d++ = a * (start.r * oppAmount + end.r * amount); + *d++ = a * (start.g * oppAmount + end.g * amount); + *d++ = a * (start.b * oppAmount + end.b * amount); + *d++ = a; + + dst += 4 * sizeof(float); +} + void GradientCache::generateTexture(uint32_t* colors, float* positions, int count, Texture* texture) { - const uint32_t width = texture->width; - const GLsizei rowBytes = width * GRADIENT_BYTES_PER_PIXEL; - uint32_t pixels[width * texture->height]; + const GLsizei rowBytes = width * bytesPerPixel(); + uint8_t pixels[rowBytes * texture->height]; - int currentPos = 1; + static ChannelSplitter gSplitters[] = { + &android::uirenderer::GradientCache::splitToBytes, + &android::uirenderer::GradientCache::splitToFloats, + }; + ChannelSplitter split = gSplitters[mUseFloatTexture]; - float startA = (colors[0] >> 24) & 0xff; - float startR = (colors[0] >> 16) & 0xff; - float startG = (colors[0] >> 8) & 0xff; - float startB = (colors[0] >> 0) & 0xff; + static ChannelMixer gMixers[] = { + &android::uirenderer::GradientCache::mixBytes, + &android::uirenderer::GradientCache::mixFloats, + }; + ChannelMixer mix = gMixers[mUseFloatTexture]; - float endA = (colors[1] >> 24) & 0xff; - float endR = (colors[1] >> 16) & 0xff; - float endG = (colors[1] >> 8) & 0xff; - float endB = (colors[1] >> 0) & 0xff; + GradientColor start; + (this->*split)(colors[0], start); - float start = positions[0]; - float distance = positions[1] - start; + GradientColor end; + (this->*split)(colors[1], end); - uint8_t* p = (uint8_t*) pixels; + int currentPos = 1; + float startPos = positions[0]; + float distance = positions[1] - startPos; + + uint8_t* dst = pixels; for (uint32_t x = 0; x < width; x++) { float pos = x / float(width - 1); if (pos > positions[currentPos]) { - startA = endA; - startR = endR; - startG = endG; - startB = endB; - start = positions[currentPos]; + start = end; + startPos = positions[currentPos]; currentPos++; - endA = (colors[currentPos] >> 24) & 0xff; - endR = (colors[currentPos] >> 16) & 0xff; - endG = (colors[currentPos] >> 8) & 0xff; - endB = (colors[currentPos] >> 0) & 0xff; - distance = positions[currentPos] - start; + (this->*split)(colors[currentPos], end); + distance = positions[currentPos] - startPos; } - float amount = (pos - start) / distance; - float oppAmount = 1.0f - amount; - - const float alpha = startA * oppAmount + endA * amount; - const float a = alpha / 255.0f; - *p++ = uint8_t(a * (startR * oppAmount + endR * amount)); - *p++ = uint8_t(a * (startG * oppAmount + endG * amount)); - *p++ = uint8_t(a * (startB * oppAmount + endB * amount)); - *p++ = uint8_t(alpha); + float amount = (pos - startPos) / distance; + (this->*mix)(start, end, amount, dst); } - for (int i = 1; i < GRADIENT_TEXTURE_HEIGHT; i++) { - memcpy(pixels + width * i, pixels, rowBytes); - } + memcpy(pixels + rowBytes, pixels, rowBytes); glGenTextures(1, &texture->id); - glBindTexture(GL_TEXTURE_2D, texture->id); - glPixelStorei(GL_UNPACK_ALIGNMENT, GRADIENT_BYTES_PER_PIXEL); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, pixels); + if (mUseFloatTexture) { + // We have to use GL_RGBA16F because GL_RGBA32F does not support filtering + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, texture->height, 0, + GL_RGBA, GL_FLOAT, pixels); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, pixels); + } texture->setFilter(GL_LINEAR); texture->setWrap(GL_CLAMP_TO_EDGE); diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h index 7dc5b8a..43934d9 100644 --- a/libs/hwui/GradientCache.h +++ b/libs/hwui/GradientCache.h @@ -17,7 +17,7 @@ #ifndef ANDROID_HWUI_GRADIENT_CACHE_H #define ANDROID_HWUI_GRADIENT_CACHE_H -#include <GLES2/gl2.h> +#include <GLES3/gl3.h> #include <SkShader.h> @@ -160,12 +160,35 @@ private: void getGradientInfo(const uint32_t* colors, const int count, GradientInfo& info); + size_t bytesPerPixel() const; + + struct GradientColor { + float r; + float g; + float b; + float a; + }; + + typedef void (GradientCache::*ChannelSplitter)(uint32_t inColor, + GradientColor& outColor) const; + + void splitToBytes(uint32_t inColor, GradientColor& outColor) const; + void splitToFloats(uint32_t inColor, GradientColor& outColor) const; + + typedef void (GradientCache::*ChannelMixer)(GradientColor& start, GradientColor& end, + float amount, uint8_t*& dst) const; + + void mixBytes(GradientColor& start, GradientColor& end, float amount, uint8_t*& dst) const; + void mixFloats(GradientColor& start, GradientColor& end, float amount, uint8_t*& dst) const; + LruCache<GradientCacheEntry, Texture*> mCache; uint32_t mSize; uint32_t mMaxSize; GLint mMaxTextureSize; + bool mUseFloatTexture; + bool mHasNpot; Vector<SkShader*> mGarbage; mutable Mutex mLock; diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index 63bb73f..a718294 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -90,7 +90,7 @@ bool Layer::resize(const uint32_t width, const uint32_t height) { if (fbo) { Caches::getInstance().activeTexture(0); bindTexture(); - allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE); + allocateTexture(); if (glGetError() != GL_NO_ERROR) { setSize(oldWidth, oldHeight); @@ -167,7 +167,6 @@ void Layer::defer() { displayList->defer(deferredState, 0); deferredUpdateScheduled = false; - displayList = NULL; } void Layer::flush() { @@ -182,7 +181,7 @@ void Layer::flush() { renderer = NULL; dirtyRect.setEmpty(); - deferredList->clear(); + displayList = NULL; } } diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index 27e0cf1..715dfa4 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -255,13 +255,14 @@ struct Layer { texture.id = 0; } - inline void allocateTexture(GLenum format, GLenum storage) { + inline void allocateTexture() { #if DEBUG_LAYERS ALOGD(" Allocate layer: %dx%d", getWidth(), getHeight()); #endif if (texture.id) { - glTexImage2D(renderTarget, 0, format, getWidth(), getHeight(), 0, - format, storage, NULL); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + glTexImage2D(renderTarget, 0, GL_RGBA, getWidth(), getHeight(), 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); } } diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index 8451048..3e55fff 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -256,7 +256,7 @@ Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque // Initialize the texture if needed if (layer->isEmpty()) { layer->setEmpty(false); - layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE); + layer->allocateTexture(); // This should only happen if we run out of memory if (glGetError() != GL_NO_ERROR) { diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 1138998..f81b4ff 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -112,11 +112,9 @@ static const Blender gBlendsSwap[] = { OpenGLRenderer::OpenGLRenderer(): mCaches(Caches::getInstance()), mExtensions(Extensions::getInstance()) { - mDrawModifiers.mShader = NULL; - mDrawModifiers.mColorFilter = NULL; + // *set* draw modifiers to be 0 + memset(&mDrawModifiers, 0, sizeof(mDrawModifiers)); mDrawModifiers.mOverrideLayerAlpha = 1.0f; - mDrawModifiers.mHasShadow = false; - mDrawModifiers.mHasDrawFilter = false; memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices)); @@ -571,8 +569,8 @@ void OpenGLRenderer::updateLayers() { startMark("Defer Layer Updates"); } - // Note: it is very important to update the layers in reverse order - for (int i = count - 1; i >= 0; i--) { + // Note: it is very important to update the layers in order + for (int i = 0; i < count; i++) { Layer* layer = mLayerUpdates.itemAt(i); updateLayer(layer, false); if (CC_UNLIKELY(mCaches.drawDeferDisabled)) { @@ -594,8 +592,8 @@ void OpenGLRenderer::flushLayers() { startMark("Apply Layer Updates"); char layerName[12]; - // Note: it is very important to update the layers in reverse order - for (int i = count - 1; i >= 0; i--) { + // Note: it is very important to update the layers in order + for (int i = 0; i < count; i++) { sprintf(layerName, "Layer #%d", i); startMark(layerName); @@ -922,7 +920,7 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLui // Initialize the texture if needed if (layer->isEmpty()) { - layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE); + layer->allocateTexture(); layer->setEmpty(false); } @@ -1330,10 +1328,9 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef } } - if (stateDeferFlags & kStateDeferFlag_Clip) { + state.mClipValid = (stateDeferFlags & kStateDeferFlag_Clip); + if (state.mClipValid) { state.mClip.set(currentClip); - } else { - state.mClip.setEmpty(); } // Transform, drawModifiers, and alpha always deferred, since they are used by state operations @@ -1344,17 +1341,22 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef return false; } -void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state) { +void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool skipClipRestore) { currentTransform().load(state.mMatrix); mDrawModifiers = state.mDrawModifiers; mSnapshot->alpha = state.mAlpha; - if (!state.mClip.isEmpty()) { + if (state.mClipValid && !skipClipRestore) { mSnapshot->setClip(state.mClip.left, state.mClip.top, state.mClip.right, state.mClip.bottom); dirtyClip(); } } +void OpenGLRenderer::setFullScreenClip() { + mSnapshot->setClip(0, 0, mWidth, mHeight); + dirtyClip(); +} + /////////////////////////////////////////////////////////////////////////////// // Transforms /////////////////////////////////////////////////////////////////////////////// @@ -1905,14 +1907,15 @@ void OpenGLRenderer::finishDrawTexture() { status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t replayFlags) { + status_t status; // All the usual checks and setup operations (quickReject, setupDraw, etc.) // will be performed by the display list itself if (displayList && displayList->isRenderable()) { if (CC_UNLIKELY(mCaches.drawDeferDisabled)) { - startFrame(); + status = startFrame(); ReplayStateStruct replayStruct(*this, dirty, replayFlags); displayList->replay(replayStruct, 0); - return replayStruct.mDrawGlStatus; + return status | replayStruct.mDrawGlStatus; } DeferredDisplayList deferredList; @@ -1920,9 +1923,9 @@ status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList, Rect& dirty, displayList->defer(deferStruct, 0); flushLayers(); - startFrame(); + status = startFrame(); - return deferredList.flush(*this, dirty); + return status | deferredList.flush(*this, dirty); } return DrawGlInfo::kStatusDone; @@ -1962,6 +1965,42 @@ void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, Sk (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); + + mCaches.activeTexture(0); + Texture* texture = mCaches.textureCache.get(bitmap); + if (!texture) return DrawGlInfo::kStatusDone; + const AutoTexture autoCleanup(texture); + + int alpha; + SkXfermode::Mode mode; + getAlphaAndMode(paint, &alpha, &mode); + + texture->setWrap(GL_CLAMP_TO_EDGE, true); + texture->setFilter(GL_NEAREST, true); // merged ops are always pure-translation for now + + const float x = (int) floorf(bounds.left + 0.5f); + const float y = (int) floorf(bounds.top + 0.5f); + if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) { + int color = paint != NULL ? paint->getColor() : 0; + 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); + } 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); + } + + return DrawGlInfo::kStatusDrew; +} + status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint) { const float right = left + bitmap->width(); const float bottom = top + bitmap->height(); @@ -2643,6 +2682,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; @@ -2792,8 +2834,11 @@ mat4 OpenGLRenderer::findBestFontTransform(const mat4& transform) const { } status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, - float x, float y, const float* positions, SkPaint* paint, float length) { - if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) { + float x, float y, const float* positions, SkPaint* paint, float length, + DrawOpMode drawOpMode) { + + if (drawOpMode == kDrawOpMode_Immediate && + (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint))) { return DrawGlInfo::kStatusDone; } @@ -2811,8 +2856,13 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, SkPaint::FontMetrics metrics; paint->getFontMetrics(&metrics, 0.0f); - if (quickReject(x, y + metrics.fTop, x + length, y + metrics.fBottom)) { - return DrawGlInfo::kStatusDone; + if (drawOpMode == kDrawOpMode_Immediate) { + if (quickReject(x, y + metrics.fTop, x + length, y + metrics.fBottom)) { + return DrawGlInfo::kStatusDone; + } + } else { + // merged draw operations don't need scissor, but clip should still be valid + mCaches.setScissorEnabled(mScissorOptimizationDisabled); } const float oldX = x; @@ -2864,17 +2914,20 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, bool status; TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint); + + // don't call issuedrawcommand, do it at end of batch + bool forceFinish = (drawOpMode != kDrawOpMode_Defer); if (CC_UNLIKELY(paint->getTextAlign() != SkPaint::kLeft_Align)) { SkPaint paintCopy(*paint); paintCopy.setTextAlign(SkPaint::kLeft_Align); status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y, - positions, hasActiveLayer ? &bounds : NULL, &functor); + positions, hasActiveLayer ? &bounds : NULL, &functor, forceFinish); } else { status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y, - positions, hasActiveLayer ? &bounds : NULL, &functor); + positions, hasActiveLayer ? &bounds : NULL, &functor, forceFinish); } - if (status && hasActiveLayer) { + if ((status || drawOpMode != kDrawOpMode_Immediate) && hasActiveLayer) { if (!pureTranslate) { transform.mapRect(bounds); } @@ -3089,7 +3142,11 @@ void OpenGLRenderer::setupShadow(float radius, float dx, float dy, int color) { /////////////////////////////////////////////////////////////////////////////// void OpenGLRenderer::resetPaintFilter() { + // when clearing the PaintFilter, the masks should also be cleared for simple DrawModifier + // comparison, see MergingDrawBatch::canMergeWith mDrawModifiers.mHasDrawFilter = false; + mDrawModifiers.mPaintFilterClearBits = 0; + mDrawModifiers.mPaintFilterSetBits = 0; } void OpenGLRenderer::setupPaintFilter(int clearBits, int setBits) { @@ -3361,7 +3418,7 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, float bottom, GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode, GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount, - bool ignoreTransform, bool dirty) { + bool ignoreTransform, bool ignoreScale, bool dirty) { setupDraw(); setupDrawWithTexture(true); @@ -3373,7 +3430,11 @@ void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, f setupDrawBlending(true, mode); setupDrawProgram(); if (!dirty) setupDrawDirtyRegionsDisabled(); - setupDrawModelView(left, top, right, bottom, ignoreTransform); + if (!ignoreScale) { + setupDrawModelView(left, top, right, bottom, ignoreTransform); + } else { + setupDrawModelViewTranslate(left, top, right, bottom, ignoreTransform); + } setupDrawTexture(texture); setupDrawPureColorUniforms(); setupDrawColorFilterUniforms(); diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index dd7a5a2..a0ad888 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -71,10 +71,17 @@ enum StateDeferFlags { kStateDeferFlag_Clip = 0x2 }; +enum DrawOpMode { + kDrawOpMode_Immediate, + kDrawOpMode_Defer, + kDrawOpMode_Flush +}; + struct DeferredDisplayState { - Rect mBounds; // local bounds, mapped with matrix to be in screen space coordinates, clipped. + 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; @@ -232,6 +239,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); 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, @@ -261,7 +270,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 = -1.0f); + const float* positions, SkPaint* paint, float length = -1.0f, + DrawOpMode drawOpMode = kDrawOpMode_Immediate); virtual status_t drawRects(const float* rects, int count, SkPaint* paint); virtual void resetShader(); @@ -282,7 +292,8 @@ public: SkPaint* filterPaint(SkPaint* paint); bool storeDisplayState(DeferredDisplayState& state, int stateDeferFlags); - void restoreDisplayState(const DeferredDisplayState& state); + void restoreDisplayState(const DeferredDisplayState& state, bool skipClipRestore = false); + void setFullScreenClip(); const DrawModifiers& getDrawModifiers() { return mDrawModifiers; } void setDrawModifiers(const DrawModifiers& drawModifiers) { mDrawModifiers = drawModifiers; } @@ -336,20 +347,18 @@ public: * @param mode Where to store the resulting xfermode */ static inline void getAlphaAndModeDirect(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) { - if (paint) { - *mode = getXfermode(paint->getXfermode()); - - // Skia draws using the color's alpha channel if < 255 - // Otherwise, it uses the paint's alpha - int color = paint->getColor(); - *alpha = (color >> 24) & 0xFF; - if (*alpha == 255) { - *alpha = paint->getAlpha(); - } - } else { - *mode = SkXfermode::kSrcOver_Mode; - *alpha = 255; - } + *mode = getXfermodeDirect(paint); + *alpha = getAlphaDirect(paint); + } + + static inline SkXfermode::Mode getXfermodeDirect(SkPaint* paint) { + if (!paint) return SkXfermode::kSrcOver_Mode; + return getXfermode(paint->getXfermode()); + } + + static inline int getAlphaDirect(SkPaint* paint) { + if (!paint) return 255; + return paint->getAlpha(); } /** @@ -358,6 +367,20 @@ public: */ mat4 findBestFontTransform(const mat4& transform) const; +#if DEBUG_MERGE_BEHAVIOR + void drawScreenSpaceColorRect(float left, float top, float right, float bottom, int color) { + mCaches.setScissorEnabled(false); + + // should only be called outside of other draw ops, so stencil can only be in test state + bool stencilWasEnabled = mCaches.stencil.isTestEnabled(); + mCaches.stencil.disable(); + + drawColorRect(left, top, right, bottom, color, SkXfermode::kSrcOver_Mode, true); + + if (stencilWasEnabled) mCaches.stencil.enableTest(); + } +#endif + protected: /** * Computes the projection matrix, initialize the first snapshot @@ -778,7 +801,7 @@ private: void drawAlpha8TextureMesh(float left, float top, float right, float bottom, GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode, GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount, - bool ignoreTransform, bool dirty = true); + bool ignoreTransform, bool ignoreScale = false, bool dirty = true); /** * Draws text underline and strike-through if needed. diff --git a/libs/hwui/PixelBuffer.cpp b/libs/hwui/PixelBuffer.cpp new file mode 100644 index 0000000..8280370 --- /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 0000000..32d5417 --- /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/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index f78fb2d..8eb85e5 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -19,6 +19,7 @@ #include <utils/String8.h> #include "Caches.h" +#include "Dither.h" #include "ProgramCache.h" namespace android { @@ -32,6 +33,9 @@ namespace uirenderer { #define MODULATE_OP_MODULATE 1 #define MODULATE_OP_MODULATE_A8 2 +#define STR(x) STR1(x) +#define STR1(x) #x + /////////////////////////////////////////////////////////////////////////////// // Vertex shaders snippets /////////////////////////////////////////////////////////////////////////////// @@ -51,17 +55,8 @@ const char* gVS_Header_Uniforms = "uniform mat4 transform;\n"; const char* gVS_Header_Uniforms_IsPoint = "uniform mediump float pointSize;\n"; -const char* gVS_Header_Uniforms_HasGradient[3] = { - // Linear - "uniform mat4 screenSpace;\n" - "uniform float ditherSize;\n", - // Circular - "uniform mat4 screenSpace;\n" - "uniform float ditherSize;\n", - // Sweep - "uniform mat4 screenSpace;\n" - "uniform float ditherSize;\n" -}; +const char* gVS_Header_Uniforms_HasGradient = + "uniform mat4 screenSpace;\n"; const char* gVS_Header_Uniforms_HasBitmap = "uniform mat4 textureTransform;\n" "uniform mediump vec2 textureDimension;\n"; @@ -105,21 +100,21 @@ const char* gVS_Main_OutTransformedTexCoords = const char* gVS_Main_OutGradient[6] = { // Linear " linear = vec2((screenSpace * position).x, 0.5);\n" - " ditherTexCoords = (transform * position).xy * ditherSize;\n", + " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", " linear = (screenSpace * position).x;\n" - " ditherTexCoords = (transform * position).xy * ditherSize;\n", + " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", // Circular " circular = (screenSpace * position).xy;\n" - " ditherTexCoords = (transform * position).xy * ditherSize;\n", + " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", " circular = (screenSpace * position).xy;\n" - " ditherTexCoords = (transform * position).xy * ditherSize;\n", + " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", // Sweep " sweep = (screenSpace * position).xy;\n" - " ditherTexCoords = (transform * position).xy * ditherSize;\n", + " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", " sweep = (screenSpace * position).xy;\n" - " ditherTexCoords = (transform * position).xy * ditherSize;\n", + " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", }; const char* gVS_Main_OutBitmapTexCoords = " outBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n"; @@ -153,24 +148,14 @@ const char* gFS_Uniforms_TextureSampler = "uniform sampler2D baseSampler;\n"; const char* gFS_Uniforms_ExternalTextureSampler = "uniform samplerExternalOES baseSampler;\n"; -#define FS_UNIFORMS_DITHER \ - "uniform float ditherSizeSquared;\n" \ - "uniform sampler2D ditherSampler;\n" -#define FS_UNIFORMS_GRADIENT \ - "uniform vec4 startColor;\n" \ +const char* gFS_Uniforms_Dither = + "uniform sampler2D ditherSampler;"; +const char* gFS_Uniforms_GradientSampler[2] = { + "%s\n" + "uniform sampler2D gradientSampler;\n", + "%s\n" + "uniform vec4 startColor;\n" "uniform vec4 endColor;\n" -const char* gFS_Uniforms_GradientSampler[6] = { - // Linear - FS_UNIFORMS_DITHER "uniform sampler2D gradientSampler;\n", - FS_UNIFORMS_DITHER FS_UNIFORMS_GRADIENT, - - // Circular - FS_UNIFORMS_DITHER "uniform sampler2D gradientSampler;\n", - FS_UNIFORMS_DITHER FS_UNIFORMS_GRADIENT, - - // Sweep - FS_UNIFORMS_DITHER "uniform sampler2D gradientSampler;\n", - FS_UNIFORMS_DITHER FS_UNIFORMS_GRADIENT }; const char* gFS_Uniforms_BitmapSampler = "uniform sampler2D bitmapSampler;\n"; @@ -197,10 +182,14 @@ const char* gFS_Main_PointBitmapTexCoords = " highp vec2 outBitmapTexCoords = outPointBitmapTexCoords + " "((gl_PointCoord - vec2(0.5, 0.5)) * textureDimension * vec2(pointSize, pointSize));\n"; -#define FS_MAIN_DITHER \ - "texture2D(ditherSampler, ditherTexCoords).a * ditherSizeSquared" +const char* gFS_Main_Dither[2] = { + // ES 2.0 + "texture2D(ditherSampler, ditherTexCoords).a * " STR(DITHER_KERNEL_SIZE_INV_SQUARE), + // ES 3.0 + "texture2D(ditherSampler, ditherTexCoords).a" +}; const char* gFS_Main_AddDitherToGradient = - " gradientColor += " FS_MAIN_DITHER ";\n"; + " gradientColor += %s;\n"; // Fast cases const char* gFS_Fast_SingleColor = @@ -233,18 +222,18 @@ const char* gFS_Fast_SingleModulateA8Texture_ApplyGamma = "}\n\n"; const char* gFS_Fast_SingleGradient[2] = { "\nvoid main(void) {\n" - " gl_FragColor = " FS_MAIN_DITHER " + texture2D(gradientSampler, linear);\n" + " gl_FragColor = %s + texture2D(gradientSampler, linear);\n" "}\n\n", "\nvoid main(void) {\n" - " gl_FragColor = " FS_MAIN_DITHER " + mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n" - "}\n\n" + " gl_FragColor = %s + mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n" + "}\n\n", }; const char* gFS_Fast_SingleModulateGradient[2] = { "\nvoid main(void) {\n" - " gl_FragColor = " FS_MAIN_DITHER " + color.a * texture2D(gradientSampler, linear);\n" + " gl_FragColor = %s + color.a * texture2D(gradientSampler, linear);\n" "}\n\n", "\nvoid main(void) {\n" - " gl_FragColor = " FS_MAIN_DITHER " + color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n" + " gl_FragColor = %s + color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n" "}\n\n" }; @@ -410,7 +399,7 @@ const char* gBlendOps[18] = { // Constructors/destructors /////////////////////////////////////////////////////////////////////////////// -ProgramCache::ProgramCache() { +ProgramCache::ProgramCache(): mHasES3(Extensions::getInstance().getMajorGlVersion() >= 3) { } ProgramCache::~ProgramCache() { @@ -484,7 +473,7 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description shader.append(gVS_Header_Uniforms_TextureTransform); } if (description.hasGradient) { - shader.append(gVS_Header_Uniforms_HasGradient[description.gradientType]); + shader.append(gVS_Header_Uniforms_HasGradient); } if (description.hasBitmap) { shader.append(gVS_Header_Uniforms_HasBitmap); @@ -601,7 +590,8 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti shader.append(gFS_Uniforms_ExternalTextureSampler); } if (description.hasGradient) { - shader.append(gFS_Uniforms_GradientSampler[gradientIndex(description)]); + shader.appendFormat(gFS_Uniforms_GradientSampler[description.isSimpleGradient], + gFS_Uniforms_Dither); } if (description.hasBitmap && description.isPoint) { shader.append(gFS_Header_Uniforms_PointHasBitmap); @@ -652,9 +642,11 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti fast = true; } else if (singleGradient) { if (!description.modulate) { - shader.append(gFS_Fast_SingleGradient[description.isSimpleGradient]); + shader.appendFormat(gFS_Fast_SingleGradient[description.isSimpleGradient], + gFS_Main_Dither[mHasES3]); } else { - shader.append(gFS_Fast_SingleModulateGradient[description.isSimpleGradient]); + shader.appendFormat(gFS_Fast_SingleModulateGradient[description.isSimpleGradient], + gFS_Main_Dither[mHasES3]); } fast = true; } @@ -708,7 +700,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti } if (description.hasGradient) { shader.append(gFS_Main_FetchGradient[gradientIndex(description)]); - shader.append(gFS_Main_AddDitherToGradient); + shader.appendFormat(gFS_Main_AddDitherToGradient, gFS_Main_Dither[mHasES3]); } if (description.hasBitmap) { if (description.isPoint) { diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h index 1ca148d..38f6f99 100644 --- a/libs/hwui/ProgramCache.h +++ b/libs/hwui/ProgramCache.h @@ -57,6 +57,8 @@ private: void printLongString(const String8& shader) const; KeyedVector<programid, Program*> mCache; + + const bool mHasES3; }; // class ProgramCache }; // namespace uirenderer diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index e4b4f3c..6eea00c 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 f1f35bd..6976eaa 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 1096642..6c5267d 100644 --- a/libs/hwui/font/CacheTexture.cpp +++ b/libs/hwui/font/CacheTexture.cpp @@ -15,10 +15,11 @@ */ #include <SkGlyph.h> -#include <utils/Log.h> -#include "Debug.h" #include "CacheTexture.h" +#include "../Debug.h" +#include "../Extensions.h" +#include "../PixelBuffer.h" namespace android { namespace uirenderer { @@ -112,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() { @@ -144,7 +150,7 @@ void CacheTexture::releaseMesh() { void CacheTexture::releaseTexture() { if (mTexture) { - delete[] mTexture; + delete mTexture; mTexture = NULL; } if (mTextureId) { @@ -155,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]; @@ -163,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) { @@ -184,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 5742941..ddcc836 100644 --- a/libs/hwui/font/CacheTexture.h +++ b/libs/hwui/font/CacheTexture.h @@ -17,7 +17,7 @@ #ifndef ANDROID_HWUI_CACHE_TEXTURE_H #define ANDROID_HWUI_CACHE_TEXTURE_H -#include <GLES2/gl2.h> +#include <GLES3/gl3.h> #include <SkScalerContext.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 02c1aa1..011cfc1 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); } } diff --git a/libs/hwui/utils/TinyHashMap.h b/libs/hwui/utils/TinyHashMap.h new file mode 100644 index 0000000..8855140 --- /dev/null +++ b/libs/hwui/utils/TinyHashMap.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_TINYHASHMAP_H +#define ANDROID_HWUI_TINYHASHMAP_H + +#include <utils/BasicHashtable.h> + +namespace android { +namespace uirenderer { + +/** + * A very simple hash map that doesn't allow duplicate keys, overwriting the older entry. + * + * Currently, expects simple keys that are handled by hash_t() + */ +template <typename TKey, typename TValue> +class TinyHashMap { +public: + typedef key_value_pair_t<TKey, TValue> TEntry; + + /** + * Puts an entry in the hash, removing any existing entry with the same key + */ + void put(TKey key, TValue value) { + hash_t hash = hash_t(key); + + ssize_t index = mTable.find(-1, hash, key); + if (index != -1) { + mTable.removeAt(index); + } + + TEntry initEntry(key, value); + mTable.add(hash, initEntry); + } + + /** + * Return true if key is in the map, in which case stores the value in the output ref + */ + bool get(TKey key, TValue& outValue) { + hash_t hash = hash_t(key); + ssize_t index = mTable.find(-1, hash, key); + if (index == -1) { + return false; + } + outValue = mTable.entryAt(index).value; + return true; + } + + void clear() { mTable.clear(); } + +private: + BasicHashtable<TKey, TEntry> mTable; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_TINYHASHMAP_H |