diff options
author | Chris Craik <ccraik@google.com> | 2013-03-04 10:19:31 -0800 |
---|---|---|
committer | Chris Craik <ccraik@google.com> | 2013-04-15 13:53:02 -0700 |
commit | 527a3aace1dd72432c2e0472a570e030ad04bf16 (patch) | |
tree | 24f8cca71f0377a88b35fbe060a3247040b3de9f /libs/hwui | |
parent | 8d4c23b9c32f8c0328ebca538bb801716fe4478a (diff) | |
download | frameworks_base-527a3aace1dd72432c2e0472a570e030ad04bf16.zip frameworks_base-527a3aace1dd72432c2e0472a570e030ad04bf16.tar.gz frameworks_base-527a3aace1dd72432c2e0472a570e030ad04bf16.tar.bz2 |
Draw Operation merging
Merge simple bitmap draw operations and text operations to avoid
issuing individual gl draws for each operation. Merging other ops to
be done eventually.
The methods are different - the bitmap merging generates a single
mesh for reused, unclipped images (esp. repeated images in a listview)
The text approach queries just defers the normal font rendering until
the last drawText in the sequence that can share the same shader.
Patches are sorted and merged, but don't yet have a multiDraw
implementation. For now, the pretending-to-merge gives better sorting
behavior by keeping similar patches together.
Change-Id: Ic300cdab0a53814cf7b09c58bf54b1bf0f58ccd6
Diffstat (limited to 'libs/hwui')
-rw-r--r-- | libs/hwui/Debug.h | 3 | ||||
-rw-r--r-- | libs/hwui/DeferredDisplayList.cpp | 259 | ||||
-rw-r--r-- | libs/hwui/DeferredDisplayList.h | 28 | ||||
-rw-r--r-- | libs/hwui/DisplayList.cpp | 2 | ||||
-rw-r--r-- | libs/hwui/DisplayListOp.h | 249 | ||||
-rw-r--r-- | libs/hwui/DisplayListRenderer.cpp | 13 | ||||
-rw-r--r-- | libs/hwui/DisplayListRenderer.h | 5 | ||||
-rw-r--r-- | libs/hwui/FontRenderer.cpp | 14 | ||||
-rw-r--r-- | libs/hwui/FontRenderer.h | 6 | ||||
-rw-r--r-- | libs/hwui/OpenGLRenderer.cpp | 93 | ||||
-rw-r--r-- | libs/hwui/OpenGLRenderer.h | 59 | ||||
-rw-r--r-- | libs/hwui/utils/TinyHashMap.h | 72 |
12 files changed, 627 insertions, 176 deletions
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 d5007e1..f0084f2 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -23,6 +23,7 @@ #include "Caches.h" #include "Debug.h" +#include "DeferredDisplayList.h" #include "DisplayListOp.h" #include "OpenGLRenderer.h" @@ -38,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 @@ -54,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++) { @@ -71,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(); @@ -84,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, 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; } +}; - virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) { +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); @@ -124,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); @@ -155,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() { @@ -174,6 +293,7 @@ void DeferredDisplayList::clear() { } mBatches.clear(); mSaveStack.clear(); + mEarliestBatchIndex = 0; } ///////////////////////////////////////////////////////////////////////////////// @@ -282,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); @@ -311,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", @@ -329,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); } @@ -363,16 +521,14 @@ 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; } @@ -400,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 653f315..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, @@ -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/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 6894ef9..543cfa2 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -57,7 +57,6 @@ FontRenderer::FontRenderer() : mGammaTable = NULL; mInitialized = false; - mMaxNumberOfQuads = 1024; mCurrentCacheTexture = NULL; @@ -293,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); @@ -320,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; @@ -594,7 +593,7 @@ void FontRenderer::endPrecaching() { 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; @@ -602,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; } diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index 348b7e3..307a1d9 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -66,7 +66,8 @@ public: // 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); @@ -101,6 +102,8 @@ public: private: friend class Font; + static const uint32_t gMaxNumberOfQuads = 2048; + const uint8_t* mGammaTable; void allocateTextureMemory(CacheTexture* cacheTexture); @@ -154,7 +157,6 @@ private: bool mUploadTexture; - uint32_t mMaxNumberOfQuads; uint32_t mIndexBufferID; Functor* mFunctor; diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index dcd1eb8..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)); @@ -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 /////////////////////////////////////////////////////////////////////////////// @@ -1963,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(); @@ -2796,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; } @@ -2815,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; @@ -2868,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); } @@ -3093,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) { @@ -3365,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); @@ -3377,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/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 |