Merge "Correct GradientDrawable outline alpha computation"
diff --git a/api/current.txt b/api/current.txt
index feb43da..ca176b6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -43255,7 +43255,7 @@
}
public final class SpellCheckerSubtype implements android.os.Parcelable {
- ctor public SpellCheckerSubtype(int, java.lang.String, java.lang.String);
+ ctor public deprecated SpellCheckerSubtype(int, java.lang.String, java.lang.String);
method public boolean containsExtraValueKey(java.lang.String);
method public int describeContents();
method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
diff --git a/api/system-current.txt b/api/system-current.txt
index bc7983a..dcb0852 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -45594,7 +45594,7 @@
}
public final class SpellCheckerSubtype implements android.os.Parcelable {
- ctor public SpellCheckerSubtype(int, java.lang.String, java.lang.String);
+ ctor public deprecated SpellCheckerSubtype(int, java.lang.String, java.lang.String);
method public boolean containsExtraValueKey(java.lang.String);
method public int describeContents();
method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
diff --git a/api/test-current.txt b/api/test-current.txt
index 3bcfd67..da02754 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -43257,7 +43257,7 @@
}
public final class SpellCheckerSubtype implements android.os.Parcelable {
- ctor public SpellCheckerSubtype(int, java.lang.String, java.lang.String);
+ ctor public deprecated SpellCheckerSubtype(int, java.lang.String, java.lang.String);
method public boolean containsExtraValueKey(java.lang.String);
method public int describeContents();
method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 11df9a3..488063b 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -5631,7 +5631,7 @@
final int matchAxisFilter = ((lp.width == MATCH_PARENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
| (lp.height == MATCH_PARENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
- if (matchAxisFilter != 0) {
+ if (matchAxisFilter != 0 || wrapAxisFilter != 0) {
final ViewParent parent = getParent();
if (parent != null) {
// If our parent depends on us for an axis, then our layout can also be affected
diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java
index 2167862..491de78 100644
--- a/core/java/android/view/textservice/SpellCheckerInfo.java
+++ b/core/java/android/view/textservice/SpellCheckerInfo.java
@@ -117,7 +117,9 @@
a.getString(com.android.internal.R.styleable
.SpellChecker_Subtype_subtypeLocale),
a.getString(com.android.internal.R.styleable
- .SpellChecker_Subtype_subtypeExtraValue));
+ .SpellChecker_Subtype_subtypeExtraValue),
+ a.getInt(com.android.internal.R.styleable
+ .SpellChecker_Subtype_subtypeId, 0));
mSubtypes.add(subtype);
}
}
diff --git a/core/java/android/view/textservice/SpellCheckerSubtype.java b/core/java/android/view/textservice/SpellCheckerSubtype.java
index 77fd002..f2b03cc 100644
--- a/core/java/android/view/textservice/SpellCheckerSubtype.java
+++ b/core/java/android/view/textservice/SpellCheckerSubtype.java
@@ -36,12 +36,21 @@
/**
* This class is used to specify meta information of a subtype contained in a spell checker.
* Subtype can describe locale (e.g. en_US, fr_FR...) used for settings.
+ *
+ * @see SpellCheckerInfo
+ *
+ * @attr ref android.R.styleable#SpellChecker_Subtype_label
+ * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeLocale
+ * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeExtraValue
+ * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeId
*/
public final class SpellCheckerSubtype implements Parcelable {
private static final String TAG = SpellCheckerSubtype.class.getSimpleName();
private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
+ private static final int SUBTYPE_ID_NONE = 0;
+ private final int mSubtypeId;
private final int mSubtypeHashCode;
private final int mSubtypeNameResId;
private final String mSubtypeLocale;
@@ -49,16 +58,40 @@
private HashMap<String, String> mExtraValueHashMapCache;
/**
- * Constructor
+ * Constructor.
+ *
+ * <p>There is no public API that requires developers to instantiate custom
+ * {@link SpellCheckerSubtype} object. Hence so far there is no need to make this constructor
+ * available in public API.</p>
+ *
* @param nameId The name of the subtype
* @param locale The locale supported by the subtype
* @param extraValue The extra value of the subtype
+ * @param subtypeId The subtype ID that is supposed to be stable during package update.
+ *
+ * @hide
*/
- public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
+ public SpellCheckerSubtype(int nameId, String locale, String extraValue, int subtypeId) {
mSubtypeNameResId = nameId;
mSubtypeLocale = locale != null ? locale : "";
mSubtypeExtraValue = extraValue != null ? extraValue : "";
- mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
+ mSubtypeId = subtypeId;
+ mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
+ mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
+ }
+
+ /**
+ * Constructor.
+ * @param nameId The name of the subtype
+ * @param locale The locale supported by the subtype
+ * @param extraValue The extra value of the subtype
+ *
+ * @deprecated There is no public API that requires developers to directly instantiate custom
+ * {@link SpellCheckerSubtype} objects right now. Hence only the system is expected to be able
+ * to instantiate {@link SpellCheckerSubtype} object.
+ */
+ public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
+ this(nameId, locale, extraValue, SUBTYPE_ID_NONE);
}
SpellCheckerSubtype(Parcel source) {
@@ -68,7 +101,9 @@
mSubtypeLocale = s != null ? s : "";
s = source.readString();
mSubtypeExtraValue = s != null ? s : "";
- mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
+ mSubtypeId = source.readInt();
+ mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
+ mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
}
/**
@@ -141,10 +176,13 @@
public boolean equals(Object o) {
if (o instanceof SpellCheckerSubtype) {
SpellCheckerSubtype subtype = (SpellCheckerSubtype) o;
+ if (subtype.mSubtypeId != SUBTYPE_ID_NONE || mSubtypeId != SUBTYPE_ID_NONE) {
+ return (subtype.hashCode() == hashCode());
+ }
return (subtype.hashCode() == hashCode())
- && (subtype.getNameResId() == getNameResId())
- && (subtype.getLocale().equals(getLocale()))
- && (subtype.getExtraValue().equals(getExtraValue()));
+ && (subtype.getNameResId() == getNameResId())
+ && (subtype.getLocale().equals(getLocale()))
+ && (subtype.getExtraValue().equals(getExtraValue()));
}
return false;
}
@@ -197,6 +235,7 @@
dest.writeInt(mSubtypeNameResId);
dest.writeString(mSubtypeLocale);
dest.writeString(mSubtypeExtraValue);
+ dest.writeInt(mSubtypeId);
}
public static final Parcelable.Creator<SpellCheckerSubtype> CREATOR
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index ba868a1..bdb1e83 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -683,7 +683,7 @@
}
}
- if (matchAxisFilter != 0) {
+ if (matchAxisFilter != 0 || wrapAxisFilter != 0) {
final ViewParent parent = getParent();
if (parent != null) {
// If our parent depends on us for an axis, then our layout can also be affected
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 9107b1f..531ba2f 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1158,6 +1158,17 @@
lp.height = insets.getSystemWindowInsetBottom();
mNavigationGuard.setLayoutParams(lp);
}
+ updateNavigationGuardColor();
+ }
+ }
+
+ void updateNavigationGuardColor() {
+ if (mNavigationGuard != null) {
+ // Make navigation bar guard invisible if the transparent color is specified.
+ // Only TRANSPARENT is sufficient for hiding the navigation bar if the no software
+ // keyboard is shown by IMS.
+ mNavigationGuard.setVisibility(mWindow.getNavigationBarColor() == Color.TRANSPARENT ?
+ View.INVISIBLE : View.VISIBLE);
}
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 57d2244..2178344 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -3726,6 +3726,7 @@
mForcedNavigationBarColor = true;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
+ mDecor.updateNavigationGuardColor();
}
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 34a66d0..9bca3d6 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3095,6 +3095,13 @@
<!-- The extra value of the subtype. This string can be any string and will be passed to
the SpellChecker. -->
<attr name="subtypeExtraValue" format="string" />
+ <!-- The unique id for the subtype. The text service (spell checker) framework keeps track
+ of enabled subtypes by ID. When the spell checker package gets upgraded, enabled IDs
+ will stay enabled even if other attributes are different. If the ID is unspecified or
+ or explicitly specified to 0 in XML resources,
+ {@code Arrays.hashCode(new Object[] {subtypeLocale, extraValue}) will be used instead.
+ -->
+ <attr name="subtypeId" />
</declare-styleable>
<!-- Use <code>accessibility-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/styles_holo.xml b/core/res/res/values/styles_holo.xml
index 841afd8..3cd60df 100644
--- a/core/res/res/values/styles_holo.xml
+++ b/core/res/res/values/styles_holo.xml
@@ -1176,7 +1176,7 @@
<style name="Widget.Holo.Light.FastScroll" parent="Widget.Holo.FastScroll" />
- <style name="Widget.Holo.SuggestionItem" parent="@android:attr/textAppearanceMedium">
+ <style name="Widget.Holo.SuggestionItem" parent="TextAppearance.Holo.Medium">
<item name="background">@color/white</item>
<item name="drawablePadding">8dip</item>
<item name="ellipsize">marquee</item>
diff --git a/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java b/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java
index 157c815..73fdb10 100644
--- a/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java
+++ b/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java
@@ -29,12 +29,15 @@
* TODO: Most of part can be, and probably should be, moved to CTS.
*/
public class SpellCheckerSubtypeTest extends InstrumentationTestCase {
+ private static final int SUBTYPE_SUBTYPE_ID_NONE = 0;
private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_A = "en_GB";
private static final int SUBTYPE_NAME_RES_ID_A = 0x12345;
private static final String SUBTYPE_EXTRA_VALUE_A = "Key1=Value1,Key2=Value2";
+ private static final int SUBTYPE_SUBTYPE_ID_A = 42;
private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_B = "en_IN";
private static final int SUBTYPE_NAME_RES_ID_B = 0x54321;
private static final String SUBTYPE_EXTRA_VALUE_B = "Key3=Value3,Key4=Value4";
+ private static final int SUBTYPE_SUBTYPE_ID_B = -42;
private static int defaultHashCodeAlgorithm(String locale, String extraValue) {
return Arrays.hashCode(new Object[] {locale, extraValue});
@@ -55,10 +58,9 @@
}
@SmallTest
- public void testSubtype() throws Exception {
+ public void testSubtypeWithNoSubtypeId() throws Exception {
final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A,
- SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A);
-
+ SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE);
assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId());
assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale());
assertEquals("Value1", subtype.getExtraValueOf("Key1"));
@@ -80,6 +82,26 @@
clonedSubtype.hashCode());
}
+ public void testSubtypeWithSubtypeId() throws Exception {
+ final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A,
+ SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A);
+
+ assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId());
+ assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale());
+ assertEquals("Value1", subtype.getExtraValueOf("Key1"));
+ assertEquals("Value2", subtype.getExtraValueOf("Key2"));
+ // Similar to "SubtypeId" in InputMethodSubtype, "SubtypeId" in SpellCheckerSubtype enables
+ // developers to specify a stable and consistent ID for each subtype.
+ assertEquals(SUBTYPE_SUBTYPE_ID_A, subtype.hashCode());
+
+ final SpellCheckerSubtype clonedSubtype = cloneViaParcel(subtype);
+ assertEquals(SUBTYPE_NAME_RES_ID_A, clonedSubtype.getNameResId());
+ assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, clonedSubtype.getLocale());
+ assertEquals("Value1", clonedSubtype.getExtraValueOf("Key1"));
+ assertEquals("Value2", clonedSubtype.getExtraValueOf("Key2"));
+ assertEquals(SUBTYPE_SUBTYPE_ID_A, clonedSubtype.hashCode());
+ }
+
@SmallTest
public void testGetLocaleObject() throws Exception {
assertEquals(new Locale("en"), new SpellCheckerSubtype(
@@ -130,5 +152,54 @@
SUBTYPE_EXTRA_VALUE_A),
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
SUBTYPE_EXTRA_VALUE_B));
+
+ // If subtype ID is 0 (== SUBTYPE_SUBTYPE_ID_NONE), we keep the same behavior.
+ assertEquals(
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE));
+ assertNotEqual(
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE));
+ assertNotEqual(
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE));
+ assertNotEqual(
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_B, SUBTYPE_SUBTYPE_ID_NONE));
+
+ // If subtype ID is not 0, we test the equality based only on the subtype ID.
+ assertEquals(
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A));
+ assertEquals(
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A));
+ assertEquals(
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A));
+ assertEquals(
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_B, SUBTYPE_SUBTYPE_ID_A));
+ assertNotEqual(
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_B));
}
}
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 9c1c0b9..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,
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 3d00e02..23ad1a81b 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -54,10 +54,6 @@
final boolean voiceInteraction;
- // Whether the window has a saved surface from last pause, which can be
- // used to start an entering animation earlier.
- boolean mHasSavedSurface;
-
// Whether we're performing an entering animation with a saved surface.
boolean mAnimatingWithSavedSurface;
@@ -316,39 +312,38 @@
// currently animating with save surfaces. (If the app didn't even finish
// drawing when the user exits, but we have a saved surface from last time,
// we still want to keep that surface.)
- mHasSavedSurface = allDrawn || mAnimatingWithSavedSurface;
- if (mHasSavedSurface) {
- if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
- "Saving surface: " + this);
- return true;
+ return allDrawn || mAnimatingWithSavedSurface;
+ }
+
+ boolean hasSavedSurface() {
+ for (int i = windows.size() -1; i >= 0; i--) {
+ final WindowState ws = windows.get(i);
+ if (ws.hasSavedSurface()) {
+ return true;
+ }
}
return false;
}
void restoreSavedSurfaces() {
- if (!mHasSavedSurface) {
+ if (!hasSavedSurface()) {
return;
}
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
"Restoring saved surfaces: " + this + ", allDrawn=" + allDrawn);
- mHasSavedSurface = false;
mAnimatingWithSavedSurface = true;
for (int i = windows.size() - 1; i >= 0; i--) {
WindowState ws = windows.get(i);
- ws.mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW;
+ ws.restoreSavedSurface();
}
}
void destroySavedSurfaces() {
- if (mHasSavedSurface) {
- if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
- "Destroying saved surface: " + this);
- for (int i = windows.size() - 1; i >= 0; i--) {
- final WindowState win = windows.get(i);
- win.mWinAnimator.destroySurfaceLocked();
- }
+ for (int i = windows.size() - 1; i >= 0; i--) {
+ WindowState win = windows.get(i);
+ win.destroySavedSurface();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0b851da..5237a13 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2816,9 +2816,7 @@
if (mInputMethodWindow == win) {
mInputMethodWindow = null;
}
- if (!win.shouldSaveSurface()) {
- winAnimator.destroySurfaceLocked();
- }
+ win.destroyOrSaveSurface();
}
//TODO (multidisplay): Magnification is supported only for the default
if (mAccessibilityController != null
@@ -8985,11 +8983,10 @@
Slog.w(TAG, "LEAKED SURFACE (app token hidden): "
+ ws + " surface=" + wsa.mSurfaceController
+ " token=" + ws.mAppToken
- + " saved=" + ws.mAppToken.mHasSavedSurface);
+ + " saved=" + ws.mAppToken.hasSavedSurface());
if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null);
wsa.destroySurface();
ws.setHasSurface(false);
- ws.mAppToken.mHasSavedSurface = false;
leakedSurface = true;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5e38492..c9ded3a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -84,6 +84,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
import static com.android.server.wm.WindowManagerService.DEBUG_FOCUS_LIGHT;
import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
@@ -402,6 +404,10 @@
/** When true this window can be displayed on screens owther than mOwnerUid's */
private boolean mShowToOwnerOnly;
+ // Whether the window has a saved surface from last pause, which can be
+ // used to start an entering animation earlier.
+ public boolean mSurfaceSaved = false;
+
/**
* Wake lock for drawing.
* Even though it's slightly more expensive to do so, we will use a separate wake lock
@@ -1670,22 +1676,45 @@
return mAppToken != null && mAppToken.mAnimatingWithSavedSurface;
}
- boolean shouldSaveSurface() {
+ // Returns true if the surface is saved.
+ boolean destroyOrSaveSurface() {
+ Task task = getTask();
if (ActivityManager.isLowRamDeviceStatic()) {
// Don't save surfaces on Svelte devices.
- return false;
- }
-
- Task task = getTask();
- if (task == null || task.inHomeStack()
+ mSurfaceSaved = false;
+ } else if (task == null || task.inHomeStack()
|| task.getTopVisibleAppToken() != mAppToken) {
// Don't save surfaces for home stack apps. These usually resume and draw
// first frame very fast. Saving surfaces are mostly a waste of memory.
// Don't save if the window is not the topmost window.
- return false;
+ mSurfaceSaved = false;
+ } else if (mAttachedWindow != null) {
+ mSurfaceSaved = false;
+ } else {
+ mSurfaceSaved = mAppToken.shouldSaveSurface();
}
+ if (mSurfaceSaved == false) {
+ mWinAnimator.destroySurfaceLocked();
+ }
+ return mSurfaceSaved;
+ }
- return mAppToken.shouldSaveSurface();
+ public void destroySavedSurface() {
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+ "Destroying saved surface: " + this);
+
+ if (mSurfaceSaved) {
+ mWinAnimator.destroySurfaceLocked();
+ }
+ }
+
+ public boolean hasSavedSurface() {
+ return mSurfaceSaved;
+ }
+
+ public void restoreSavedSurface() {
+ mSurfaceSaved = false;
+ mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW;
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 132b1b6..1790fb3 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -726,13 +726,14 @@
void destroySurfaceLocked() {
final AppWindowToken wtoken = mWin.mAppToken;
if (wtoken != null) {
- wtoken.mHasSavedSurface = false;
wtoken.mAnimatingWithSavedSurface = false;
if (mWin == wtoken.startingWindow) {
wtoken.startingDisplayed = false;
}
}
+ mWin.mSurfaceSaved = false;
+
if (mSurfaceController != null) {
int i = mWin.mChildWindows.size();
// When destroying a surface we want to make sure child windows
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 2149019..3ae3be5 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -394,9 +394,7 @@
if (mWallpaperControllerLocked.isWallpaperTarget(win)) {
wallpaperDestroyed = true;
}
- if (!win.shouldSaveSurface()) {
- win.mWinAnimator.destroySurfaceLocked();
- }
+ win.destroyOrSaveSurface();
} while (i > 0);
mService.mDestroySurface.clear();
}
@@ -1252,7 +1250,7 @@
+ wtoken.startingDisplayed + " startingMoved="
+ wtoken.startingMoved);
- if (wtoken.mHasSavedSurface || wtoken.mAnimatingWithSavedSurface) {
+ if (wtoken.hasSavedSurface() || wtoken.mAnimatingWithSavedSurface) {
continue;
}
if (!wtoken.allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) {