Merged op dispatch in OpReorderer
bug:22480459
Also switches std::functions to function pointers on OpReorderer, and
switches AssetAtlas' entry getter methods to using pixelRef pointers,
so it's clear they're the keys.
Change-Id: I3040ce5ff4e178a8364e0fd7ab0876ada7d4de05
diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp
index 7e09699..41411a9 100644
--- a/libs/hwui/AssetAtlas.cpp
+++ b/libs/hwui/AssetAtlas.cpp
@@ -79,13 +79,13 @@
// Entries
///////////////////////////////////////////////////////////////////////////////
-AssetAtlas::Entry* AssetAtlas::getEntry(const SkBitmap* bitmap) const {
- ssize_t index = mEntries.indexOfKey(bitmap->pixelRef());
+AssetAtlas::Entry* AssetAtlas::getEntry(const SkPixelRef* pixelRef) const {
+ ssize_t index = mEntries.indexOfKey(pixelRef);
return index >= 0 ? mEntries.valueAt(index) : nullptr;
}
-Texture* AssetAtlas::getEntryTexture(const SkBitmap* bitmap) const {
- ssize_t index = mEntries.indexOfKey(bitmap->pixelRef());
+Texture* AssetAtlas::getEntryTexture(const SkPixelRef* pixelRef) const {
+ ssize_t index = mEntries.indexOfKey(pixelRef);
return index >= 0 ? mEntries.valueAt(index)->texture : nullptr;
}
diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h
index f1cd0b4..a037725 100644
--- a/libs/hwui/AssetAtlas.h
+++ b/libs/hwui/AssetAtlas.h
@@ -148,15 +148,15 @@
/**
* Returns the entry in the atlas associated with the specified
- * bitmap. If the bitmap is not in the atlas, return NULL.
+ * pixelRef. If the pixelRef is not in the atlas, return NULL.
*/
- Entry* getEntry(const SkBitmap* bitmap) const;
+ Entry* getEntry(const SkPixelRef* pixelRef) const;
/**
* Returns the texture for the atlas entry associated with the
- * specified bitmap. If the bitmap is not in the atlas, return NULL.
+ * specified pixelRef. If the pixelRef is not in the atlas, return NULL.
*/
- Texture* getEntryTexture(const SkBitmap* bitmap) const;
+ Texture* getEntryTexture(const SkPixelRef* pixelRef) const;
private:
void createEntries(Caches& caches, int64_t* map, int count);
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index b56b1e4..fde12dd 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -31,20 +31,182 @@
namespace android {
namespace uirenderer {
+static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds, const Rect& texCoord) {
+ vertices[0] = { bounds.left, bounds.top, texCoord.left, texCoord.top };
+ vertices[1] = { bounds.right, bounds.top, texCoord.right, texCoord.top };
+ vertices[2] = { bounds.left, bounds.bottom, texCoord.left, texCoord.bottom };
+ vertices[3] = { bounds.right, bounds.bottom, texCoord.right, texCoord.bottom };
+}
+
+void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer,
+ const MergedBakedOpList& opList) {
+
+ const BakedOpState& firstState = *(opList.states[0]);
+ const SkBitmap* bitmap = (static_cast<const BitmapOp*>(opList.states[0]->op))->bitmap;
+
+ AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(bitmap->pixelRef());
+ Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(bitmap);
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ TextureVertex vertices[opList.count * 4];
+ Rect texCoords(0, 0, 1, 1);
+ if (entry) {
+ entry->uvMapper.map(texCoords);
+ }
+ // init to non-empty, so we can safely expandtoCoverRect
+ Rect totalBounds = firstState.computedState.clippedBounds;
+ for (size_t i = 0; i < opList.count; i++) {
+ const BakedOpState& state = *(opList.states[i]);
+ TextureVertex* rectVerts = &vertices[i * 4];
+ Rect opBounds = state.computedState.clippedBounds;
+ if (CC_LIKELY(state.computedState.transform.isPureTranslate())) {
+ // pure translate, so snap (same behavior as onBitmapOp)
+ opBounds.snapToPixelBoundaries();
+ }
+ storeTexturedRect(rectVerts, opBounds, texCoords);
+ renderer.dirtyRenderTarget(opBounds);
+
+ totalBounds.expandToCover(opBounds);
+ }
+
+ const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType)
+ ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(firstState.roundRectClipState)
+ .setMeshTexturedIndexedQuads(vertices, opList.count * 6)
+ .setFillTexturePaint(*texture, textureFillFlags, firstState.op->paint, firstState.alpha)
+ .setTransform(Matrix4::identity(), TransformFlags::None)
+ .setModelViewOffsetRect(0, 0, totalBounds) // don't snap here, we snap per-quad above
+ .build();
+ renderer.renderGlop(nullptr, opList.clipSideFlags ? &opList.clip : nullptr, glop);
+}
+
+static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
+ const TextOp& op, const BakedOpState& state) {
+ renderer.caches().textureState().activateTexture(0);
+
+ PaintUtils::TextShadow textShadow;
+ if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
+ LOG_ALWAYS_FATAL("failed to query shadow attributes");
+ }
+
+ renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
+ ShadowTexture* texture = renderer.caches().dropShadowCache.get(
+ op.paint, (const char*) op.glyphs,
+ op.glyphCount, textShadow.radius, op.positions);
+ // If the drop shadow exceeds the max texture size or couldn't be
+ // allocated, skip drawing
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ const float sx = op.x - texture->left + textShadow.dx;
+ const float sy = op.y - texture->top + textShadow.dy;
+
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUnitQuad(nullptr)
+ .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+enum class TextRenderType {
+ Defer,
+ Flush
+};
+
+static void renderTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state,
+ const Rect* renderClip, TextRenderType renderType) {
+ FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
+
+ if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ renderTextShadow(renderer, fontRenderer, op, state);
+ }
+
+ float x = op.x;
+ float y = op.y;
+ const Matrix4& transform = state.computedState.transform;
+ const bool pureTranslate = transform.isPureTranslate();
+ if (CC_LIKELY(pureTranslate)) {
+ x = floorf(x + transform.getTranslateX() + 0.5f);
+ y = floorf(y + transform.getTranslateY() + 0.5f);
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(false);
+ } else if (CC_UNLIKELY(transform.isPerspective())) {
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(true);
+ } else {
+ // We only pass a partial transform to the font renderer. That partial
+ // matrix defines how glyphs are rasterized. Typically we want glyphs
+ // to be rasterized at their final size on screen, which means the partial
+ // matrix needs to take the scale factor into account.
+ // When a partial matrix is used to transform glyphs during rasterization,
+ // the mesh is generated with the inverse transform (in the case of scale,
+ // the mesh is generated at 1.0 / scale for instance.) This allows us to
+ // apply the full transform matrix at draw time in the vertex shader.
+ // Applying the full matrix in the shader is the easiest way to handle
+ // rotation and perspective and allows us to always generated quads in the
+ // font renderer which greatly simplifies the code, clipping in particular.
+ float sx, sy;
+ transform.decomposeScale(sx, sy);
+ fontRenderer.setFont(op.paint, SkMatrix::MakeScale(
+ roundf(std::max(1.0f, sx)),
+ roundf(std::max(1.0f, sy))));
+ fontRenderer.setTextureFiltering(true);
+ }
+ Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
+
+ int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
+ SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
+ TextDrawFunctor functor(&renderer, &state, renderClip,
+ x, y, pureTranslate, alpha, mode, op.paint);
+
+ bool forceFinish = (renderType == TextRenderType::Flush);
+ bool mustDirtyRenderTarget = renderer.offscreenRenderTarget();
+ const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect : nullptr;
+ fontRenderer.renderPosText(op.paint, localOpClip,
+ (const char*) op.glyphs, op.glyphCount, x, y,
+ op.positions, mustDirtyRenderTarget ? &layerBounds : nullptr, &functor, forceFinish);
+
+ if (mustDirtyRenderTarget) {
+ if (!pureTranslate) {
+ transform.mapRect(layerBounds);
+ }
+ renderer.dirtyRenderTarget(layerBounds);
+ }
+}
+
+void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer,
+ const MergedBakedOpList& opList) {
+ const Rect* clip = opList.clipSideFlags ? &opList.clip : nullptr;
+ for (size_t i = 0; i < opList.count; i++) {
+ const BakedOpState& state = *(opList.states[i]);
+ const TextOp& op = *(static_cast<const TextOp*>(state.op));
+ TextRenderType renderType = (i + 1 == opList.count)
+ ? TextRenderType::Flush : TextRenderType::Defer;
+ renderTextOp(renderer, op, state, clip, renderType);
+ }
+}
+
void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, const BakedOpState&) {
LOG_ALWAYS_FATAL("unsupported operation");
}
-void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
+void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer&, const BeginLayerOp&, const BakedOpState&) {
LOG_ALWAYS_FATAL("unsupported operation");
}
-void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
+void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer&, const EndLayerOp&, const BakedOpState&) {
LOG_ALWAYS_FATAL("unsupported operation");
}
void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) {
- renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
Texture* texture = renderer.getTexture(op.bitmap);
if (!texture) return;
const AutoTexture autoCleanup(texture);
@@ -153,89 +315,9 @@
renderer.renderGlop(state, glop);
}
-static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
- const TextOp& op, const BakedOpState& state) {
- renderer.caches().textureState().activateTexture(0);
-
- PaintUtils::TextShadow textShadow;
- if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
- LOG_ALWAYS_FATAL("failed to query shadow attributes");
- }
-
- renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
- ShadowTexture* texture = renderer.caches().dropShadowCache.get(
- op.paint, (const char*) op.glyphs,
- op.glyphCount, textShadow.radius, op.positions);
- // If the drop shadow exceeds the max texture size or couldn't be
- // allocated, skip drawing
- if (!texture) return;
- const AutoTexture autoCleanup(texture);
-
- const float sx = op.x - texture->left + textShadow.dx;
- const float sy = op.y - texture->top + textShadow.dy;
-
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshTexturedUnitQuad(nullptr)
- .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha)
- .setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
- .build();
- renderer.renderGlop(state, glop);
-}
-
void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
- FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
-
- if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
- fontRenderer.setFont(op.paint, SkMatrix::I());
- renderTextShadow(renderer, fontRenderer, op, state);
- }
-
- float x = op.x;
- float y = op.y;
- const Matrix4& transform = state.computedState.transform;
- const bool pureTranslate = transform.isPureTranslate();
- if (CC_LIKELY(pureTranslate)) {
- x = floorf(x + transform.getTranslateX() + 0.5f);
- y = floorf(y + transform.getTranslateY() + 0.5f);
- fontRenderer.setFont(op.paint, SkMatrix::I());
- fontRenderer.setTextureFiltering(false);
- } else if (CC_UNLIKELY(transform.isPerspective())) {
- fontRenderer.setFont(op.paint, SkMatrix::I());
- fontRenderer.setTextureFiltering(true);
- } else {
- // We only pass a partial transform to the font renderer. That partial
- // matrix defines how glyphs are rasterized. Typically we want glyphs
- // to be rasterized at their final size on screen, which means the partial
- // matrix needs to take the scale factor into account.
- // When a partial matrix is used to transform glyphs during rasterization,
- // the mesh is generated with the inverse transform (in the case of scale,
- // the mesh is generated at 1.0 / scale for instance.) This allows us to
- // apply the full transform matrix at draw time in the vertex shader.
- // Applying the full matrix in the shader is the easiest way to handle
- // rotation and perspective and allows us to always generated quads in the
- // font renderer which greatly simplifies the code, clipping in particular.
- float sx, sy;
- transform.decomposeScale(sx, sy);
- fontRenderer.setFont(op.paint, SkMatrix::MakeScale(
- roundf(std::max(1.0f, sx)),
- roundf(std::max(1.0f, sy))));
- fontRenderer.setTextureFiltering(true);
- }
-
- // TODO: Implement better clipping for scaled/rotated text
- const Rect* clip = !pureTranslate ? nullptr : &state.computedState.clipRect;
- Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
-
- int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
- SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
- TextDrawFunctor functor(&renderer, &state, x, y, pureTranslate, alpha, mode, op.paint);
-
- bool hasActiveLayer = false; // TODO
- fontRenderer.renderPosText(op.paint, clip, (const char*) op.glyphs, op.glyphCount, x, y,
- op.positions, hasActiveLayer ? &layerBounds : nullptr, &functor, true); // TODO: merging
+ const Rect* clip = state.computedState.clipSideFlags ? &state.computedState.clipRect : nullptr;
+ renderTextOp(renderer, op, state, clip, TextRenderType::Flush);
}
void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
diff --git a/libs/hwui/BakedOpDispatcher.h b/libs/hwui/BakedOpDispatcher.h
index caf14bf..0e763d9 100644
--- a/libs/hwui/BakedOpDispatcher.h
+++ b/libs/hwui/BakedOpDispatcher.h
@@ -26,16 +26,21 @@
/**
* Provides all "onBitmapOp(...)" style static methods for every op type, which convert the
* RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer.
- *
- * This dispatcher is separate from the renderer so that the dispatcher / renderer interaction is
- * minimal through public BakedOpRenderer APIs.
*/
class BakedOpDispatcher {
public:
+ // Declares all "onMergedBitmapOps(...)" style methods for mergeable op types
+#define X(Type) \
+ static void onMerged##Type##s(BakedOpRenderer& renderer, const MergedBakedOpList& opList);
+ MAP_MERGED_OPS(X)
+#undef X
+
// Declares all "onBitmapOp(...)" style methods for every op type
-#define DISPATCH_METHOD(Type) \
+#define X(Type) \
static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state);
- MAP_OPS(DISPATCH_METHOD);
+ MAP_OPS(X)
+#undef X
+
};
}; // namespace uirenderer
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 6cdc320..93a9406 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -121,30 +121,35 @@
}
Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
- Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
+ Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef());
if (!texture) {
return mCaches.textureCache.get(bitmap);
}
return texture;
}
-void BakedOpRenderer::renderGlop(const BakedOpState& state, const Glop& glop) {
- bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
- mRenderState.scissor().setEnabled(useScissor);
- if (useScissor) {
- const Rect& clip = state.computedState.clipRect;
- mRenderState.scissor().set(clip.left, mRenderTarget.viewportHeight - clip.bottom,
- clip.getWidth(), clip.getHeight());
+void BakedOpRenderer::renderGlop(const Rect* dirtyBounds, const Rect* clip, const Glop& glop) {
+ mRenderState.scissor().setEnabled(clip != nullptr);
+ if (clip) {
+ mRenderState.scissor().set(clip->left, mRenderTarget.viewportHeight - clip->bottom,
+ clip->getWidth(), clip->getHeight());
}
- if (mRenderTarget.offscreenBuffer) { // TODO: not with multi-draw
+ if (dirtyBounds && mRenderTarget.offscreenBuffer) {
// register layer damage to draw-back region
- const Rect& uiDirty = state.computedState.clippedBounds;
- android::Rect dirty(uiDirty.left, uiDirty.top, uiDirty.right, uiDirty.bottom);
+ android::Rect dirty(dirtyBounds->left, dirtyBounds->top,
+ dirtyBounds->right, dirtyBounds->bottom);
mRenderTarget.offscreenBuffer->region.orSelf(dirty);
}
mRenderState.render(glop, mRenderTarget.orthoMatrix);
if (!mRenderTarget.frameBufferId) mHasDrawn = true;
}
+void BakedOpRenderer::dirtyRenderTarget(const Rect& uiDirty) {
+ if (mRenderTarget.offscreenBuffer) {
+ android::Rect dirty(uiDirty.left, uiDirty.top, uiDirty.right, uiDirty.bottom);
+ mRenderTarget.offscreenBuffer->region.orSelf(dirty);
+ }
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index 62d1838..d7600db 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -67,7 +67,16 @@
Texture* getTexture(const SkBitmap* bitmap);
const LightInfo& getLightInfo() { return mLightInfo; }
- void renderGlop(const BakedOpState& state, const Glop& glop);
+ void renderGlop(const BakedOpState& state, const Glop& glop) {
+ bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
+ renderGlop(&state.computedState.clippedBounds,
+ useScissor ? &state.computedState.clipRect : nullptr,
+ glop);
+ }
+
+ void renderGlop(const Rect* dirtyBounds, const Rect* clip, const Glop& glop);
+ bool offscreenRenderTarget() { return mRenderTarget.offscreenBuffer != nullptr; }
+ void dirtyRenderTarget(const Rect& dirtyRect);
bool didDraw() { return mHasDrawn; }
private:
void setViewport(uint32_t width, uint32_t height);
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index 9a40c3b..983c27b 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -38,6 +38,16 @@
}
/**
+ * Holds a list of BakedOpStates of ops that can be drawn together
+ */
+struct MergedBakedOpList {
+ const BakedOpState*const* states;
+ size_t count;
+ int clipSideFlags;
+ Rect clip;
+};
+
+/**
* Holds the resolved clip, transform, and bounds of a recordedOp, when replayed with a snapshot
*/
class ResolvedRenderState {
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp
index a9d1e42..fd6f0b5 100644
--- a/libs/hwui/ClipArea.cpp
+++ b/libs/hwui/ClipArea.cpp
@@ -26,7 +26,7 @@
static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) {
Vertex v = {x, y};
transform.mapPoint(v.x, v.y);
- transformedBounds.expandToCoverVertex(v.x, v.y);
+ transformedBounds.expandToCover(v.x, v.y);
}
Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) {
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index e7cc464..92217edc 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -612,7 +612,7 @@
AssetAtlas::Entry* getAtlasEntry(OpenGLRenderer& renderer) {
if (!mEntryValid) {
mEntryValid = true;
- mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap);
+ mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap->pixelRef());
}
return mEntry;
}
@@ -777,7 +777,7 @@
AssetAtlas::Entry* getAtlasEntry(OpenGLRenderer& renderer) {
if (!mEntryValid) {
mEntryValid = true;
- mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap);
+ mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap->pixelRef());
}
return mEntry;
}
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 47654f4..9c8649f 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -75,7 +75,8 @@
.setTransform(bakedState->computedState.transform, transformFlags)
.setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
.build();
- renderer->renderGlop(*bakedState, glop);
+ // Note: don't pass dirty bounds here, so user must manage passing dirty bounds to renderer
+ renderer->renderGlop(nullptr, clip, glop);
#else
GlopBuilder(renderer->mRenderState, renderer->mCaches, &glop)
.setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState)
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 87cfe7f..ff4dc4a 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -57,6 +57,7 @@
#if HWUI_NEW_OPS
BakedOpRenderer* renderer,
const BakedOpState* bakedState,
+ const Rect* clip,
#else
OpenGLRenderer* renderer,
#endif
@@ -65,6 +66,7 @@
: renderer(renderer)
#if HWUI_NEW_OPS
, bakedState(bakedState)
+ , clip(clip)
#endif
, x(x)
, y(y)
@@ -79,6 +81,7 @@
#if HWUI_NEW_OPS
BakedOpRenderer* renderer;
const BakedOpState* bakedState;
+ const Rect* clip;
#else
OpenGLRenderer* renderer;
#endif
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 6270dcb..b647b90 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -53,7 +53,7 @@
GlopBuilder& setMeshTexturedUvQuad(const UvMapper* uvMapper, const Rect uvs);
GlopBuilder& setMeshVertexBuffer(const VertexBuffer& vertexBuffer, bool shadowInterp);
GlopBuilder& setMeshIndexedQuads(Vertex* vertexData, int quadCount);
- GlopBuilder& setMeshTexturedMesh(TextureVertex* vertexData, int elementCount); // TODO: use indexed quads
+ GlopBuilder& setMeshTexturedMesh(TextureVertex* vertexData, int elementCount); // TODO: delete
GlopBuilder& setMeshColoredTexturedMesh(ColorTextureVertex* vertexData, int elementCount); // TODO: use indexed quads
GlopBuilder& setMeshTexturedIndexedQuads(TextureVertex* vertexData, int elementCount); // TODO: take quadCount
GlopBuilder& setMeshPatchQuads(const Patch& patch);
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 9cbd9c2d..9460361 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -202,6 +202,9 @@
if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom;
}
+ bool getClipSideFlags() const { return mClipSideFlags; }
+ const Rect& getClipRect() const { return mClipRect; }
+
private:
int mClipSideFlags = 0;
Rect mClipRect;
@@ -291,12 +294,31 @@
}
}
-void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const {
+void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg,
+ BakedOpReceiver* unmergedReceivers, MergedOpReceiver* mergedReceivers) const {
ATRACE_NAME("flush drawing commands");
for (const BatchBase* batch : mBatches) {
- // TODO: different behavior based on batch->isMerging()
- for (const BakedOpState* op : batch->getOps()) {
- receivers[op->op->opId](arg, *op->op, *op);
+ size_t size = batch->getOps().size();
+ if (size > 1 && batch->isMerging()) {
+ int opId = batch->getOps()[0]->op->opId;
+ const MergingOpBatch* mergingBatch = static_cast<const MergingOpBatch*>(batch);
+ MergedBakedOpList data = {
+ batch->getOps().data(),
+ size,
+ mergingBatch->getClipSideFlags(),
+ mergingBatch->getClipRect()
+ };
+ if (data.clipSideFlags) {
+ // if right or bottom sides aren't used to clip, init them to viewport bounds
+ // in the clip rect, so it can be used to scissor
+ if (!(data.clipSideFlags & OpClipSideFlags::Right)) data.clip.right = width;
+ if (!(data.clipSideFlags & OpClipSideFlags::Bottom)) data.clip.bottom = height;
+ }
+ mergedReceivers[opId](arg, data);
+ } else {
+ for (const BakedOpState* op : batch->getOps()) {
+ unmergedReceivers[op->op->opId](arg, *op);
+ }
}
}
}
@@ -639,7 +661,8 @@
#define OP_RECEIVER(Type) \
[](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
void OpReorderer::deferNodeOps(const RenderNode& renderNode) {
- static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
+ typedef void (*OpDispatcher) (OpReorderer& reorderer, const RecordedOp& op);
+ static OpDispatcher receivers[] = {
MAP_OPS(OP_RECEIVER)
};
@@ -692,42 +715,57 @@
}
void OpReorderer::onBitmapOp(const BitmapOp& op) {
- BakedOpState* bakedStateOp = tryBakeOpState(op);
- if (!bakedStateOp) return; // quick rejected
+ BakedOpState* bakedState = tryBakeOpState(op);
+ if (!bakedState) return; // quick rejected
- mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
- // TODO: AssetAtlas
- currentLayer().deferMergeableOp(mAllocator, bakedStateOp, OpBatchType::Bitmap, mergeId);
+ // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation
+ // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in
+ // MergingDrawBatch::canMergeWith()
+ if (bakedState->computedState.transform.isSimple()
+ && bakedState->computedState.transform.positiveScale()
+ && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
+ && op.bitmap->colorType() != kAlpha_8_SkColorType) {
+ mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
+ // TODO: AssetAtlas in mergeId
+ currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId);
+ } else {
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
+ }
}
void OpReorderer::onLinesOp(const LinesOp& op) {
- BakedOpState* bakedStateOp = tryBakeOpState(op);
- if (!bakedStateOp) return; // quick rejected
- currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
-
+ BakedOpState* bakedState = tryBakeOpState(op);
+ if (!bakedState) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, tessellatedBatchId(*op.paint));
}
void OpReorderer::onRectOp(const RectOp& op) {
- BakedOpState* bakedStateOp = tryBakeOpState(op);
- if (!bakedStateOp) return; // quick rejected
- currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, tessellatedBatchId(*op.paint));
+ BakedOpState* bakedState = tryBakeOpState(op);
+ if (!bakedState) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, tessellatedBatchId(*op.paint));
}
void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
- BakedOpState* bakedStateOp = tryBakeOpState(op);
- if (!bakedStateOp) return; // quick rejected
- currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
+ BakedOpState* bakedState = tryBakeOpState(op);
+ if (!bakedState) return; // quick rejected
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
}
void OpReorderer::onTextOp(const TextOp& op) {
- BakedOpState* bakedStateOp = tryBakeOpState(op);
- if (!bakedStateOp) return; // quick rejected
+ BakedOpState* bakedState = tryBakeOpState(op);
+ if (!bakedState) return; // quick rejected
// TODO: better handling of shader (since we won't care about color then)
batchid_t batchId = op.paint->getColor() == SK_ColorBLACK
? OpBatchType::Text : OpBatchType::ColorText;
- mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
- currentLayer().deferMergeableOp(mAllocator, bakedStateOp, batchId, mergeId);
+
+ if (bakedState->computedState.transform.isPureTranslate()
+ && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode) {
+ mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
+ currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId);
+ } else {
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
+ }
}
void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 00df8b0..fc77c61 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -58,7 +58,8 @@
}
class OpReorderer : public CanvasStateClient {
- typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpDispatcher;
+ typedef void (*BakedOpReceiver)(void*, const BakedOpState&);
+ typedef void (*MergedOpReceiver)(void*, const MergedBakedOpList& opList);
/**
* Stores the deferred render operations and state used to compute ordering
@@ -87,7 +88,7 @@
void deferMergeableOp(LinearAllocator& allocator,
BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
- void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const;
+ void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers, MergedOpReceiver*) const;
bool empty() const {
return mBatches.empty();
@@ -132,19 +133,44 @@
* It constructs a lookup array of lambdas, which allows a recorded BakeOpState to use
* state->op->opId to lookup a receiver that will be called when the op is replayed.
*
- * For example a BitmapOp would resolve, via the lambda lookup, to calling:
- *
- * StaticDispatcher::onBitmapOp(Renderer& renderer, const BitmapOp& op, const BakedOpState& state);
*/
-#define BAKED_OP_RECEIVER(Type) \
- [](void* internalRenderer, const RecordedOp& op, const BakedOpState& state) { \
- StaticDispatcher::on##Type(*(static_cast<Renderer*>(internalRenderer)), static_cast<const Type&>(op), state); \
- },
template <typename StaticDispatcher, typename Renderer>
void replayBakedOps(Renderer& renderer) {
- static BakedOpDispatcher receivers[] = {
- MAP_OPS(BAKED_OP_RECEIVER)
+ /**
+ * defines a LUT of lambdas which allow a recorded BakedOpState to use state->op->opId to
+ * dispatch the op via a method on a static dispatcher when the op is replayed.
+ *
+ * For example a BitmapOp would resolve, via the lambda lookup, to calling:
+ *
+ * StaticDispatcher::onBitmapOp(Renderer& renderer, const BitmapOp& op, const BakedOpState& state);
+ */
+ #define X(Type) \
+ [](void* renderer, const BakedOpState& state) { \
+ StaticDispatcher::on##Type(*(static_cast<Renderer*>(renderer)), static_cast<const Type&>(*(state.op)), state); \
+ },
+ static BakedOpReceiver unmergedReceivers[] = {
+ MAP_OPS(X)
};
+ #undef X
+
+ /**
+ * defines a LUT of lambdas which allow merged arrays of BakedOpState* to be passed to a
+ * static dispatcher when the group of merged ops is replayed. Unmergeable ops trigger
+ * a LOG_ALWAYS_FATAL().
+ */
+ #define X(Type) \
+ [](void* renderer, const MergedBakedOpList& opList) { \
+ LOG_ALWAYS_FATAL("op type %d does not support merging", opList.states[0]->op->opId); \
+ },
+ #define Y(Type) \
+ [](void* renderer, const MergedBakedOpList& opList) { \
+ StaticDispatcher::onMerged##Type##s(*(static_cast<Renderer*>(renderer)), opList); \
+ },
+ static MergedOpReceiver mergedReceivers[] = {
+ MAP_OPS_BASED_ON_MERGEABILITY(X, Y)
+ };
+ #undef X
+ #undef Y
// Relay through layers in reverse order, since layers
// later in the list will be drawn by earlier ones
@@ -153,18 +179,18 @@
if (layer.renderNode) {
// cached HW layer - can't skip layer if empty
renderer.startRepaintLayer(layer.offscreenBuffer, layer.repaintRect);
- layer.replayBakedOpsImpl((void*)&renderer, receivers);
+ layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
renderer.endLayer();
} else if (!layer.empty()) { // save layer - skip entire layer if empty
layer.offscreenBuffer = renderer.startTemporaryLayer(layer.width, layer.height);
- layer.replayBakedOpsImpl((void*)&renderer, receivers);
+ layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
renderer.endLayer();
}
}
const LayerReorderer& fbo0 = mLayerReorderers[0];
renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
- fbo0.replayBakedOpsImpl((void*)&renderer, receivers);
+ fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
renderer.endFrame();
}
@@ -213,7 +239,7 @@
void deferRenderNodeOp(const RenderNodeOp& op);
- void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
+ void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers);
SkPath* createFrameAllocatedPath() {
mFrameAllocatedPaths.emplace_back(new SkPath);
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index e386b1c..2cb32c4 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1525,7 +1525,7 @@
colors = tempColors.get();
}
- Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
+ Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef());
const UvMapper& mapper(getMapper(texture));
for (int32_t y = 0; y < meshHeight; y++) {
@@ -2146,7 +2146,7 @@
bool status;
#if HWUI_NEW_OPS
LOG_ALWAYS_FATAL("unsupported");
- TextDrawFunctor functor(nullptr, nullptr, x, y, pureTranslate, alpha, mode, paint);
+ TextDrawFunctor functor(nullptr, nullptr, nullptr, x, y, pureTranslate, alpha, mode, paint);
#else
TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
#endif
@@ -2190,7 +2190,7 @@
SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint);
#if HWUI_NEW_OPS
LOG_ALWAYS_FATAL("unsupported");
- TextDrawFunctor functor(nullptr, nullptr, 0.0f, 0.0f, false, alpha, mode, paint);
+ TextDrawFunctor functor(nullptr, nullptr, nullptr, 0.0f, 0.0f, false, alpha, mode, paint);
#else
TextDrawFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint);
#endif
@@ -2308,7 +2308,7 @@
///////////////////////////////////////////////////////////////////////////////
Texture* OpenGLRenderer::getTexture(const SkBitmap* bitmap) {
- Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
+ Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef());
if (!texture) {
return mCaches.textureCache.get(bitmap);
}
diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp
index b57b8f0..9246237 100644
--- a/libs/hwui/PathTessellator.cpp
+++ b/libs/hwui/PathTessellator.cpp
@@ -799,7 +799,7 @@
dstBuffer.alloc<TYPE>(numPoints * verticesPerPoint + (numPoints - 1) * 2);
for (int i = 0; i < count; i += 2) {
- bounds.expandToCoverVertex(points[i + 0], points[i + 1]);
+ bounds.expandToCover(points[i + 0], points[i + 1]);
dstBuffer.copyInto<TYPE>(srcBuffer, points[i + 0], points[i + 1]);
}
dstBuffer.createDegenerateSeparators<TYPE>(verticesPerPoint);
@@ -878,8 +878,8 @@
}
// calculate bounds
- bounds.expandToCoverVertex(tempVerticesData[0].x, tempVerticesData[0].y);
- bounds.expandToCoverVertex(tempVerticesData[1].x, tempVerticesData[1].y);
+ bounds.expandToCover(tempVerticesData[0].x, tempVerticesData[0].y);
+ bounds.expandToCover(tempVerticesData[1].x, tempVerticesData[1].y);
}
// since multiple objects tessellated into buffer, separate them with degen tris
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index b4a201e..b966401 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -37,21 +37,34 @@
struct Vertex;
/**
- * The provided macro is executed for each op type in order, with the results separated by commas.
+ * On of the provided macros is executed for each op type in order. The first will be used for ops
+ * that cannot merge, and the second for those that can.
*
* This serves as the authoritative list of ops, used for generating ID enum, and ID based LUTs.
*/
+#define MAP_OPS_BASED_ON_MERGEABILITY(U_OP_FN, M_OP_FN) \
+ M_OP_FN(BitmapOp) \
+ U_OP_FN(LinesOp) \
+ U_OP_FN(RectOp) \
+ U_OP_FN(RenderNodeOp) \
+ U_OP_FN(ShadowOp) \
+ U_OP_FN(SimpleRectsOp) \
+ M_OP_FN(TextOp) \
+ U_OP_FN(BeginLayerOp) \
+ U_OP_FN(EndLayerOp) \
+ U_OP_FN(LayerOp)
+
+/**
+ * The provided macro is executed for each op type in order. This is used in cases where
+ * merge-ability of ops doesn't matter.
+ */
#define MAP_OPS(OP_FN) \
- OP_FN(BitmapOp) \
- OP_FN(LinesOp) \
- OP_FN(RectOp) \
- OP_FN(RenderNodeOp) \
- OP_FN(ShadowOp) \
- OP_FN(SimpleRectsOp) \
- OP_FN(TextOp) \
- OP_FN(BeginLayerOp) \
- OP_FN(EndLayerOp) \
- OP_FN(LayerOp)
+ MAP_OPS_BASED_ON_MERGEABILITY(OP_FN, OP_FN)
+
+#define NULL_OP_FN(Type)
+
+#define MAP_MERGED_OPS(OP_FN) \
+ MAP_OPS_BASED_ON_MERGEABILITY(NULL_OP_FN, OP_FN)
// Generate OpId enum
#define IDENTITY_FN(Type) Type,
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 69c686e..e6020cd 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -248,10 +248,7 @@
Rect unmappedBounds(points[0], points[1], points[0], points[1]);
for (int i = 2; i < floatCount; i += 2) {
- unmappedBounds.left = std::min(unmappedBounds.left, points[i]);
- unmappedBounds.right = std::max(unmappedBounds.right, points[i]);
- unmappedBounds.top = std::min(unmappedBounds.top, points[i + 1]);
- unmappedBounds.bottom = std::max(unmappedBounds.bottom, points[i + 1]);
+ unmappedBounds.expandToCover(points[i], points[i + 1]);
}
// since anything AA stroke with less than 1.0 pixel width is drawn with an alpha-reduced
@@ -413,6 +410,7 @@
glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
positions = refBuffer<float>(positions, glyphCount * 2);
+ // TODO: either must account for text shadow in bounds, or record separate ops for text shadows
addOp(new (alloc()) TextOp(
Rect(boundsLeft, boundsTop, boundsRight, boundsBottom),
*(mState.currentSnapshot()->transform),
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 472aad7..30c925c 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -253,7 +253,18 @@
bottom = ceilf(bottom);
}
- void expandToCoverVertex(float x, float y) {
+ /*
+ * Similar to unionWith, except this makes the assumption that both rects are non-empty
+ * to avoid both emptiness checks.
+ */
+ void expandToCover(const Rect& other) {
+ left = std::min(left, other.left);
+ top = std::min(top, other.top);
+ right = std::max(right, other.right);
+ bottom = std::max(bottom, other.bottom);
+ }
+
+ void expandToCover(float x, float y) {
left = std::min(left, x);
top = std::min(top, y);
right = std::max(right, x);
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index a6c72a3..21901cf 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -138,7 +138,7 @@
// in the cache (and is thus added to the cache)
Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) {
if (CC_LIKELY(mAssetAtlas != nullptr) && atlasUsageType == AtlasUsageType::Use) {
- AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap);
+ AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap->pixelRef());
if (CC_UNLIKELY(entry)) {
return entry->texture;
}
diff --git a/libs/hwui/VertexBuffer.h b/libs/hwui/VertexBuffer.h
index c0373ac..bdb5b7b 100644
--- a/libs/hwui/VertexBuffer.h
+++ b/libs/hwui/VertexBuffer.h
@@ -118,7 +118,7 @@
TYPE* end = current + vertexCount;
mBounds.set(current->x, current->y, current->x, current->y);
for (; current < end; current++) {
- mBounds.expandToCoverVertex(current->x, current->y);
+ mBounds.expandToCover(current->x, current->y);
}
}
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 9c1c0b9d..0af9939 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -103,10 +103,11 @@
return snapshot;
}
- static SkBitmap createSkBitmap(int width, int height) {
+ static SkBitmap createSkBitmap(int width, int height,
+ SkColorType colorType = kN32_SkColorType) {
SkBitmap bitmap;
SkImageInfo info = SkImageInfo::Make(width, height,
- kN32_SkColorType, kPremul_SkAlphaType);
+ colorType, kPremul_SkAlphaType);
bitmap.setInfo(info);
bitmap.allocPixels(info);
return bitmap;
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index 27adb12..6c64a32 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -62,7 +62,9 @@
int cardIndexOffset = scrollPx / (cardSpacing + cardHeight);
int pxOffset = -(scrollPx % (cardSpacing + cardHeight));
- TestCanvas canvas(cardWidth, cardHeight);
+ TestCanvas canvas(
+ listView->stagingProperties().getWidth(),
+ listView->stagingProperties().getHeight());
for (size_t ci = 0; ci < cards.size(); ci++) {
// update card position
auto card = cards[(ci + cardIndexOffset) % cards.size()];
@@ -121,9 +123,11 @@
static SkBitmap filledBox = createBoxBitmap(true);
static SkBitmap strokedBox = createBoxBitmap(false);
- props.mutableOutline().setRoundRect(0, 0, cardWidth, cardHeight, dp(6), 1);
- props.mutableOutline().setShouldClip(true);
- canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
+ // TODO: switch to using round rect clipping, once merging correctly handles that
+ SkPaint roundRectPaint;
+ roundRectPaint.setAntiAlias(true);
+ roundRectPaint.setColor(Color::White);
+ canvas.drawRoundRect(0, 0, cardWidth, cardHeight, dp(6), dp(6), roundRectPaint);
SkPaint textPaint;
textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
diff --git a/libs/hwui/tests/microbench/OpReordererBench.cpp b/libs/hwui/tests/microbench/OpReordererBench.cpp
index 406bfcc..ac2b15c 100644
--- a/libs/hwui/tests/microbench/OpReordererBench.cpp
+++ b/libs/hwui/tests/microbench/OpReordererBench.cpp
@@ -25,7 +25,7 @@
#include "RecordingCanvas.h"
#include "tests/common/TestUtils.h"
#include "Vector.h"
-#include "microbench/MicroBench.h"
+#include "tests/microbench/MicroBench.h"
#include <vector>
diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp
index 98a430a..068e832 100644
--- a/libs/hwui/tests/unit/OpReordererTests.cpp
+++ b/libs/hwui/tests/unit/OpReordererTests.cpp
@@ -65,12 +65,22 @@
virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {}
virtual void endFrame() {}
- // define virtual defaults for direct
-#define BASE_OP_METHOD(Type) \
+ // define virtual defaults for single draw methods
+#define X(Type) \
virtual void on##Type(const Type&, const BakedOpState&) { \
ADD_FAILURE() << #Type " not expected in this test"; \
}
- MAP_OPS(BASE_OP_METHOD)
+ MAP_OPS(X)
+#undef X
+
+ // define virtual defaults for merged draw methods
+#define X(Type) \
+ virtual void onMerged##Type##s(const MergedBakedOpList& opList) { \
+ ADD_FAILURE() << "Merged " #Type "s not expected in this test"; \
+ }
+ MAP_MERGED_OPS(X)
+#undef X
+
int getIndex() { return mIndex; }
protected:
@@ -83,11 +93,21 @@
*/
class TestDispatcher {
public:
-#define DISPATCHER_METHOD(Type) \
+ // define single op methods, which redirect to TestRendererBase
+#define X(Type) \
static void on##Type(TestRendererBase& renderer, const Type& op, const BakedOpState& state) { \
renderer.on##Type(op, state); \
}
- MAP_OPS(DISPATCHER_METHOD);
+ MAP_OPS(X);
+#undef X
+
+ // define merged op methods, which redirect to TestRendererBase
+#define X(Type) \
+ static void onMerged##Type##s(TestRendererBase& renderer, const MergedBakedOpList& opList) { \
+ renderer.onMerged##Type##s(opList); \
+ }
+ MAP_MERGED_OPS(X);
+#undef X
};
class FailRenderer : public TestRendererBase {};
@@ -153,7 +173,8 @@
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
- SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
+ SkBitmap bitmap = TestUtils::createSkBitmap(10, 10,
+ kAlpha_8_SkColorType); // Disable merging by using alpha 8 bitmap
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
// Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
@@ -171,7 +192,7 @@
SimpleBatchingTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2 * LOOPS, renderer.getIndex())
- << "Expect number of ops = 2 * loop count"; // TODO: force no merging
+ << "Expect number of ops = 2 * loop count";
}
TEST(OpReorderer, textStrikethroughBatching) {
@@ -181,8 +202,10 @@
void onRectOp(const RectOp& op, const BakedOpState& state) override {
EXPECT_TRUE(mIndex++ >= LOOPS) << "Strikethrough rects should be above all text";
}
- void onTextOp(const TextOp& op, const BakedOpState& state) override {
- EXPECT_TRUE(mIndex++ < LOOPS) << "Text should be beneath all strikethrough rects";
+ void onMergedTextOps(const MergedBakedOpList& opList) override {
+ EXPECT_EQ(0, mIndex);
+ mIndex += opList.count;
+ EXPECT_EQ(5u, opList.count);
}
};
auto node = TestUtils::createNode(0, 0, 200, 2000,