From 000c374e2b00e9192c944e61531570654f720d24 Mon Sep 17 00:00:00 2001 From: John Reck Date: Thu, 17 Feb 2022 17:43:36 -0500 Subject: Add P010 constant for HardwareBuffer Also fix a typo in ImageFormat's docs Fixes: 218928677 Test: make Change-Id: Icc5d32afac113e639a81a11c86bee1698ef12861 --- graphics/java/android/graphics/ImageFormat.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index 9feb619b34e3..b341a4e27e67 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -428,7 +428,7 @@ public class ImageFormat { /** *

Private raw camera sensor image format, a single channel image with - * implementation depedent pixel layout.

+ * implementation dependent pixel layout.

* *

RAW_PRIVATE is a format for unprocessed raw image buffers coming from an * image sensor. The actual structure of buffers of this format is -- cgit v1.2.3-59-g8ed1b From 4ae65dff9f8d164343d46e8a50c6804d0f0fe7ab Mon Sep 17 00:00:00 2001 From: "James.cf Lin" Date: Tue, 8 Feb 2022 16:11:47 +0800 Subject: Update the text wrapping API. 1) Update the LineBreakConfig class to be immutable. 2) Do not return null in the PrecomputedText.Params#getLineBreaiConfig API Bug: 216638444 Test: atest TextViewTest; atest MeasuredTextTest; atest PrecomputedTextTest; atest TextViewPrecomputedTextTest; atest StaticLayoutLineBreakingVariantsTest Change-Id: I07766137ff6639c7d4acaad07dbcf11a2841cdb0 --- core/api/current.txt | 19 +-- core/java/android/text/PrecomputedText.java | 40 ++----- core/java/android/text/StaticLayout.java | 3 +- core/java/android/widget/TextView.java | 130 ++++++++++++--------- core/res/res/values/attrs.xml | 4 +- .../android/graphics/text/LineBreakConfig.java | 106 ++++++++++++----- 6 files changed, 178 insertions(+), 124 deletions(-) (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index 4d1e280f798f..5f498034b0fc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -16384,12 +16384,8 @@ package android.graphics.pdf { package android.graphics.text { public final class LineBreakConfig { - ctor public LineBreakConfig(); method public int getLineBreakStyle(); method public int getLineBreakWordStyle(); - method public void set(@NonNull android.graphics.text.LineBreakConfig); - method public void setLineBreakStyle(int); - method public void setLineBreakWordStyle(int); field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1 field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0 field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2 @@ -16398,6 +16394,13 @@ package android.graphics.text { field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1 } + public static final class LineBreakConfig.Builder { + ctor public LineBreakConfig.Builder(); + method @NonNull public android.graphics.text.LineBreakConfig build(); + method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakStyle(int); + method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakWordStyle(int); + } + public class LineBreaker { method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int); field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2 @@ -45026,7 +45029,7 @@ package android.text { public static final class PrecomputedText.Params { method public int getBreakStrategy(); method public int getHyphenationFrequency(); - method @Nullable public android.graphics.text.LineBreakConfig getLineBreakConfig(); + method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig(); method @NonNull public android.text.TextDirectionHeuristic getTextDirection(); method @NonNull public android.text.TextPaint getTextPaint(); } @@ -57333,7 +57336,8 @@ package android.widget { method public final android.text.Layout getLayout(); method public float getLetterSpacing(); method public int getLineBounds(int, android.graphics.Rect); - method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig(); + method public int getLineBreakStyle(); + method public int getLineBreakWordStyle(); method public int getLineCount(); method public int getLineHeight(); method public float getLineSpacingExtra(); @@ -57461,7 +57465,8 @@ package android.widget { method public void setKeyListener(android.text.method.KeyListener); method public void setLastBaselineToBottomHeight(@IntRange(from=0) @Px int); method public void setLetterSpacing(float); - method public void setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig); + method public void setLineBreakStyle(int); + method public void setLineBreakWordStyle(int); method public void setLineHeight(@IntRange(from=0) @Px int); method public void setLineSpacing(float, float); method public void setLines(int); diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java index be66db2a4c05..f38a10f8b647 100644 --- a/core/java/android/text/PrecomputedText.java +++ b/core/java/android/text/PrecomputedText.java @@ -98,7 +98,7 @@ public class PrecomputedText implements Spannable { private final @Layout.HyphenationFrequency int mHyphenationFrequency; // The line break configuration for calculating text wrapping. - private final @Nullable LineBreakConfig mLineBreakConfig; + private final @NonNull LineBreakConfig mLineBreakConfig; /** * A builder for creating {@link Params}. @@ -118,7 +118,7 @@ public class PrecomputedText implements Spannable { Layout.HYPHENATION_FREQUENCY_NORMAL; // The line break configuration for calculating text wrapping. - private @Nullable LineBreakConfig mLineBreakConfig; + private @NonNull LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE; /** * Builder constructor. @@ -211,7 +211,7 @@ public class PrecomputedText implements Spannable { // For the external developers, use Builder instead. /** @hide */ public Params(@NonNull TextPaint paint, - @Nullable LineBreakConfig lineBreakConfig, + @NonNull LineBreakConfig lineBreakConfig, @NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { @@ -259,11 +259,12 @@ public class PrecomputedText implements Spannable { } /** - * Return the line break configuration for this text. + * Returns the {@link LineBreakConfig} for this text. * - * @return the current line break configuration, null if no line break configuration is set. + * @return the current line break configuration. The {@link LineBreakConfig} with default + * values will be returned if no line break configuration is set. */ - public @Nullable LineBreakConfig getLineBreakConfig() { + public @NonNull LineBreakConfig getLineBreakConfig() { return mLineBreakConfig; } @@ -296,9 +297,9 @@ public class PrecomputedText implements Spannable { /** @hide */ public @CheckResultUsableResult int checkResultUsable(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy, - @Layout.HyphenationFrequency int frequency, @Nullable LineBreakConfig lbConfig) { + @Layout.HyphenationFrequency int frequency, @NonNull LineBreakConfig lbConfig) { if (mBreakStrategy == strategy && mHyphenationFrequency == frequency - && isLineBreakEquals(mLineBreakConfig, lbConfig) + && mLineBreakConfig.equals(lbConfig) && mPaint.equalsForTextMeasurement(paint)) { return mTextDir == textDir ? USABLE : NEED_RECOMPUTE; } else { @@ -306,29 +307,6 @@ public class PrecomputedText implements Spannable { } } - /** - * Check the two LineBreakConfig instances are equal. - * This method assumes they are equal if one parameter is null and the other parameter has - * a LineBreakStyle value of LineBreakConfig.LINE_BREAK_STYLE_NONE. - * - * @param o1 the first LineBreakConfig instance. - * @param o2 the second LineBreakConfig instance. - * @return true if the two LineBreakConfig instances are equal. - */ - private boolean isLineBreakEquals(LineBreakConfig o1, LineBreakConfig o2) { - if (Objects.equals(o1, o2)) { - return true; - } - if (o1 == null && (o2 != null - && o2.getLineBreakStyle() == LineBreakConfig.LINE_BREAK_STYLE_NONE)) { - return true; - } else if (o2 == null && (o1 != null - && o1.getLineBreakStyle() == LineBreakConfig.LINE_BREAK_STYLE_NONE)) { - return true; - } - return false; - } - /** * Check if the same text layout. * diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index b10fc37bff2f..d63d66e965c2 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -410,7 +410,8 @@ public class StaticLayout extends Layout { * * @param lineBreakConfig the line break configuration for text wrapping. * @return this builder, useful for chaining. - * @see android.widget.TextView#setLineBreakConfig + * @see android.widget.TextView#setLineBreakStyle + * @see android.widget.TextView#setLineBreakWordStyle */ @NonNull public Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3dfb4a5a084a..dbaf0aaccb1a 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -788,7 +788,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private Layout mLayout; private boolean mLocalesChanged = false; private int mTextSizeUnit = -1; - private LineBreakConfig mLineBreakConfig = new LineBreakConfig(); + private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; + private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; // This is used to reflect the current user preference for changing font weight and making text // more bold. @@ -1457,13 +1458,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextView_lineBreakStyle: - mLineBreakConfig.setLineBreakStyle( - a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE)); + mLineBreakStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE); break; case com.android.internal.R.styleable.TextView_lineBreakWordStyle: - mLineBreakConfig.setLineBreakWordStyle( - a.getInt(attr, LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)); + mLineBreakWordStyle = a.getInt(attr, + LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE); break; case com.android.internal.R.styleable.TextView_autoSizeTextType: @@ -4301,13 +4301,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @LineBreakConfig.LineBreakStyle int lineBreakStyle, @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { boolean updated = false; - if (isLineBreakStyleSpecified && mLineBreakConfig.getLineBreakStyle() != lineBreakStyle) { - mLineBreakConfig.setLineBreakStyle(lineBreakStyle); + if (isLineBreakStyleSpecified && mLineBreakStyle != lineBreakStyle) { + mLineBreakStyle = lineBreakStyle; updated = true; } - if (isLineBreakWordStyleSpecified - && mLineBreakConfig.getLineBreakWordStyle() != lineBreakWordStyle) { - mLineBreakConfig.setLineBreakWordStyle(lineBreakWordStyle); + if (isLineBreakWordStyleSpecified && mLineBreakWordStyle != lineBreakWordStyle) { + mLineBreakWordStyle = lineBreakWordStyle; updated = true; } if (updated && mLayout != null) { @@ -4871,50 +4870,72 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Sets line break configuration indicates which strategy needs to be used when calculating the - * text wrapping. - *

- * There are two types of line break rules that can be configured at the same time. One is - * line break style(lb) and the other is line break word style(lw). The line break style - * affects rule-based breaking. The line break word style affects dictionary-based breaking - * and provide phrase-based breaking opportunities. There are several types for the - * line break style: + * Set the line break style for text wrapping. + * + * The line break style to indicates the line break strategies can be used when + * calculating the text wrapping. The line break style affects rule-based breaking. It + * specifies the strictness of line-breaking rules. + * There are several types for the line break style: * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}, * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and - * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. - * The type for the line break word style is - * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}. - * The default values of the line break style and the line break word style are - * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE} and - * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE} respectively, indicating that no line - * breaking rules are specified. - * See + * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. The default values of the line break style + * is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, indicating no breaking rule is specified. + * See * the line-break property * - * @param lineBreakConfig the line break config for text wrapping. + * @param lineBreakStyle the line break style for the text. */ - public void setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) { - Objects.requireNonNull(lineBreakConfig); - if (mLineBreakConfig.equals(lineBreakConfig)) { - return; + public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) { + if (mLineBreakStyle != lineBreakStyle) { + mLineBreakStyle = lineBreakStyle; + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } } - mLineBreakConfig.set(lineBreakConfig); - if (mLayout != null) { - nullLayouts(); - requestLayout(); - invalidate(); + } + + /** + * Set the line break word style for text wrapping. + * + * The line break word style affects dictionary-based breaking and provide phrase-based + * breaking opportunities. The type for the line break word style is + * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}. The default values of the line break + * word style is {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, indicating no breaking rule + * is specified. + * See + * the word-break property + * + * @param lineBreakWordStyle the line break word style for the tet + */ + public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { + if (mLineBreakWordStyle != lineBreakWordStyle) { + mLineBreakWordStyle = lineBreakWordStyle; + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } } } /** - * Get the current line break configuration for text wrapping. + * Get the current line break style for text wrapping. * - * @return the current line break configuration to be used for text wrapping. + * @return the current line break style to be used for text wrapping. */ - public @NonNull LineBreakConfig getLineBreakConfig() { - LineBreakConfig lbConfig = new LineBreakConfig(); - lbConfig.set(mLineBreakConfig); - return lbConfig; + public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() { + return mLineBreakStyle; + } + + /** + * Get the current line word break style for text wrapping. + * + * @return the current line break word style to be used for text wrapping. + */ + public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() { + return mLineBreakWordStyle; } /** @@ -4924,7 +4945,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see PrecomputedText */ public @NonNull PrecomputedText.Params getTextMetricsParams() { - return new PrecomputedText.Params(new TextPaint(mTextPaint), mLineBreakConfig, + return new PrecomputedText.Params(new TextPaint(mTextPaint), + LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle), getTextDirectionHeuristic(), mBreakStrategy, mHyphenationFrequency); } @@ -4941,13 +4963,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mTextDir = params.getTextDirection(); mBreakStrategy = params.getBreakStrategy(); mHyphenationFrequency = params.getHyphenationFrequency(); - if (params.getLineBreakConfig() != null) { - mLineBreakConfig.set(params.getLineBreakConfig()); - } else { - // Set default value if the line break config in the PrecomputedText.Params is null. - mLineBreakConfig.setLineBreakStyle(DEFAULT_LINE_BREAK_STYLE); - mLineBreakConfig.setLineBreakWordStyle(DEFAULT_LINE_BREAK_WORD_STYLE); - } + LineBreakConfig lineBreakConfig = params.getLineBreakConfig(); + mLineBreakStyle = lineBreakConfig.getLineBreakStyle(); + mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle(); if (mLayout != null) { nullLayouts(); requestLayout(); @@ -6486,7 +6504,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, - mHyphenationFrequency, mLineBreakConfig); + mHyphenationFrequency, LineBreakConfig.getLineBreakConfig( + mLineBreakStyle, mLineBreakWordStyle)); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: throw new IllegalArgumentException( @@ -9383,7 +9402,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) - .setLineBreakConfig(mLineBreakConfig); + .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( + mLineBreakStyle, mLineBreakWordStyle)); if (shouldEllipsize) { builder.setEllipsize(mEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -9498,7 +9518,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) - .setLineBreakConfig(mLineBreakConfig); + .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( + mLineBreakStyle, mLineBreakWordStyle)); if (shouldEllipsize) { builder.setEllipsize(effectiveEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -9866,7 +9887,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setJustificationMode(getJustificationMode()) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setTextDirection(getTextDirectionHeuristic()) - .setLineBreakConfig(mLineBreakConfig); + .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( + mLineBreakStyle, mLineBreakWordStyle)); final StaticLayout layout = layoutBuilder.build(); diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index fca2bd15787d..025027c74abc 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5471,9 +5471,9 @@ - + - + diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index cffdf28dbc27..d083e444e996 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -26,7 +26,7 @@ import java.util.Objects; /** * Indicates the strategies can be used when calculating the text wrapping. * - * See the line-break property + * See the line-break property */ public final class LineBreakConfig { @@ -78,39 +78,96 @@ public final class LineBreakConfig { @Retention(RetentionPolicy.SOURCE) public @interface LineBreakWordStyle {} - private @LineBreakStyle int mLineBreakStyle = LINE_BREAK_STYLE_NONE; - private @LineBreakWordStyle int mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_NONE; - - public LineBreakConfig() { + /** + * A builder for creating {@link LineBreakConfig}. + */ + public static final class Builder { + // The line break style for the LineBreakConfig. + private @LineBreakStyle int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE; + + // The line break word style for the LineBreakConfig. + private @LineBreakWordStyle int mLineBreakWordStyle = + LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; + + /** + * Builder constructor with line break parameters. + */ + public Builder() { + } + + /** + * Set the line break style. + * + * @param lineBreakStyle the new line break style. + * @return this Builder + */ + public @NonNull Builder setLineBreakStyle(@LineBreakStyle int lineBreakStyle) { + mLineBreakStyle = lineBreakStyle; + return this; + } + + /** + * Set the line break word style. + * + * @param lineBreakWordStyle the new line break word style. + * @return this Builder + */ + public @NonNull Builder setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) { + mLineBreakWordStyle = lineBreakWordStyle; + return this; + } + + /** + * Build the {@link LineBreakConfig} + * + * @return the LineBreakConfig instance. + */ + public @NonNull LineBreakConfig build() { + return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle); + } } /** - * Set the line break configuration. + * Create the LineBreakConfig instance. * - * @param lineBreakConfig the new line break configuration. + * @param lineBreakStyle the line break style for text wrapping. + * @param lineBreakWordStyle the line break word style for text wrapping. + * @return the {@link LineBreakConfig} instance. + * @hide */ - public void set(@NonNull LineBreakConfig lineBreakConfig) { - Objects.requireNonNull(lineBreakConfig); - mLineBreakStyle = lineBreakConfig.getLineBreakStyle(); - mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle(); + public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle, + @LineBreakWordStyle int lineBreakWordStyle) { + LineBreakConfig.Builder builder = new LineBreakConfig.Builder(); + return builder.setLineBreakStyle(lineBreakStyle) + .setLineBreakWordStyle(lineBreakWordStyle) + .build(); } + /** @hide */ + public static final LineBreakConfig NONE = + new Builder().setLineBreakStyle(LINE_BREAK_STYLE_NONE) + .setLineBreakWordStyle(LINE_BREAK_WORD_STYLE_NONE).build(); + + private final @LineBreakStyle int mLineBreakStyle; + private final @LineBreakWordStyle int mLineBreakWordStyle; + /** - * Get the line break style. - * - * @return The current line break style to be used for the text wrapping. + * Constructor with the line break parameters. + * Use the {@link LineBreakConfig.Builder} to create the LineBreakConfig instance. */ - public @LineBreakStyle int getLineBreakStyle() { - return mLineBreakStyle; + private LineBreakConfig(@LineBreakStyle int lineBreakStyle, + @LineBreakWordStyle int lineBreakWordStyle) { + mLineBreakStyle = lineBreakStyle; + mLineBreakWordStyle = lineBreakWordStyle; } /** - * Set the line break style. + * Get the line break style. * - * @param lineBreakStyle the new line break style. + * @return The current line break style to be used for the text wrapping. */ - public void setLineBreakStyle(@LineBreakStyle int lineBreakStyle) { - mLineBreakStyle = lineBreakStyle; + public @LineBreakStyle int getLineBreakStyle() { + return mLineBreakStyle; } /** @@ -122,15 +179,6 @@ public final class LineBreakConfig { return mLineBreakWordStyle; } - /** - * Set the line break word style. - * - * @param lineBreakWordStyle the new line break word style. - */ - public void setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) { - mLineBreakWordStyle = lineBreakWordStyle; - } - @Override public boolean equals(Object o) { if (o == null) return false; -- cgit v1.2.3-59-g8ed1b From 97726de9053156dfa8cb71cef9b64eb46603a3f1 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Wed, 16 Feb 2022 14:18:28 -0800 Subject: Add conversion between @NamedDataSpace and ColorSpace.Named enums - Add getDataSpace/getFromDataSpace in ColorSpace class Bug: 220016464 Test: android.graphics.cts.ColorSpaceTest Change-Id: Ic909c57045ffb6462a7f6b4768e515924715d09a --- core/api/current.txt | 2 + graphics/java/android/graphics/ColorSpace.java | 56 ++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index d9e856a6ddc0..362978589eb4 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -14332,6 +14332,8 @@ package android.graphics { method @NonNull @Size(min=3) public abstract float[] fromXyz(@NonNull @Size(min=3) float[]); method @NonNull public static android.graphics.ColorSpace get(@NonNull android.graphics.ColorSpace.Named); method @IntRange(from=1, to=4) public int getComponentCount(); + method public int getDataSpace(); + method @Nullable public static android.graphics.ColorSpace getFromDataSpace(int); method @IntRange(from=android.graphics.ColorSpace.MIN_ID, to=android.graphics.ColorSpace.MAX_ID) public int getId(); method public abstract float getMaxValue(@IntRange(from=0, to=3) int); method public abstract float getMinValue(@IntRange(from=0, to=3) int); diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 2f978fc1fc2d..582488ff8de3 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -22,6 +22,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; import android.annotation.SuppressAutoDoc; +import android.annotation.SuppressLint; +import android.hardware.DataSpace; +import android.hardware.DataSpace.NamedDataSpace; +import android.util.SparseIntArray; import libcore.util.NativeAllocationRegistry; @@ -207,6 +211,7 @@ public abstract class ColorSpace { // See static initialization block next to #get(Named) private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length]; + private static final SparseIntArray sDataToColorSpaces = new SparseIntArray(); @NonNull private final String mName; @NonNull private final Model mModel; @@ -1388,6 +1393,47 @@ public abstract class ColorSpace { return sNamedColorSpaces[index]; } + /** + * Create a {@link ColorSpace} object using a {@link android.hardware.DataSpace DataSpace} + * value. + * + *

This function maps from a dataspace to a {@link Named} ColorSpace. + * If no {@link Named} ColorSpace object matching the {@code dataSpace} value can be created, + * {@code null} will return.

+ * + * @param dataSpace The dataspace value + * @return the ColorSpace object or {@code null} if no matching colorspace can be found. + */ + @SuppressLint("MethodNameUnits") + @Nullable + public static ColorSpace getFromDataSpace(@NamedDataSpace int dataSpace) { + int index = sDataToColorSpaces.get(dataSpace, -1); + if (index != -1) { + return ColorSpace.get(index); + } else { + return null; + } + } + + /** + * Retrieve the {@link android.hardware.DataSpace DataSpace} value from a {@link ColorSpace} + * object. + * + *

If this {@link ColorSpace} object has no matching {@code dataSpace} value, + * {@link android.hardware.DataSpace#DATASPACE_UNKNOWN DATASPACE_UNKNOWN} will return.

+ * + * @return the dataspace value. + */ + @SuppressLint("MethodNameUnits") + public @NamedDataSpace int getDataSpace() { + int index = sDataToColorSpaces.indexOfValue(getId()); + if (index != -1) { + return sDataToColorSpaces.keyAt(index); + } else { + return DataSpace.DATASPACE_UNKNOWN; + } + } + /** *

Returns an instance of {@link ColorSpace} identified by the specified * name. The list of names provided in the {@link Named} enum gives access @@ -1445,6 +1491,7 @@ public abstract class ColorSpace { SRGB_TRANSFER_PARAMETERS, Named.SRGB.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB, Named.SRGB.ordinal()); sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb( "sRGB IEC61966-2.1 (Linear)", SRGB_PRIMARIES, @@ -1453,6 +1500,7 @@ public abstract class ColorSpace { 0.0f, 1.0f, Named.LINEAR_SRGB.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB_LINEAR, Named.LINEAR_SRGB.ordinal()); sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( "scRGB-nl IEC 61966-2-2:2003", SRGB_PRIMARIES, @@ -1464,6 +1512,7 @@ public abstract class ColorSpace { SRGB_TRANSFER_PARAMETERS, Named.EXTENDED_SRGB.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_SCRGB, Named.EXTENDED_SRGB.ordinal()); sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( "scRGB IEC 61966-2-2:2003", SRGB_PRIMARIES, @@ -1472,6 +1521,8 @@ public abstract class ColorSpace { -0.5f, 7.499f, Named.LINEAR_EXTENDED_SRGB.ordinal() ); + sDataToColorSpaces.put( + DataSpace.DATASPACE_SCRGB_LINEAR, Named.LINEAR_EXTENDED_SRGB.ordinal()); sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb( "Rec. ITU-R BT.709-5", new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, @@ -1480,6 +1531,7 @@ public abstract class ColorSpace { new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), Named.BT709.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal()); sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb( "Rec. ITU-R BT.2020-1", new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f }, @@ -1488,6 +1540,7 @@ public abstract class ColorSpace { new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45), Named.BT2020.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020, Named.BT2020.ordinal()); sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb( "SMPTE RP 431-2-2007 DCI (P3)", new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, @@ -1496,6 +1549,7 @@ public abstract class ColorSpace { 0.0f, 1.0f, Named.DCI_P3.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_DCI_P3, Named.DCI_P3.ordinal()); sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb( "Display P3", new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, @@ -1504,6 +1558,7 @@ public abstract class ColorSpace { SRGB_TRANSFER_PARAMETERS, Named.DISPLAY_P3.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_DISPLAY_P3, Named.DISPLAY_P3.ordinal()); sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb( "NTSC (1953)", NTSC_1953_PRIMARIES, @@ -1528,6 +1583,7 @@ public abstract class ColorSpace { 0.0f, 1.0f, Named.ADOBE_RGB.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_ADOBE_RGB, Named.ADOBE_RGB.ordinal()); sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb( "ROMM RGB ISO 22028-2:2013", new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f }, -- cgit v1.2.3-59-g8ed1b From 0bc60e70dabe1c5c5df682ca196f3b32e226a230 Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Wed, 2 Mar 2022 15:43:49 -0800 Subject: Add legacy emoji font file for compatibility Bug: 222362958 Test: Manually verified Flutter Gallary shows emoji. Test: atest CtsTextTestCases CtsGraphicsTestCases Test: atest FontListParserTest Change-Id: If5b5ddfb6d0d46cfb8d27cf66843ae95d4b50e03 --- .../src/android/graphics/FontListParserTest.java | 21 +++++++++++++++++++++ data/fonts/fonts.xml | 3 +++ graphics/java/android/graphics/FontListParser.java | 5 ++++- tools/fonts/fontchain_linter.py | 7 +++++++ 4 files changed, 35 insertions(+), 1 deletion(-) (limited to 'graphics/java/android') diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java index 701e6194d4ee..479e52aab029 100644 --- a/core/tests/coretests/src/android/graphics/FontListParserTest.java +++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java @@ -355,6 +355,27 @@ public final class FontListParserTest { assertThat(config.getAliases()).isEmpty(); } + @Test + public void ignore() throws Exception { + String xml = "" + + "" + + " " + + " test.ttf" + + " " + + " " + + " emoji_legacy.ttf" + + " " + + " " + + " emoji.ttf" + + " " + + ""; + FontConfig config = readFamilies(xml, true /* include non-existing font files */); + List families = config.getFontFamilies(); + assertThat(families.size()).isEqualTo(2); // legacy one should be ignored. + assertThat(families.get(1).getFontList().get(0).getFile().getName()) + .isEqualTo("emoji.ttf"); + } + private FontConfig readFamilies(String xml, boolean allowNonExisting) throws IOException, XmlPullParserException { ByteArrayInputStream buffer = new ByteArrayInputStream( diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index e050e1784fac..84e949a18c52 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -1328,6 +1328,9 @@ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc + + NotoColorEmojiLegacy.ttf + NotoColorEmoji.ttf diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 96b33259e739..4bb16c6b8186 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -218,6 +218,7 @@ public class FontListParser { final String name = parser.getAttributeValue(null, "name"); final String lang = parser.getAttributeValue("", "lang"); final String variant = parser.getAttributeValue(null, "variant"); + final String ignore = parser.getAttributeValue(null, "ignore"); final List fonts = new ArrayList<>(); while (keepReading(parser)) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; @@ -240,7 +241,9 @@ public class FontListParser { intVariant = FontConfig.FontFamily.VARIANT_ELEGANT; } } - if (fonts.isEmpty()) { + + boolean skip = (ignore != null && (ignore.equals("true") || ignore.equals("1"))); + if (skip || fonts.isEmpty()) { return null; } return new FontConfig.FontFamily(fonts, name, LocaleList.forLanguageTags(lang), intVariant); diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index 2c2c91825162..0d9ea1b70680 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -243,6 +243,8 @@ def parse_fonts_xml(fonts_xml_path): name = family.get('name') variant = family.get('variant') langs = family.get('lang') + ignoreAttr = family.get('ignore') + if name: assert variant is None, ( 'No variant expected for LGC font %s.' % name) @@ -259,6 +261,11 @@ def parse_fonts_xml(fonts_xml_path): name = family.get('name') variant = family.get('variant') langs = family.get('lang') + ignoreAttr = family.get('ignore') + ignore = ignoreAttr == 'true' or ignoreAttr == '1' + + if ignore: + continue if langs: langs = langs.split() -- cgit v1.2.3-59-g8ed1b From f70429088abf54867a920433e9c25f33f773fd53 Mon Sep 17 00:00:00 2001 From: allenwtsu Date: Thu, 10 Mar 2022 18:13:13 +0800 Subject: Text Wrapping automation Bug: 220836284 Test: atest StaticLayoutTest Change-Id: I2aead872b0b472b3c625e075348e46da768ff3be --- core/java/android/text/StaticLayout.java | 53 ++++++++++++++++++++++ core/java/android/util/FeatureFlagUtils.java | 5 ++ core/java/android/widget/TextView.java | 39 +++++++++++++--- .../src/android/text/StaticLayoutTest.java | 21 +++++++++ .../android/graphics/text/LineBreakConfig.java | 52 +++++++++++++++++++-- 5 files changed, 161 insertions(+), 9 deletions(-) (limited to 'graphics/java/android') diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 2f85d2b63840..520ceb2582db 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -25,6 +25,7 @@ import android.graphics.Paint; import android.graphics.text.LineBreakConfig; import android.graphics.text.LineBreaker; import android.os.Build; +import android.os.SystemProperties; import android.text.style.LeadingMarginSpan; import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; import android.text.style.LineHeightSpan; @@ -32,6 +33,7 @@ import android.text.style.TabStopSpan; import android.util.Log; import android.util.Pools.SynchronizedPool; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; @@ -73,6 +75,13 @@ public class StaticLayout extends Layout { * default values. */ public final static class Builder { + // The content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE. + private static final int DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE = 3; + + // The property of content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE. + private static final String PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE = + "android.phrase.linecount.threshold"; + private Builder() {} /** @@ -431,11 +440,55 @@ public class StaticLayout extends Layout { */ @NonNull public StaticLayout build() { + reviseLineBreakConfig(); StaticLayout result = new StaticLayout(this); Builder.recycle(this); return result; } + private void reviseLineBreakConfig() { + boolean autoPhraseBreaking = mLineBreakConfig.getAutoPhraseBreaking(); + int wordStyle = mLineBreakConfig.getLineBreakWordStyle(); + if (autoPhraseBreaking) { + if (wordStyle != LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) { + if (shouldEnablePhraseBreaking()) { + mLineBreakConfig = LineBreakConfig.getLineBreakConfig( + mLineBreakConfig.getLineBreakStyle(), + LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE, + mLineBreakConfig.getAutoPhraseBreaking()); + } + } + } + } + + private boolean shouldEnablePhraseBreaking() { + if (TextUtils.isEmpty(mText) || mWidth <= 0) { + return false; + } + int lineLimit = SystemProperties.getInt( + PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE, + DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE); + double desiredWidth = (double) Layout.getDesiredWidth(mText, mStart, + mEnd, mPaint, mTextDir); + int lineCount = (int) Math.ceil(desiredWidth / mWidth); + if (lineCount > 0 && lineCount <= lineLimit) { + return true; + } + return false; + } + + /** + * Get the line break word style. + * + * @return The current line break word style. + * + * @hide + */ + @VisibleForTesting + public int getLineBreakWordStyle() { + return mLineBreakConfig.getLineBreakWordStyle(); + } + private CharSequence mText; private int mStart; private int mEnd; diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 34e7ea74a2db..faf88f88d308 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -74,6 +74,9 @@ public class FeatureFlagUtils { public static final String SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE = "settings_hide_second_layer_page_navigate_up_button_in_two_pane"; + /** @hide */ + public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping"; + private static final Map DEFAULT_FLAGS; static { @@ -100,6 +103,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true"); DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "false"); DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true"); + DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false"); } private static final Set PERSISTENT_FLAGS; @@ -110,6 +114,7 @@ public class FeatureFlagUtils { PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS); PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME); PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE); + PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING); } /** diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3c8fcb978fbd..ce8d5e8de7b5 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -144,6 +144,7 @@ import android.text.style.UpdateAppearance; import android.text.util.Linkify; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.util.FeatureFlagUtils; import android.util.IntArray; import android.util.Log; import android.util.SparseIntArray; @@ -791,6 +792,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; + // The auto option for LINE_BREAK_WORD_STYLE_PHRASE may not be applied in recycled view due to + // one-way flag flipping. This is a tentative limitation during experiment and will not have the + // issue once this is finalized to LINE_BREAK_WORD_STYLE_PHRASE_AUTO option. + private boolean mUserSpeficiedLineBreakwordStyle = false; + // This is used to reflect the current user preference for changing font weight and making text // more bold. private int mFontWeightAdjustment; @@ -1462,6 +1468,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextView_lineBreakWordStyle: + if (a.hasValue(attr)) { + mUserSpeficiedLineBreakwordStyle = true; + } mLineBreakWordStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE); break; @@ -4209,6 +4218,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle: attributes.mHasLineBreakWordStyle = true; + mUserSpeficiedLineBreakwordStyle = true; attributes.mLineBreakWordStyle = appearance.getInt(attr, attributes.mLineBreakWordStyle); break; @@ -4910,6 +4920,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @param lineBreakWordStyle the line break word style for the tet */ public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { + mUserSpeficiedLineBreakwordStyle = true; if (mLineBreakWordStyle != lineBreakWordStyle) { mLineBreakWordStyle = lineBreakWordStyle; if (mLayout != null) { @@ -4945,8 +4956,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see PrecomputedText */ public @NonNull PrecomputedText.Params getTextMetricsParams() { + final boolean autoPhraseBreaking = + !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); return new PrecomputedText.Params(new TextPaint(mTextPaint), - LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle), + LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, + autoPhraseBreaking), getTextDirectionHeuristic(), mBreakStrategy, mHyphenationFrequency); } @@ -4966,6 +4981,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener LineBreakConfig lineBreakConfig = params.getLineBreakConfig(); mLineBreakStyle = lineBreakConfig.getLineBreakStyle(); mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle(); + mUserSpeficiedLineBreakwordStyle = true; if (mLayout != null) { nullLayouts(); requestLayout(); @@ -6502,10 +6518,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTextDir == null) { mTextDir = getTextDirectionHeuristic(); } + final boolean autoPhraseBreaking = + !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, mHyphenationFrequency, LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle)); + mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: throw new IllegalArgumentException( @@ -9391,6 +9410,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // TODO: code duplication with makeSingleLayout() if (mHintLayout == null) { + final boolean autoPhraseBreaking = + !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, mHint.length(), mTextPaint, hintWidth) .setAlignment(alignment) @@ -9403,7 +9425,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setJustificationMode(mJustificationMode) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle)); + mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); if (shouldEllipsize) { builder.setEllipsize(mEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -9507,6 +9529,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } if (result == null) { + final boolean autoPhraseBreaking = + !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 0, mTransformed.length(), mTextPaint, wantWidth) .setAlignment(alignment) @@ -9519,7 +9544,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setJustificationMode(mJustificationMode) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle)); + mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); if (shouldEllipsize) { builder.setEllipsize(effectiveEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -9877,7 +9902,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); - + final boolean autoPhraseBreaking = + !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); layoutBuilder.setAlignment(getLayoutAlignment()) .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) .setIncludePad(getIncludeFontPadding()) @@ -9888,7 +9915,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setTextDirection(getTextDirectionHeuristic()) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle)); + mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); final StaticLayout layout = layoutBuilder.build(); diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java index 0ebf03fab966..925da4968517 100644 --- a/core/tests/coretests/src/android/text/StaticLayoutTest.java +++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; +import android.graphics.text.LineBreakConfig; import android.os.LocaleList; import android.platform.test.annotations.Presubmit; import android.text.Layout.Alignment; @@ -925,4 +926,24 @@ public class StaticLayoutTest { assertEquals(0, layout.getHeight(true)); assertEquals(2, layout.getLineCount()); } + + @Test + public void testBuilder_autoPhraseBreaking() { + { + // setAutoPhraseBreaking true + LineBreakConfig lineBreakConfig = new LineBreakConfig.Builder() + .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_STYLE_NONE) + .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE) + .setAutoPhraseBreaking(true) + .build(); + final String text = "これが正解。"; + // Obtain. + StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, + text.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH); + builder.setLineBreakConfig(lineBreakConfig); + builder.build(); + assertEquals(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE, + builder.getLineBreakWordStyle()); + } + } } diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index d083e444e996..7ad9aecaf6a3 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -89,6 +89,11 @@ public final class LineBreakConfig { private @LineBreakWordStyle int mLineBreakWordStyle = LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; + // Whether or not enabling phrase breaking automatically. + // TODO(b/226012260): Remove this and add LINE_BREAK_WORD_STYLE_PHRASE_AUTO after + // the experiment. + private boolean mAutoPhraseBreaking = false; + /** * Builder constructor with line break parameters. */ @@ -117,13 +122,23 @@ public final class LineBreakConfig { return this; } + /** + * Enable or disable the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}. + * + * @hide + */ + public @NonNull Builder setAutoPhraseBreaking(boolean autoPhraseBreaking) { + mAutoPhraseBreaking = autoPhraseBreaking; + return this; + } + /** * Build the {@link LineBreakConfig} * * @return the LineBreakConfig instance. */ public @NonNull LineBreakConfig build() { - return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle); + return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking); } } @@ -143,6 +158,23 @@ public final class LineBreakConfig { .build(); } + /** + * Create the LineBreakConfig instance. + * + * @param lineBreakStyle the line break style for text wrapping. + * @param lineBreakWordStyle the line break word style for text wrapping. + * @return the {@link LineBreakConfig} instance. * + * @hide + */ + public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle, + @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) { + LineBreakConfig.Builder builder = new LineBreakConfig.Builder(); + return builder.setLineBreakStyle(lineBreakStyle) + .setLineBreakWordStyle(lineBreakWordStyle) + .setAutoPhraseBreaking(autoPhraseBreaking) + .build(); + } + /** @hide */ public static final LineBreakConfig NONE = new Builder().setLineBreakStyle(LINE_BREAK_STYLE_NONE) @@ -150,15 +182,17 @@ public final class LineBreakConfig { private final @LineBreakStyle int mLineBreakStyle; private final @LineBreakWordStyle int mLineBreakWordStyle; + private final boolean mAutoPhraseBreaking; /** * Constructor with the line break parameters. * Use the {@link LineBreakConfig.Builder} to create the LineBreakConfig instance. */ private LineBreakConfig(@LineBreakStyle int lineBreakStyle, - @LineBreakWordStyle int lineBreakWordStyle) { + @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) { mLineBreakStyle = lineBreakStyle; mLineBreakWordStyle = lineBreakWordStyle; + mAutoPhraseBreaking = autoPhraseBreaking; } /** @@ -179,6 +213,17 @@ public final class LineBreakConfig { return mLineBreakWordStyle; } + /** + * Used to identify if the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled. + * + * @return The result that records whether or not the automation of + * {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled. + * @hide + */ + public boolean getAutoPhraseBreaking() { + return mAutoPhraseBreaking; + } + @Override public boolean equals(Object o) { if (o == null) return false; @@ -186,7 +231,8 @@ public final class LineBreakConfig { if (!(o instanceof LineBreakConfig)) return false; LineBreakConfig that = (LineBreakConfig) o; return (mLineBreakStyle == that.mLineBreakStyle) - && (mLineBreakWordStyle == that.mLineBreakWordStyle); + && (mLineBreakWordStyle == that.mLineBreakWordStyle) + && (mAutoPhraseBreaking == that.mAutoPhraseBreaking); } @Override -- cgit v1.2.3-59-g8ed1b From f5733b0bdaa1bb50c1901b76641b715fe5d69f11 Mon Sep 17 00:00:00 2001 From: Michael Hoisie Date: Thu, 21 Apr 2022 22:15:04 +0000 Subject: Remove unused native method Font$Builder.nGetReleaseNativeFont The nGetReleaseNativeFont native method has been moved to the enclosing Font class. JNI registration also does not occur for Font$Builder.nGetReleaseNativeFont. Test: m -j framework Bug: 230032992 Change-Id: Ic987812cabe57c5c964b3cb8036cd7e297c11c36 --- graphics/java/android/graphics/fonts/Font.java | 2 -- 1 file changed, 2 deletions(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java index abd0be9c2872..28cc05162cbb 100644 --- a/graphics/java/android/graphics/fonts/Font.java +++ b/graphics/java/android/graphics/fonts/Font.java @@ -497,8 +497,6 @@ public final class Font { private static native long nBuild( long builderPtr, @NonNull ByteBuffer buffer, @NonNull String filePath, @NonNull String localeList, int weight, boolean italic, int ttcIndex); - @CriticalNative - private static native long nGetReleaseNativeFont(); @FastNative private static native long nClone(long fontPtr, long builderPtr, int weight, -- cgit v1.2.3-59-g8ed1b From 72c111de2c445e929ee1de29fd6727ec4225ef75 Mon Sep 17 00:00:00 2001 From: Louis Chang Date: Mon, 16 May 2022 10:29:25 +0800 Subject: Reduce RippleDrawable background opacity when no window focus Lower the background opacity when the window loses focus while the view still has focus, in order to prevent user confusion in a multi-window environment. Bug: 230355625 Test: atest RippleDrawableTest Change-Id: I59cd7faf35f05a12451f015f8ef2da47077b1bf2 --- core/api/test-current.txt | 5 ++++ .../android/graphics/drawable/RippleDrawable.java | 29 ++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) (limited to 'graphics/java/android') diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 2fff53791f52..814f15eb2a48 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1042,6 +1042,11 @@ package android.graphics.drawable { method public android.graphics.Xfermode getXfermode(); } + public class RippleDrawable extends android.graphics.drawable.LayerDrawable { + method public float getTargetBackgroundOpacity(); + method public void setBackgroundActive(boolean, boolean, boolean, boolean); + } + public class ShapeDrawable extends android.graphics.drawable.Drawable { method public void setXfermode(@Nullable android.graphics.Xfermode); } diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 74cad1aaa057..54c9f6222f7e 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -16,6 +16,8 @@ package android.graphics.drawable; +import android.annotation.TestApi; + import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.LOCAL_VARIABLE; import static java.lang.annotation.ElementType.METHOD; @@ -321,6 +323,7 @@ public class RippleDrawable extends LayerDrawable { boolean pressed = false; boolean focused = false; boolean hovered = false; + boolean windowFocused = false; for (int state : stateSet) { if (state == R.attr.state_enabled) { @@ -331,10 +334,12 @@ public class RippleDrawable extends LayerDrawable { pressed = true; } else if (state == R.attr.state_hovered) { hovered = true; + } else if (state == R.attr.state_window_focused) { + windowFocused = true; } } setRippleActive(enabled && pressed); - setBackgroundActive(hovered, focused, pressed); + setBackgroundActive(hovered, focused, pressed, windowFocused); return changed; } @@ -358,7 +363,10 @@ public class RippleDrawable extends LayerDrawable { } } - private void setBackgroundActive(boolean hovered, boolean focused, boolean pressed) { + /** @hide */ + @TestApi + public void setBackgroundActive(boolean hovered, boolean focused, boolean pressed, + boolean windowFocused) { if (mState.mRippleStyle == STYLE_SOLID) { if (mBackground == null && (hovered || focused)) { mBackground = new RippleBackground(this, mHotspotBounds, isBounded()); @@ -370,7 +378,7 @@ public class RippleDrawable extends LayerDrawable { } else { if (focused || hovered) { if (!pressed) { - enterPatternedBackgroundAnimation(focused, hovered); + enterPatternedBackgroundAnimation(focused, hovered, windowFocused); } } else { exitPatternedBackgroundAnimation(); @@ -840,9 +848,20 @@ public class RippleDrawable extends LayerDrawable { invalidateSelf(false); } - private void enterPatternedBackgroundAnimation(boolean focused, boolean hovered) { + /** @hide */ + @TestApi + public float getTargetBackgroundOpacity() { + return mTargetBackgroundOpacity; + } + + private void enterPatternedBackgroundAnimation(boolean focused, boolean hovered, + boolean windowFocused) { mBackgroundOpacity = 0; - mTargetBackgroundOpacity = focused ? .6f : hovered ? .2f : 0f; + if (focused) { + mTargetBackgroundOpacity = windowFocused ? .6f : .2f; + } else { + mTargetBackgroundOpacity = hovered ? .2f : 0f; + } if (mBackgroundAnimation != null) mBackgroundAnimation.cancel(); // after cancel mRunBackgroundAnimation = true; -- cgit v1.2.3-59-g8ed1b From 0547bffe400ecc877e22701d6bd7f6af33c29b4d Mon Sep 17 00:00:00 2001 From: Brian Osman Date: Wed, 29 Jun 2022 20:34:19 +0000 Subject: Fix example AGSL code in the javadoc comments Bug: b/237562620 Change-Id: I5f562b70d04bc86779190002a2640fb51dc00531 --- graphics/java/android/graphics/RuntimeShader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java index 6abe34b1d675..9c36fc36474c 100644 --- a/graphics/java/android/graphics/RuntimeShader.java +++ b/graphics/java/android/graphics/RuntimeShader.java @@ -214,7 +214,7 @@ import libcore.util.NativeAllocationRegistry; * uniform shader myShader; * vec4 main(vec2 canvas_coordinates) { * // swap the red and blue color channels when sampling from myShader - * return myShader.sample(canvas_coordinates).bgra; + * return myShader.eval(canvas_coordinates).bgra; * } * *

After creating a {@link RuntimeShader} with that program the shader uniform can -- cgit v1.2.3-59-g8ed1b From c8abba490763142b12f034a7a8578ab33959794f Mon Sep 17 00:00:00 2001 From: Haoyu Zhang Date: Thu, 23 Jun 2022 10:29:24 -0700 Subject: Introduce TextLine#measureAllbounds This new method computes the horizontal character bounds by making use of the character advances returned from native layer. It'll be used in TextView to populate character bounds, and it's much efficent compared to calling getPrimaryHorizontal for each character. Bug: 233922052 Test: atest android.text.TextLineTest Change-Id: Icdd53e61e2d1513b2231affb19bb00ea5d938d48 --- core/api/current.txt | 2 + core/java/android/text/TextLine.java | 200 ++++++++++++--- .../coretests/src/android/text/TextLineTest.java | 268 ++++++++++++++++++++- graphics/java/android/graphics/Paint.java | 125 ++++++++++ libs/hwui/jni/Paint.cpp | 248 ++++++++++--------- 5 files changed, 698 insertions(+), 145 deletions(-) (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index bb3264701ca5..8d703d048e3b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -14928,6 +14928,8 @@ package android.graphics { method public android.graphics.PathEffect getPathEffect(); method public float getRunAdvance(char[], int, int, int, int, boolean, int); method public float getRunAdvance(CharSequence, int, int, int, int, boolean, int); + method public float getRunCharacterAdvance(@NonNull char[], int, int, int, int, boolean, int, @Nullable float[], int); + method public float getRunCharacterAdvance(@NonNull CharSequence, int, int, int, int, boolean, int, @Nullable float[], int); method public android.graphics.Shader getShader(); method @ColorInt public int getShadowLayerColor(); method @ColorLong public long getShadowLayerColorLong(); diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index e39231c9d64b..7394c22ee93f 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -408,14 +408,14 @@ public class TextLine { final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; if (targetIsInThisSegment && sameDirection) { - return h + measureRun(segStart, offset, j, runIsRtl, fmi); + return h + measureRun(segStart, offset, j, runIsRtl, fmi, null, 0); } - final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi); + final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, null, 0); h += sameDirection ? segmentWidth : -segmentWidth; if (targetIsInThisSegment) { - return h + measureRun(segStart, offset, j, runIsRtl, null); + return h + measureRun(segStart, offset, j, runIsRtl, null, null, 0); } if (j != runLimit) { // charAt(j) == TAB_CHAR @@ -436,6 +436,116 @@ public class TextLine { return h; } + /** + * Return the signed horizontal bounds of the characters in the line. + * + * The length of the returned array equals to 2 * mLen. The left bound of the i th character + * is stored at index 2 * i. And the right bound of the i th character is stored at index + * (2 * i + 1). + * + * Check the following examples. LX(e.g. L0, L1, ...) denotes a character which has LTR BiDi + * property. On the other hand, RX(e.g. R0, R1, ...) denotes a character which has RTL BiDi + * property. Assuming all character has 1em width. + * + * Example 1: All LTR chars within LTR context + * Input Text (logical) : L0 L1 L2 L3 + * Input Text (visual) : L0 L1 L2 L3 + * Output : [0em, 1em, 1em, 2em, 2em, 3em, 3em, 4em] + * + * Example 2: All RTL chars within RTL context. + * Input Text (logical) : R0 R1 R2 R3 + * Input Text (visual) : R3 R2 R1 R0 + * Output : [-1em, 0em, -2em, -1em, -3em, -2em, -4em, -3em] + + * + * Example 3: BiDi chars within LTR context. + * Input Text (logical) : L0 L1 R2 R3 L4 L5 + * Input Text (visual) : L0 L1 R3 R2 L4 L5 + * Output : [0em, 1em, 1em, 2em, 3em, 4em, 2em, 3em, 4em, 5em, 5em, 6em] + + * + * Example 4: BiDi chars within RTL context. + * Input Text (logical) : L0 L1 R2 R3 L4 L5 + * Input Text (visual) : L4 L5 R3 R2 L0 L1 + * Output : [-2em, -1em, -1em, 0em, -3em, -2em, -4em, -3em, -6em, -5em, -5em, -4em] + * + * @param bounds the array to receive the character bounds data. Its length should be at least + * 2 times of the line length. + * @param advances the array to receive the character advance data, nullable. If provided, its + * length should be equal or larger than the line length. + * + * @throws IllegalArgumentException if the given {@code bounds} is null. + * @throws IndexOutOfBoundsException if the given {@code bounds} or {@code advances} doesn't + * have enough space to hold the result. + */ + public void measureAllBounds(@NonNull float[] bounds, @Nullable float[] advances) { + if (bounds == null) { + throw new IllegalArgumentException("bounds can't be null"); + } + if (bounds.length < 2 * mLen) { + throw new IndexOutOfBoundsException("bounds doesn't have enough space to receive the " + + "result, needed: " + (2 * mLen) + " had: " + bounds.length); + } + if (advances == null) { + advances = new float[mLen]; + } + if (advances.length < mLen) { + throw new IndexOutOfBoundsException("advance doesn't have enough space to receive the " + + "result, needed: " + mLen + " had: " + advances.length); + } + float h = 0; + for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) { + final int runStart = mDirections.getRunStart(runIndex); + if (runStart > mLen) break; + final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen); + final boolean runIsRtl = mDirections.isRunRtl(runIndex); + + int segStart = runStart; + for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { + if (j == runLimit || charAt(j) == TAB_CHAR) { + final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; + + final float segmentWidth = + measureRun(segStart, j, j, runIsRtl, null, advances, segStart); + + final float oldh = h; + h += sameDirection ? segmentWidth : -segmentWidth; + float currh = sameDirection ? oldh : h; + for (int offset = segStart; offset < j && offset < mLen; ++offset) { + if (runIsRtl) { + bounds[2 * offset + 1] = currh; + currh -= advances[offset]; + bounds[2 * offset] = currh; + } else { + bounds[2 * offset] = currh; + currh += advances[offset]; + bounds[2 * offset + 1] = currh; + } + } + + if (j != runLimit) { // charAt(j) == TAB_CHAR + final float leftX; + final float rightX; + if (runIsRtl) { + rightX = h; + h = mDir * nextTab(h * mDir); + leftX = h; + } else { + leftX = h; + h = mDir * nextTab(h * mDir); + rightX = h; + } + bounds[2 * j] = leftX; + bounds[2 * j + 1] = rightX; + advances[j] = rightX - leftX; + } + + segStart = j + 1; + } + } + } + } + /** * @see #measure(int, boolean, FontMetricsInt) * @return The measure results for all possible offsets @@ -464,15 +574,15 @@ public class TextLine { if (j == runLimit || charAt(j) == TAB_CHAR) { final float oldh = h; final boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; - final float w = measureRun(segStart, j, j, runIsRtl, fmi); + final float w = measureRun(segStart, j, j, runIsRtl, fmi, null, 0); h += advance ? w : -w; final float baseh = advance ? oldh : h; FontMetricsInt crtfmi = advance ? fmi : null; for (int offset = segStart; offset <= j && offset <= mLen; ++offset) { if (target[offset] >= segStart && target[offset] < j) { - measurement[offset] = - baseh + measureRun(segStart, offset, j, runIsRtl, crtfmi); + measurement[offset] = baseh + + measureRun(segStart, offset, j, runIsRtl, crtfmi, null, 0); } } @@ -518,14 +628,14 @@ public class TextLine { boolean needWidth) { if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { - float w = -measureRun(start, limit, limit, runIsRtl, null); + float w = -measureRun(start, limit, limit, runIsRtl, null, null, 0); handleRun(start, limit, limit, runIsRtl, c, null, x + w, top, - y, bottom, null, false); + y, bottom, null, false, null, 0); return w; } return handleRun(start, limit, limit, runIsRtl, c, null, x, top, - y, bottom, null, needWidth); + y, bottom, null, needWidth, null, 0); } /** @@ -538,12 +648,15 @@ public class TextLine { * @param runIsRtl true if the run is right-to-left * @param fmi receives metrics information about the requested * run, can be null. + * @param advances receives the advance information about the requested run, can be null. + * @param advancesIndex the start index to fill in the advance information. * @return the signed width from the start of the run to the leading edge * of the character at offset, based on the run (not paragraph) direction */ private float measureRun(int start, int offset, int limit, boolean runIsRtl, - FontMetricsInt fmi) { - return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true); + @Nullable FontMetricsInt fmi, @Nullable float[] advances, int advancesIndex) { + return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true, + advances, advancesIndex); } /** @@ -562,13 +675,14 @@ public class TextLine { int limit, boolean runIsRtl, float x, boolean needWidth) { if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { - float w = -measureRun(start, limit, limit, runIsRtl, null); - handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, false); + float w = -measureRun(start, limit, limit, runIsRtl, null, null, 0); + handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, + false, null, 0); return w; } return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null, - needWidth); + needWidth, null, 0); } @@ -908,14 +1022,16 @@ public class TextLine { } private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd, - boolean runIsRtl, int offset) { + boolean runIsRtl, int offset, @Nullable float[] advances, int advancesIndex) { if (mCharsValid) { - return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset); + return wp.getRunCharacterAdvance(mChars, start, end, contextStart, contextEnd, + runIsRtl, offset, advances, advancesIndex); } else { final int delta = mStart; - if (mComputed == null) { - return wp.getRunAdvance(mText, delta + start, delta + end, - delta + contextStart, delta + contextEnd, runIsRtl, delta + offset); + if (mComputed == null || advances != null) { + return wp.getRunCharacterAdvance(mText, delta + start, delta + end, + delta + contextStart, delta + contextEnd, runIsRtl, + delta + offset, advances, advancesIndex); } else { return mComputed.getWidth(start + delta, end + delta); } @@ -940,6 +1056,8 @@ public class TextLine { * @param needWidth true if the width of the run is needed * @param offset the offset for the purpose of measuring * @param decorations the list of locations and paremeters for drawing decorations + * @param advances receives the advance information about the requested run, can be null. + * @param advancesIndex the start index to fill in the advance information. * @return the signed width of the run based on the run direction; only * valid if needWidth is true */ @@ -947,7 +1065,8 @@ public class TextLine { int contextStart, int contextEnd, boolean runIsRtl, Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom, FontMetricsInt fmi, boolean needWidth, int offset, - @Nullable ArrayList decorations) { + @Nullable ArrayList decorations, + @Nullable float[] advances, int advancesIndex) { if (mIsJustifying) { wp.setWordSpacing(mAddedWidthForJustify); @@ -967,7 +1086,8 @@ public class TextLine { final int numDecorations = decorations == null ? 0 : decorations.size(); if (needWidth || ((c != null || consumer != null) && (wp.bgColor != 0 || numDecorations != 0 || runIsRtl))) { - totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset); + totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset, + advances, advancesIndex); } final float leftX, rightX; @@ -1009,10 +1129,10 @@ public class TextLine { final int decorationStart = Math.max(info.start, start); final int decorationEnd = Math.min(info.end, offset); - float decorationStartAdvance = getRunAdvance( - wp, start, end, contextStart, contextEnd, runIsRtl, decorationStart); - float decorationEndAdvance = getRunAdvance( - wp, start, end, contextStart, contextEnd, runIsRtl, decorationEnd); + float decorationStartAdvance = getRunAdvance(wp, start, end, contextStart, + contextEnd, runIsRtl, decorationStart, null, 0); + float decorationEndAdvance = getRunAdvance(wp, start, end, contextStart, + contextEnd, runIsRtl, decorationEnd, null, 0); final float decorationXLeft, decorationXRight; if (runIsRtl) { decorationXLeft = rightX - decorationEndAdvance; @@ -1179,19 +1299,27 @@ public class TextLine { * @param bottom the bottom of the line * @param fmi receives metrics information, can be null * @param needWidth true if the width is required + * @param advances receives the advance information about the requested run, can be null. + * @param advancesIndex the start index to fill in the advance information. * @return the signed width of the run based on the run direction; only * valid if needWidth is true */ private float handleRun(int start, int measureLimit, int limit, boolean runIsRtl, Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, - int bottom, FontMetricsInt fmi, boolean needWidth) { + int bottom, FontMetricsInt fmi, boolean needWidth, + @Nullable float[] advances, int advancesIndex) { if (measureLimit < start || measureLimit > limit) { throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of " + "start (" + start + ") and limit (" + limit + ") bounds"); } + if (advances != null && advances.length - advancesIndex < measureLimit - start) { + throw new IndexOutOfBoundsException("advances doesn't have enough space to receive the " + + "result"); + } + // Case of an empty line, make sure we update fmi according to mPaint if (start == measureLimit) { final TextPaint wp = mWorkPaint; @@ -1218,7 +1346,7 @@ public class TextLine { wp.setStartHyphenEdit(adjustStartHyphenEdit(start, wp.getStartHyphenEdit())); wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit())); return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top, - y, bottom, fmi, needWidth, measureLimit, null); + y, bottom, fmi, needWidth, measureLimit, null, advances, advancesIndex); } // Shaping needs to take into account context up to metric boundaries, @@ -1257,8 +1385,16 @@ public class TextLine { } if (replacement != null) { - x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y, - bottom, fmi, needWidth || mlimit < measureLimit); + final float width = handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, + x, top, y, bottom, fmi, needWidth || mlimit < measureLimit); + x += width; + if (advances != null) { + // For replacement, the entire width is assigned to the first character. + advances[advancesIndex + i - start] = runIsRtl ? -width : width; + for (int j = i + 1; j < mlimit; ++j) { + advances[advancesIndex + j - start] = 0.0f; + } + } continue; } @@ -1300,7 +1436,8 @@ public class TextLine { adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit())); x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, - Math.min(activeEnd, mlimit), mDecorations); + Math.min(activeEnd, mlimit), mDecorations, + advances, advancesIndex + activeStart - start); activeStart = j; activePaint.set(wp); @@ -1327,7 +1464,8 @@ public class TextLine { adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit())); x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit, - Math.min(activeEnd, mlimit), mDecorations); + Math.min(activeEnd, mlimit), mDecorations, + advances, advancesIndex + activeStart - start); } return x - originalX; diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java index 412d6ec975ac..e3bcc8d2c200 100644 --- a/core/tests/coretests/src/android/text/TextLineTest.java +++ b/core/tests/coretests/src/android/text/TextLineTest.java @@ -26,6 +26,7 @@ import android.graphics.Paint; import android.graphics.Typeface; import android.platform.test.annotations.Presubmit; import android.text.Layout.TabStops; +import android.text.style.AbsoluteSizeSpan; import android.text.style.ReplacementSpan; import android.text.style.TabStopSpan; @@ -97,7 +98,7 @@ public class TextLineTest { InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets(), "fonts/StaticLayoutLineBreakingTestFont.ttf"); - private TextLine getTextLine(String str, TextPaint paint, TabStops tabStops) { + private TextLine getTextLine(CharSequence str, TextPaint paint, TabStops tabStops) { Layout layout = StaticLayout.Builder.obtain(str, 0, str.length(), paint, Integer.MAX_VALUE) .build(); @@ -109,7 +110,7 @@ public class TextLineTest { return tl; } - private TextLine getTextLine(String str, TextPaint paint) { + private TextLine getTextLine(CharSequence str, TextPaint paint) { return getTextLine(str, paint, null); } @@ -316,14 +317,275 @@ public class TextLineTest { assertTrue(span.mIsUsed); } + @Test + public void testMeasureAllBounds_LTR() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("IIIIIV", paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 20.0f, 20.0f, 30.0f, 30.0f, 40.0f, + 40.0f, 50.0f, 50.0f, 100.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 10.0f, 10.0f, 10.0f, 50.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_LTR_StyledText() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + SpannableString text = new SpannableString("IIIIIV"); + text.setSpan(new AbsoluteSizeSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextLine tl = getTextLine(text, paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 15.0f, 15.0f, 20.0f, 20.0f, 30.0f, + 30.0f, 40.0f, 40.0f, 90.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 5.0f, 5.0f, 10.0f, 10.0f, 50.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_RTL() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("\u05D0\u05D0\u05D0\u05D0\u05D0\u05D1", paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {-10.0f, 0.0f, -20.0f, -10.0f, -30.0f, -20.0f, -40.0f, -30.0f, + -50.0f, -40.0f, -100.0f, -50.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 10.0f, 10.0f, 10.0f, 50.0f}, advances, 0.0f); + } + + + @Test + public void testMeasureAllBounds_RTL_StyledText() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + SpannableString text = new SpannableString("\u05D0\u05D0\u05D0\u05D0\u05D0\u05D1"); + text.setSpan(new AbsoluteSizeSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextLine tl = getTextLine(text, paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {-10.0f, 0.0f, -15.0f, -10.0f, -20.0f, -15.0f, + -30.0f, -20.0f, -40.0f, -30.0f, -90.0f, -40.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 5.0f, 5.0f, 10.0f, 10.0f, 50.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_BiDi() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("II\u05D0\u05D0II", paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 20.0f, 30.0f, 40.0f, 20.0f, 30.0f, + 40.0f, 50.0f, 50.0f, 60.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 10.0f, 10.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_BiDi2() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("I" + RLI + "I\u05D0\u05D0" + PDI + "I", paint); + float[] bounds = new float[14]; + float[] advances = new float[7]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 10.0f, 30.0f, 40.0f, 20.0f, 30.0f, + 10.0f, 20.0f, 40.0f, 40.0f, 40.0f, 50.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 0.0f, 10.0f, 10.0f, 10.0f, 0.0f, 10.0f}, advances, + 0.0f); + } + + @Test + public void testMeasureAllBounds_BiDi3() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("\u05D0" + LRI + "\u05D0II" + PDI + "\u05D0", paint); + float[] bounds = new float[14]; + float[] advances = new float[7]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {-10.0f, 0.0f, -10.0f, -10.0f, -40.0f, -30.0f, + -30.0f, -20.0f, -20.0f, -10.0f, -40.0f, -40.0f, -50.0f, -40.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 0.0f, 10.0f, 10.0f, 10.0f, 0.0f, 10.0f}, advances, + 0.0f); + } + + @Test + public void testMeasureAllBounds_styled_BiDi() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + SpannableString text = new SpannableString("II\u05D0\u05D0II"); + text.setSpan(new AbsoluteSizeSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextLine tl = getTextLine(text, paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 15.0f, 25.0f, 30.0f, + 15.0f, 25.0f, 30.0f, 40.0f, 40.0f, 50.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 5.0f, 5.0f, 10.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_Tab_LTR() { + final Object[] spans = { new TabStopSpan.Standard(100) }; + final TabStops stops = new TabStops(100, spans); + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("II\tII", paint, stops); + float[] bounds = new float[10]; + float[] advances = new float[5]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 20.0f, 20.0f, 100.0f, 100.0f, 110.0f, + 110.0f, 120.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 80.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_Tab_RTL() { + final Object[] spans = { new TabStopSpan.Standard(100) }; + final TabStops stops = new TabStops(100, spans); + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("\u05D0\u05D0\t\u05D0\u05D0", paint, stops); + float[] bounds = new float[10]; + float[] advances = new float[5]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {-10.0f, 0.0f, -20.0f, -10.0f, -100.0f, -20.0f, + -110.0f, -100.0f, -120.0f, -110.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 80.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_Tab_BiDi() { + final Object[] spans = { new TabStopSpan.Standard(100) }; + final TabStops stops = new TabStops(100, spans); + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("I\u05D0\tI\u05D0", paint, stops); + float[] bounds = new float[10]; + float[] advances = new float[5]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 20.0f, 20.0f, 100.0f, + 100.0f, 110.0f, 110.0f, 120.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 80.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_Tab_BiDi2() { + final Object[] spans = { new TabStopSpan.Standard(100) }; + final TabStops stops = new TabStops(100, spans); + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + TextLine tl = getTextLine("\u05D0I\t\u05D0I", paint, stops); + float[] bounds = new float[10]; + float[] advances = new float[5]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {-10.0f, 0.0f, -20.0f, -10.0f, -100.0f, -20.0f, + -110.0f, -100.0f, -120.0f, -110.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 10.0f, 80.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_replacement_LTR() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + SpannableString text = new SpannableString("IIIII"); + text.setSpan(new TestReplacementSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextLine tl = getTextLine(text, paint); + float[] bounds = new float[10]; + float[] advances = new float[5]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 15.0f, 15.0f, 15.0f, + 15.0f, 25.0f, 25.0f, 35.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 5.0f, 0.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_replacement_RTL() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + SpannableString text = new SpannableString("\u05D0\u05D0\u05D0\u05D0\u05D0"); + text.setSpan(new TestReplacementSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextLine tl = getTextLine(text, paint); + float[] bounds = new float[10]; + float[] advances = new float[5]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {-10.0f, 0.0f, -15.0f, -10.0f, -15.0f, -15.0f, + -25.0f, -15.0f, -35.0f, -25.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 5.0f, 0.0f, 10.0f, 10.0f}, advances, 0.0f); + } + + @Test + public void testMeasureAllBounds_replacement_BiDi() { + final TextPaint paint = new TextPaint(); + paint.setTypeface(TYPEFACE); + paint.setTextSize(10.0f); // make 1em = 10px + + SpannableString text = new SpannableString("II\u05D0\u05D0II"); + text.setSpan(new TestReplacementSpan(5), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + TextLine tl = getTextLine(text, paint); + float[] bounds = new float[12]; + float[] advances = new float[6]; + tl.measureAllBounds(bounds, advances); + assertArrayEquals(new float[] {0.0f, 10.0f, 10.0f, 15.0f, 15.0f, 15.0f, + 15.0f, 25.0f, 25.0f, 35.0f, 35.0f, 45.0f}, bounds, 0.0f); + assertArrayEquals(new float[] {10.0f, 5.0f, 0.0f, 10.0f, 10.0f, 10.0f}, advances, 0.0f); + } + private static class TestReplacementSpan extends ReplacementSpan { boolean mIsUsed; + private final int mWidth; + + TestReplacementSpan() { + mWidth = 0; + } + + TestReplacementSpan(int width) { + mWidth = width; + } @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { mIsUsed = true; - return 0; + return mWidth; } @Override diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 451b99ea7550..1a80ab308bb5 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -3136,6 +3136,128 @@ public class Paint { return result; } + + /** + * Measure the advance of each character within a run of text and also return the cursor + * position within the run. + * + * @see #getRunAdvance(char[], int, int, int, int, boolean, int) for more details. + * + * @param text the text to measure. Cannot be null. + * @param start the index of the start of the range to measure + * @param end the index + 1 of the end of the range to measure + * @param contextStart the index of the start of the shaping context + * @param contextEnd the index + 1 of the end of the shaping context + * @param isRtl whether the run is in RTL direction + * @param offset index of caret position + * @param advances the array that receives the computed character advances + * @param advancesIndex the start index from which the advances array is filled + * @return width measurement between start and offset + * @throws IndexOutOfBoundsException if a) contextStart or contextEnd is out of array's range + * or contextStart is larger than contextEnd, + * b) start or end is not within the range [contextStart, contextEnd), or start is larger than + * end, + * c) offset is not within the range [start, end), + * d) advances.length - advanceIndex is smaller than the length of the run, which equals to + * end - start. + * + */ + public float getRunCharacterAdvance(@NonNull char[] text, int start, int end, int contextStart, + int contextEnd, boolean isRtl, int offset, + @Nullable float[] advances, int advancesIndex) { + if (text == null) { + throw new IllegalArgumentException("text cannot be null"); + } + if (contextStart < 0 || contextEnd > text.length) { + throw new IndexOutOfBoundsException("Invalid Context Range: " + contextStart + ", " + + contextEnd + " must be in 0, " + text.length); + } + + if (start < contextStart || contextEnd < end) { + throw new IndexOutOfBoundsException("Invalid start/end range: " + start + ", " + end + + " must be in " + contextStart + ", " + contextEnd); + } + + if (offset < start || end < offset) { + throw new IndexOutOfBoundsException("Invalid offset position: " + offset + + " must be in " + start + ", " + end); + } + + if (advances != null && advances.length < advancesIndex - start + end) { + throw new IndexOutOfBoundsException("Given array doesn't have enough space to receive " + + "the result, advances.length: " + advances.length + " advanceIndex: " + + advancesIndex + " needed space: " + (offset - start)); + } + + if (end == start) { + return 0.0f; + } + + return nGetRunCharacterAdvance(mNativePaint, text, start, end, contextStart, contextEnd, + isRtl, offset, advances, advancesIndex); + } + + /** + * @see #getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int) + * + * @param text the text to measure. Cannot be null. + * @param start the index of the start of the range to measure + * @param end the index + 1 of the end of the range to measure + * @param contextStart the index of the start of the shaping context + * @param contextEnd the index + 1 of the end of the shaping context + * @param isRtl whether the run is in RTL direction + * @param offset index of caret position + * @param advances the array that receives the computed character advances + * @param advancesIndex the start index from which the advances array is filled + * @return width measurement between start and offset + * @throws IndexOutOfBoundsException if a) contextStart or contextEnd is out of array's range + * or contextStart is larger than contextEnd, + * b) start or end is not within the range [contextStart, contextEnd), or end is larger than + * start, + * c) offset is not within the range [start, end), + * d) advances.length - advanceIndex is smaller than the run length, which equals to + * end - start. + */ + public float getRunCharacterAdvance(@NonNull CharSequence text, int start, int end, + int contextStart, int contextEnd, boolean isRtl, int offset, + @Nullable float[] advances, int advancesIndex) { + if (text == null) { + throw new IllegalArgumentException("text cannot be null"); + } + if (contextStart < 0 || contextEnd > text.length()) { + throw new IndexOutOfBoundsException("Invalid Context Range: " + contextStart + ", " + + contextEnd + " must be in 0, " + text.length()); + } + + if (start < contextStart || contextEnd < end) { + throw new IndexOutOfBoundsException("Invalid start/end range: " + start + ", " + end + + " must be in " + contextStart + ", " + contextEnd); + } + + if (offset < start || end < offset) { + throw new IndexOutOfBoundsException("Invalid offset position: " + offset + + " must be in " + start + ", " + end); + } + + if (advances != null && advances.length < advancesIndex - start + end) { + throw new IndexOutOfBoundsException("Given array doesn't have enough space to receive " + + "the result, advances.length: " + advances.length + " advanceIndex: " + + advancesIndex + " needed space: " + (offset - start)); + } + + if (end == start) { + return 0.0f; + } + + char[] buf = TemporaryBuffer.obtain(contextEnd - contextStart); + TextUtils.getChars(text, contextStart, contextEnd, buf, 0); + final float result = getRunCharacterAdvance(buf, start - contextStart, end - contextStart, + 0, contextEnd - contextStart, isRtl, offset - contextStart, + advances, advancesIndex); + TemporaryBuffer.recycle(buf); + return result; + } + /** * Get the character offset within the string whose position is closest to the specified * horizontal position. @@ -3247,6 +3369,9 @@ public class Paint { private static native boolean nHasGlyph(long paintPtr, int bidiFlags, String string); private static native float nGetRunAdvance(long paintPtr, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset); + private static native float nGetRunCharacterAdvance(long paintPtr, char[] text, int start, + int end, int contextStart, int contextEnd, boolean isRtl, int offset, float[] advances, + int advancesIndex); private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance); private static native void nGetFontMetricsIntForText(long paintPtr, char[] text, diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index 0aa14655725c..ed453b158579 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -497,16 +497,29 @@ namespace PaintGlue { return true; } - static jfloat doRunAdvance(const Paint* paint, const Typeface* typeface, const jchar buf[], - jint start, jint count, jint bufSize, jboolean isRtl, jint offset) { + static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface, + const jchar buf[], jint start, jint count, jint bufSize, + jboolean isRtl, jint offset, jfloatArray advances, + jint advancesIndex) { + if (advances) { + size_t advancesLength = env->GetArrayLength(advances); + if ((size_t)(count + advancesIndex) > advancesLength) { + doThrowAIOOBE(env); + return 0; + } + } minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; - if (offset == start + count) { + if (offset == start + count && advances == nullptr) { return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, nullptr); } std::unique_ptr advancesArray(new float[count]); MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, advancesArray.get()); + + if (advances) { + env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get()); + } return minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset); } @@ -515,9 +528,23 @@ namespace PaintGlue { const Paint* paint = reinterpret_cast(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); ScopedCharArrayRO textArray(env, text); - jfloat result = doRunAdvance(paint, typeface, textArray.get() + contextStart, - start - contextStart, end - start, contextEnd - contextStart, isRtl, - offset - contextStart); + jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart, + start - contextStart, end - start, contextEnd - contextStart, + isRtl, offset - contextStart, nullptr, 0); + return result; + } + + static jfloat getRunCharacterAdvance___CIIIIZI_FI_F(JNIEnv* env, jclass, jlong paintHandle, + jcharArray text, jint start, jint end, + jint contextStart, jint contextEnd, + jboolean isRtl, jint offset, + jfloatArray advances, jint advancesIndex) { + const Paint* paint = reinterpret_cast(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + ScopedCharArrayRO textArray(env, text); + jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart, + start - contextStart, end - start, contextEnd - contextStart, + isRtl, offset - contextStart, advances, advancesIndex); return result; } @@ -1034,113 +1061,112 @@ namespace PaintGlue { }; // namespace PaintGlue static const JNINativeMethod methods[] = { - {"nGetNativeFinalizer", "()J", (void*) PaintGlue::getNativeFinalizer}, - {"nInit","()J", (void*) PaintGlue::init}, - {"nInitWithPaint","(J)J", (void*) PaintGlue::initWithPaint}, - {"nBreakText","(J[CIIFI[F)I", (void*) PaintGlue::breakTextC}, - {"nBreakText","(JLjava/lang/String;ZFI[F)I", (void*) PaintGlue::breakTextS}, - {"nGetTextAdvances","(J[CIIIII[FI)F", - (void*) PaintGlue::getTextAdvances___CIIIII_FI}, - {"nGetTextAdvances","(JLjava/lang/String;IIIII[FI)F", - (void*) PaintGlue::getTextAdvances__StringIIIII_FI}, - - {"nGetTextRunCursor", "(J[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C}, - {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I", - (void*) PaintGlue::getTextRunCursor__String}, - {"nGetTextPath", "(JI[CIIFFJ)V", (void*) PaintGlue::getTextPath___C}, - {"nGetTextPath", "(JILjava/lang/String;IIFFJ)V", (void*) PaintGlue::getTextPath__String}, - {"nGetStringBounds", "(JLjava/lang/String;IIILandroid/graphics/Rect;)V", - (void*) PaintGlue::getStringBounds }, - {"nGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V", - (void*) PaintGlue::getCharArrayBounds }, - {"nHasGlyph", "(JILjava/lang/String;)Z", (void*) PaintGlue::hasGlyph }, - {"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F}, - {"nGetOffsetForAdvance", "(J[CIIIIZF)I", - (void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I}, - {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V", - (void*)PaintGlue::getFontMetricsIntForText___C}, - {"nGetFontMetricsIntForText", - "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V", - (void*)PaintGlue::getFontMetricsIntForText___String}, - - // --------------- @FastNative ---------------------- - - {"nSetTextLocales","(JLjava/lang/String;)I", (void*) PaintGlue::setTextLocales}, - {"nSetFontFeatureSettings","(JLjava/lang/String;)V", - (void*) PaintGlue::setFontFeatureSettings}, - {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F", - (void*)PaintGlue::getFontMetrics}, - {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I", - (void*)PaintGlue::getFontMetricsInt}, - - // --------------- @CriticalNative ------------------ - - {"nReset","(J)V", (void*) PaintGlue::reset}, - {"nSet","(JJ)V", (void*) PaintGlue::assign}, - {"nGetFlags","(J)I", (void*) PaintGlue::getFlags}, - {"nSetFlags","(JI)V", (void*) PaintGlue::setFlags}, - {"nGetHinting","(J)I", (void*) PaintGlue::getHinting}, - {"nSetHinting","(JI)V", (void*) PaintGlue::setHinting}, - {"nSetAntiAlias","(JZ)V", (void*) PaintGlue::setAntiAlias}, - {"nSetSubpixelText","(JZ)V", (void*) PaintGlue::setSubpixelText}, - {"nSetLinearText","(JZ)V", (void*) PaintGlue::setLinearText}, - {"nSetUnderlineText","(JZ)V", (void*) PaintGlue::setUnderlineText}, - {"nSetStrikeThruText","(JZ)V", (void*) PaintGlue::setStrikeThruText}, - {"nSetFakeBoldText","(JZ)V", (void*) PaintGlue::setFakeBoldText}, - {"nSetFilterBitmap","(JZ)V", (void*) PaintGlue::setFilterBitmap}, - {"nSetDither","(JZ)V", (void*) PaintGlue::setDither}, - {"nGetStyle","(J)I", (void*) PaintGlue::getStyle}, - {"nSetStyle","(JI)V", (void*) PaintGlue::setStyle}, - {"nSetColor","(JI)V", (void*) PaintGlue::setColor}, - {"nSetColor","(JJJ)V", (void*) PaintGlue::setColorLong}, - {"nSetAlpha","(JI)V", (void*) PaintGlue::setAlpha}, - {"nGetStrokeWidth","(J)F", (void*) PaintGlue::getStrokeWidth}, - {"nSetStrokeWidth","(JF)V", (void*) PaintGlue::setStrokeWidth}, - {"nGetStrokeMiter","(J)F", (void*) PaintGlue::getStrokeMiter}, - {"nSetStrokeMiter","(JF)V", (void*) PaintGlue::setStrokeMiter}, - {"nGetStrokeCap","(J)I", (void*) PaintGlue::getStrokeCap}, - {"nSetStrokeCap","(JI)V", (void*) PaintGlue::setStrokeCap}, - {"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin}, - {"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin}, - {"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath}, - {"nSetShader","(JJ)J", (void*) PaintGlue::setShader}, - {"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter}, - {"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode}, - {"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect}, - {"nSetMaskFilter","(JJ)J", (void*) PaintGlue::setMaskFilter}, - {"nSetTypeface","(JJ)V", (void*) PaintGlue::setTypeface}, - {"nGetTextAlign","(J)I", (void*) PaintGlue::getTextAlign}, - {"nSetTextAlign","(JI)V", (void*) PaintGlue::setTextAlign}, - {"nSetTextLocalesByMinikinLocaleListId","(JI)V", - (void*) PaintGlue::setTextLocalesByMinikinLocaleListId}, - {"nIsElegantTextHeight","(J)Z", (void*) PaintGlue::isElegantTextHeight}, - {"nSetElegantTextHeight","(JZ)V", (void*) PaintGlue::setElegantTextHeight}, - {"nGetTextSize","(J)F", (void*) PaintGlue::getTextSize}, - {"nSetTextSize","(JF)V", (void*) PaintGlue::setTextSize}, - {"nGetTextScaleX","(J)F", (void*) PaintGlue::getTextScaleX}, - {"nSetTextScaleX","(JF)V", (void*) PaintGlue::setTextScaleX}, - {"nGetTextSkewX","(J)F", (void*) PaintGlue::getTextSkewX}, - {"nSetTextSkewX","(JF)V", (void*) PaintGlue::setTextSkewX}, - {"nGetLetterSpacing","(J)F", (void*) PaintGlue::getLetterSpacing}, - {"nSetLetterSpacing","(JF)V", (void*) PaintGlue::setLetterSpacing}, - {"nGetWordSpacing","(J)F", (void*) PaintGlue::getWordSpacing}, - {"nSetWordSpacing","(JF)V", (void*) PaintGlue::setWordSpacing}, - {"nGetStartHyphenEdit", "(J)I", (void*) PaintGlue::getStartHyphenEdit}, - {"nGetEndHyphenEdit", "(J)I", (void*) PaintGlue::getEndHyphenEdit}, - {"nSetStartHyphenEdit", "(JI)V", (void*) PaintGlue::setStartHyphenEdit}, - {"nSetEndHyphenEdit", "(JI)V", (void*) PaintGlue::setEndHyphenEdit}, - {"nAscent","(J)F", (void*) PaintGlue::ascent}, - {"nDescent","(J)F", (void*) PaintGlue::descent}, - {"nGetUnderlinePosition","(J)F", (void*) PaintGlue::getUnderlinePosition}, - {"nGetUnderlineThickness","(J)F", (void*) PaintGlue::getUnderlineThickness}, - {"nGetStrikeThruPosition","(J)F", (void*) PaintGlue::getStrikeThruPosition}, - {"nGetStrikeThruThickness","(J)F", (void*) PaintGlue::getStrikeThruThickness}, - {"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer}, - {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer}, - {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, + {"nGetNativeFinalizer", "()J", (void*)PaintGlue::getNativeFinalizer}, + {"nInit", "()J", (void*)PaintGlue::init}, + {"nInitWithPaint", "(J)J", (void*)PaintGlue::initWithPaint}, + {"nBreakText", "(J[CIIFI[F)I", (void*)PaintGlue::breakTextC}, + {"nBreakText", "(JLjava/lang/String;ZFI[F)I", (void*)PaintGlue::breakTextS}, + {"nGetTextAdvances", "(J[CIIIII[FI)F", (void*)PaintGlue::getTextAdvances___CIIIII_FI}, + {"nGetTextAdvances", "(JLjava/lang/String;IIIII[FI)F", + (void*)PaintGlue::getTextAdvances__StringIIIII_FI}, + + {"nGetTextRunCursor", "(J[CIIIII)I", (void*)PaintGlue::getTextRunCursor___C}, + {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I", + (void*)PaintGlue::getTextRunCursor__String}, + {"nGetTextPath", "(JI[CIIFFJ)V", (void*)PaintGlue::getTextPath___C}, + {"nGetTextPath", "(JILjava/lang/String;IIFFJ)V", (void*)PaintGlue::getTextPath__String}, + {"nGetStringBounds", "(JLjava/lang/String;IIILandroid/graphics/Rect;)V", + (void*)PaintGlue::getStringBounds}, + {"nGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V", + (void*)PaintGlue::getCharArrayBounds}, + {"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph}, + {"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F}, + {"nGetRunCharacterAdvance", "(J[CIIIIZI[FI)F", + (void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F}, + {"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I}, + {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V", + (void*)PaintGlue::getFontMetricsIntForText___C}, + {"nGetFontMetricsIntForText", + "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V", + (void*)PaintGlue::getFontMetricsIntForText___String}, + + // --------------- @FastNative ---------------------- + + {"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales}, + {"nSetFontFeatureSettings", "(JLjava/lang/String;)V", + (void*)PaintGlue::setFontFeatureSettings}, + {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F", + (void*)PaintGlue::getFontMetrics}, + {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I", + (void*)PaintGlue::getFontMetricsInt}, + + // --------------- @CriticalNative ------------------ + + {"nReset", "(J)V", (void*)PaintGlue::reset}, + {"nSet", "(JJ)V", (void*)PaintGlue::assign}, + {"nGetFlags", "(J)I", (void*)PaintGlue::getFlags}, + {"nSetFlags", "(JI)V", (void*)PaintGlue::setFlags}, + {"nGetHinting", "(J)I", (void*)PaintGlue::getHinting}, + {"nSetHinting", "(JI)V", (void*)PaintGlue::setHinting}, + {"nSetAntiAlias", "(JZ)V", (void*)PaintGlue::setAntiAlias}, + {"nSetSubpixelText", "(JZ)V", (void*)PaintGlue::setSubpixelText}, + {"nSetLinearText", "(JZ)V", (void*)PaintGlue::setLinearText}, + {"nSetUnderlineText", "(JZ)V", (void*)PaintGlue::setUnderlineText}, + {"nSetStrikeThruText", "(JZ)V", (void*)PaintGlue::setStrikeThruText}, + {"nSetFakeBoldText", "(JZ)V", (void*)PaintGlue::setFakeBoldText}, + {"nSetFilterBitmap", "(JZ)V", (void*)PaintGlue::setFilterBitmap}, + {"nSetDither", "(JZ)V", (void*)PaintGlue::setDither}, + {"nGetStyle", "(J)I", (void*)PaintGlue::getStyle}, + {"nSetStyle", "(JI)V", (void*)PaintGlue::setStyle}, + {"nSetColor", "(JI)V", (void*)PaintGlue::setColor}, + {"nSetColor", "(JJJ)V", (void*)PaintGlue::setColorLong}, + {"nSetAlpha", "(JI)V", (void*)PaintGlue::setAlpha}, + {"nGetStrokeWidth", "(J)F", (void*)PaintGlue::getStrokeWidth}, + {"nSetStrokeWidth", "(JF)V", (void*)PaintGlue::setStrokeWidth}, + {"nGetStrokeMiter", "(J)F", (void*)PaintGlue::getStrokeMiter}, + {"nSetStrokeMiter", "(JF)V", (void*)PaintGlue::setStrokeMiter}, + {"nGetStrokeCap", "(J)I", (void*)PaintGlue::getStrokeCap}, + {"nSetStrokeCap", "(JI)V", (void*)PaintGlue::setStrokeCap}, + {"nGetStrokeJoin", "(J)I", (void*)PaintGlue::getStrokeJoin}, + {"nSetStrokeJoin", "(JI)V", (void*)PaintGlue::setStrokeJoin}, + {"nGetFillPath", "(JJJ)Z", (void*)PaintGlue::getFillPath}, + {"nSetShader", "(JJ)J", (void*)PaintGlue::setShader}, + {"nSetColorFilter", "(JJ)J", (void*)PaintGlue::setColorFilter}, + {"nSetXfermode", "(JI)V", (void*)PaintGlue::setXfermode}, + {"nSetPathEffect", "(JJ)J", (void*)PaintGlue::setPathEffect}, + {"nSetMaskFilter", "(JJ)J", (void*)PaintGlue::setMaskFilter}, + {"nSetTypeface", "(JJ)V", (void*)PaintGlue::setTypeface}, + {"nGetTextAlign", "(J)I", (void*)PaintGlue::getTextAlign}, + {"nSetTextAlign", "(JI)V", (void*)PaintGlue::setTextAlign}, + {"nSetTextLocalesByMinikinLocaleListId", "(JI)V", + (void*)PaintGlue::setTextLocalesByMinikinLocaleListId}, + {"nIsElegantTextHeight", "(J)Z", (void*)PaintGlue::isElegantTextHeight}, + {"nSetElegantTextHeight", "(JZ)V", (void*)PaintGlue::setElegantTextHeight}, + {"nGetTextSize", "(J)F", (void*)PaintGlue::getTextSize}, + {"nSetTextSize", "(JF)V", (void*)PaintGlue::setTextSize}, + {"nGetTextScaleX", "(J)F", (void*)PaintGlue::getTextScaleX}, + {"nSetTextScaleX", "(JF)V", (void*)PaintGlue::setTextScaleX}, + {"nGetTextSkewX", "(J)F", (void*)PaintGlue::getTextSkewX}, + {"nSetTextSkewX", "(JF)V", (void*)PaintGlue::setTextSkewX}, + {"nGetLetterSpacing", "(J)F", (void*)PaintGlue::getLetterSpacing}, + {"nSetLetterSpacing", "(JF)V", (void*)PaintGlue::setLetterSpacing}, + {"nGetWordSpacing", "(J)F", (void*)PaintGlue::getWordSpacing}, + {"nSetWordSpacing", "(JF)V", (void*)PaintGlue::setWordSpacing}, + {"nGetStartHyphenEdit", "(J)I", (void*)PaintGlue::getStartHyphenEdit}, + {"nGetEndHyphenEdit", "(J)I", (void*)PaintGlue::getEndHyphenEdit}, + {"nSetStartHyphenEdit", "(JI)V", (void*)PaintGlue::setStartHyphenEdit}, + {"nSetEndHyphenEdit", "(JI)V", (void*)PaintGlue::setEndHyphenEdit}, + {"nAscent", "(J)F", (void*)PaintGlue::ascent}, + {"nDescent", "(J)F", (void*)PaintGlue::descent}, + {"nGetUnderlinePosition", "(J)F", (void*)PaintGlue::getUnderlinePosition}, + {"nGetUnderlineThickness", "(J)F", (void*)PaintGlue::getUnderlineThickness}, + {"nGetStrikeThruPosition", "(J)F", (void*)PaintGlue::getStrikeThruPosition}, + {"nGetStrikeThruThickness", "(J)F", (void*)PaintGlue::getStrikeThruThickness}, + {"nSetShadowLayer", "(JFFFJJ)V", (void*)PaintGlue::setShadowLayer}, + {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer}, + {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, }; - int register_android_graphics_Paint(JNIEnv* env) { return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods)); } -- cgit v1.2.3-59-g8ed1b From 9feb92f9454cabc44cdce493ee11c34a4bc9b72b Mon Sep 17 00:00:00 2001 From: Kohsuke Yatoh Date: Fri, 15 Jul 2022 06:03:07 +0000 Subject: Add Typeface memory perf test. This test measures: - SharedMemory (mmap) byte size of serialized font map. - Native allocation (malloc) byte size of deserialized font map. Typeface is updated to avoid OOM during test. Results on oriole-userdebug: [1/5] android.graphics.perftests.TypefaceSerializationPerfTest#testSerializeFontMap: PASSED (29.222s) testSerializeFontMap_stddev (ns): 74859 testSerializeFontMap_mean (ns): 1560404 testSerializeFontMap_median (ns): 1549601 testSerializeFontMap_percentile90 (ns): 1602499 testSerializeFontMap_percentile95 (ns): 1624715 [2/5] android.graphics.perftests.TypefaceSerializationPerfTest#testSerializeFontMap_memory: PASSED (13.376s) testSerializeFontMap_memory_stddev (ns): 0 testSerializeFontMap_memory_mean (ns): 931071 testSerializeFontMap_memory_median (ns): 931071 testSerializeFontMap_memory_percentile90 (ns): 931071 testSerializeFontMap_memory_percentile95 (ns): 931071 [3/5] android.graphics.perftests.TypefaceSerializationPerfTest#testDeserializeFontMap: PASSED (32.451s) testDeserializeFontMap_stddev (ns): 60524 testDeserializeFontMap_mean (ns): 232285 testDeserializeFontMap_median (ns): 228149 testDeserializeFontMap_percentile90 (ns): 272094 testDeserializeFontMap_percentile95 (ns): 291708 [4/5] android.graphics.perftests.TypefaceSerializationPerfTest#testDeserializeFontMap_memory: PASSED (29.089s) testDeserializeFontMap_memory_stddev (ns): 134 testDeserializeFontMap_memory_mean (ns): 170209 testDeserializeFontMap_memory_median (ns): 170208 testDeserializeFontMap_memory_percentile90 (ns): 170208 testDeserializeFontMap_memory_percentile95 (ns): 170208 [5/5] android.graphics.perftests.TypefaceSerializationPerfTest#testSetSystemFontMap: PASSED (28.687s) testSetSystemFontMap_stddev (ns): 2345934 testSetSystemFontMap_mean (ns): 649662 testSetSystemFontMap_median (ns): 396260 testSetSystemFontMap_percentile90 (ns): 436483 testSetSystemFontMap_percentile95 (ns): 457479 Bug: 174672300 Fix: 238679890 Test: atest CorePerfTests:android.graphics.perftests.TypefaceSerializationPerfTest Change-Id: Idb0c2a040d3ca488134f2b91a5bcbbe33a7a59c4 --- apct-tests/perftests/core/AndroidManifest.xml | 12 ++++ .../perftests/TypefaceSerializationPerfTest.java | 75 ++++++++++++++++++---- graphics/java/android/graphics/Typeface.java | 29 ++++++++- 3 files changed, 104 insertions(+), 12 deletions(-) (limited to 'graphics/java/android') diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml index 56fa70cfc220..bb57161b4f6b 100644 --- a/apct-tests/perftests/core/AndroidManifest.xml +++ b/apct-tests/perftests/core/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -33,6 +34,17 @@ android:exported="false" android:process=":some_provider" /> + + + + + systemFontMap = Typeface.getSystemFontMap(); - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState(); - while (state.keepRunning()) { + long elapsedTime = 0; + while (state.keepRunning(elapsedTime)) { + long startTime = System.nanoTime(); Typeface.serializeFontMap(systemFontMap); + elapsedTime = System.nanoTime() - startTime; + } + } + + @Test + public void testSerializeFontMap_memory() throws Exception { + Map systemFontMap = Typeface.getSystemFontMap(); + SharedMemory memory = Typeface.serializeFontMap(systemFontMap); + ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState(); + + while (state.keepRunning(memory.getSize())) { + // Rate-limiting + SystemClock.sleep(100); } } @@ -54,22 +74,54 @@ public class TypefaceSerializationPerfTest { public void testDeserializeFontMap() throws Exception { SharedMemory memory = Typeface.serializeFontMap(Typeface.getSystemFontMap()); ByteBuffer buffer = memory.mapReadOnly().order(ByteOrder.BIG_ENDIAN); - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState(); + + ArrayMap out = new ArrayMap<>(); + long elapsedTime = 0; + while (state.keepRunning(elapsedTime)) { + long startTime = System.nanoTime(); + buffer.position(0); + Typeface.deserializeFontMap(buffer, out); + elapsedTime = System.nanoTime() - startTime; + } + } + + @Test + public void testDeserializeFontMap_memory() throws Exception { + SharedMemory memory = Typeface.serializeFontMap(Typeface.getSystemFontMap()); + ByteBuffer buffer = memory.mapReadOnly().order(ByteOrder.BIG_ENDIAN); + ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState(); ArrayMap out = new ArrayMap<>(); - while (state.keepRunning()) { + // Diff of native heap allocation size (in bytes) before and after deserializeFontMap. + // Note: we don't measure memory usage of setSystemFontMap because setSystemFontMap sets + // some global variables, and it's hard to clear them. + long heapDiff = 0; + // Sometimes heapDiff may become negative due to GC. + // Use 0 in that case to avoid crashing in keepRunning. + while (state.keepRunning(Math.max(0, heapDiff))) { buffer.position(0); + long baselineSize = Debug.getNativeHeapAllocatedSize(); Typeface.deserializeFontMap(buffer, out); + long currentSize = Debug.getNativeHeapAllocatedSize(); + heapDiff = currentSize - baselineSize; + Log.i(TAG, String.format("native heap alloc: current = %d, baseline = %d, diff = %d", + currentSize, baselineSize, heapDiff)); + // Release native objects here to minimize the impact of GC. + for (Typeface typeface : out.values()) { + typeface.releaseNativeObjectForTest(); + } + out.clear(); } } @Test public void testSetSystemFontMap() throws Exception { SharedMemory memory = null; - BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState(); - while (state.keepRunning()) { - state.pauseTiming(); + long elapsedTime = 0; + while (state.keepRunning(elapsedTime)) { // Explicitly destroy lazy-loaded typefaces, so that we don't hit the mmap limit // (max_map_count). Typeface.destroySystemFontMap(); @@ -78,8 +130,9 @@ public class TypefaceSerializationPerfTest { memory.close(); } memory = Typeface.serializeFontMap(Typeface.getSystemFontMap()); - state.resumeTiming(); + long startTime = System.nanoTime(); Typeface.setSystemFontMap(memory); + elapsedTime = System.nanoTime() - startTime; } } } diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index a2f5301e353f..8e3acb8570f0 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -1176,6 +1176,17 @@ public class Typeface { mWeight = nativeGetWeight(ni); } + /** + * Releases the underlying native object. + * + *

For testing only. Do not use the instance after this method is called. + * It is safe to call this method twice or more on the same instance. + * @hide + */ + public void releaseNativeObjectForTest() { + mCleaner.run(); + } + private static Typeface getSystemDefaultTypeface(@NonNull String familyName) { Typeface tf = sSystemFontMap.get(familyName); return tf == null ? Typeface.DEFAULT : tf; @@ -1425,7 +1436,7 @@ public class Typeface { public static void destroySystemFontMap() { synchronized (SYSTEM_FONT_MAP_LOCK) { for (Typeface typeface : sSystemFontMap.values()) { - typeface.mCleaner.run(); + typeface.releaseNativeObjectForTest(); } sSystemFontMap.clear(); if (sSystemFontMapBuffer != null) { @@ -1433,7 +1444,23 @@ public class Typeface { } sSystemFontMapBuffer = null; sSystemFontMapSharedMemory = null; + synchronized (sStyledCacheLock) { + destroyTypefaceCacheLocked(sStyledTypefaceCache); + } + synchronized (sWeightCacheLock) { + destroyTypefaceCacheLocked(sWeightTypefaceCache); + } + } + } + + private static void destroyTypefaceCacheLocked(LongSparseArray> cache) { + for (int i = 0; i < cache.size(); i++) { + SparseArray array = cache.valueAt(i); + for (int j = 0; j < array.size(); j++) { + array.valueAt(j).releaseNativeObjectForTest(); + } } + cache.clear(); } /** @hide */ -- cgit v1.2.3-59-g8ed1b From bb4e06fde0f19884ca30f676e4370b3b5f7833f6 Mon Sep 17 00:00:00 2001 From: Kohsuke Yatoh Date: Tue, 19 Jul 2022 06:10:24 +0000 Subject: Preload minikin LocaleListCache in Zygote. This saves ~31 KB per process on oriole. Bug: 174672300 Test: atest CtsGraphicsTestCases:android.graphics.cts.TypefaceTest Change-Id: Ic70ea842985a0aca441a31327e41980ed47bc961 --- graphics/java/android/graphics/Typeface.java | 7 +++++++ libs/hwui/jni/Typeface.cpp | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index a2f5301e353f..4ba6fba69de5 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -57,6 +57,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; @@ -1395,6 +1396,9 @@ public class Typeface { FontConfig config = SystemFonts.getSystemPreinstalledFontConfig(); for (int i = 0; i < config.getFontFamilies().size(); ++i) { FontConfig.FontFamily family = config.getFontFamilies().get(i); + if (!family.getLocaleList().isEmpty()) { + nativeRegisterLocaleList(family.getLocaleList().toLanguageTags()); + } boolean loadFamily = false; for (int j = 0; j < family.getLocaleList().size(); ++j) { String fontScript = ULocale.addLikelySubtags( @@ -1541,4 +1545,7 @@ public class Typeface { private static native void nativeAddFontCollections(long nativePtr); private static native void nativeWarmUpCache(String fileName); + + @FastNative + private static native void nativeRegisterLocaleList(String locales); } diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp index d86d9ee56f4c..e79b395412b0 100644 --- a/libs/hwui/jni/Typeface.cpp +++ b/libs/hwui/jni/Typeface.cpp @@ -20,18 +20,20 @@ #include #include #include +#include #include #include #include + +#include +#include + #include "FontUtils.h" #include "GraphicsJNI.h" #include "SkData.h" #include "SkTypeface.h" #include "fonts/Font.h" -#include -#include - #ifdef __ANDROID__ #include #endif @@ -380,6 +382,12 @@ static void Typeface_addFontCollection(CRITICAL_JNI_PARAMS_COMMA jlong faceHandl minikin::SystemFonts::addFontMap(std::move(collection)); } +// Fast Native +static void Typeface_registerLocaleList(JNIEnv* env, jobject, jstring jLocales) { + ScopedUtfChars locales(env, jLocales); + minikin::registerLocaleList(locales.c_str()); +} + /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gTypefaceMethods[] = { @@ -405,6 +413,7 @@ static const JNINativeMethod gTypefaceMethods[] = { {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily}, {"nativeWarmUpCache", "(Ljava/lang/String;)V", (void*)Typeface_warmUpCache}, {"nativeAddFontCollections", "(J)V", (void*)Typeface_addFontCollection}, + {"nativeRegisterLocaleList", "(Ljava/lang/String;)V", (void*)Typeface_registerLocaleList}, }; int register_android_graphics_Typeface(JNIEnv* env) -- cgit v1.2.3-59-g8ed1b From a2ffe9084acf4102de59e62216f0b957dbe70a13 Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Thu, 21 Jul 2022 14:12:14 +0900 Subject: Add TestApi that change default typeface Bug: 238597089 Test: atest android.theme.cts.ThemeHostTest#testThemes Change-Id: Ia56b4424ab27b2f8dd01c0e61a1f9d26bd661fc9 --- core/api/test-current.txt | 1 + graphics/java/android/graphics/Typeface.java | 36 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) (limited to 'graphics/java/android') diff --git a/core/api/test-current.txt b/core/api/test-current.txt index b13ebf388d1f..221cbdcfd4d7 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1026,6 +1026,7 @@ package android.graphics { } public class Typeface { + method @NonNull public static android.util.Pair,java.util.List> changeDefaultFontForTest(@NonNull java.util.List, @NonNull java.util.List); method @NonNull public static long[] deserializeFontMap(@NonNull java.nio.ByteBuffer, @NonNull java.util.Map) throws java.io.IOException; method @Nullable public static android.os.SharedMemory getSystemFontMapSharedMemory(); method @NonNull public static android.os.SharedMemory serializeFontMap(@NonNull java.util.Map) throws android.system.ErrnoException, java.io.IOException; diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index a2f5301e353f..d6692a968e45 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -50,6 +50,7 @@ import android.util.Base64; import android.util.Log; import android.util.LongSparseArray; import android.util.LruCache; +import android.util.Pair; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -1384,6 +1385,41 @@ public class Typeface { } } + /** + * Change default typefaces for testing purpose. + * + * Note: The existing TextView or Paint instance still holds the old Typeface. + * + * @param defaults array of [default, default_bold, default_italic, default_bolditalic]. + * @param genericFamilies array of [sans-serif, serif, monospace] + * @return return the old defaults and genericFamilies + * @hide + */ + @TestApi + @NonNull + public static Pair, List> changeDefaultFontForTest( + @NonNull List defaults, + @NonNull List genericFamilies + ) { + synchronized (SYSTEM_FONT_MAP_LOCK) { + List oldDefaults = Arrays.asList(sDefaults); + sDefaults = defaults.toArray(new Typeface[4]); + setDefault(defaults.get(0)); + + ArrayList oldGenerics = new ArrayList<>(); + oldGenerics.add(sSystemFontMap.get("sans-serif")); + sSystemFontMap.put("sans-serif", genericFamilies.get(0)); + + oldGenerics.add(sSystemFontMap.get("serif")); + sSystemFontMap.put("serif", genericFamilies.get(1)); + + oldGenerics.add(sSystemFontMap.get("monospace")); + sSystemFontMap.put("monospace", genericFamilies.get(2)); + + return new Pair<>(oldDefaults, oldGenerics); + } + } + static { // Preload Roboto-Regular.ttf in Zygote for improving app launch performance. preloadFontFile("/system/fonts/Roboto-Regular.ttf"); -- cgit v1.2.3-59-g8ed1b From 711a120d16188b69a75792dcbf053c91916ca980 Mon Sep 17 00:00:00 2001 From: Kohsuke Yatoh Date: Fri, 29 Jul 2022 00:46:39 +0000 Subject: Pass buffer base address to minikin::BufferReader. Bug: 174672300 Test: atest CtsGraphicsTestCases:android.graphics.cts.TypefaceTest Change-Id: Ie6bf1bb01ccc2803bc6206a0d2276ac59c6877a1 --- graphics/java/android/graphics/Typeface.java | 17 +++++++++++------ libs/hwui/jni/Typeface.cpp | 28 ++++++++++++++++++++-------- libs/hwui/jni/fonts/Font.cpp | 8 ++++---- 3 files changed, 35 insertions(+), 18 deletions(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index f4f5e1e792ec..8ae0d3d48d6d 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -1244,14 +1244,16 @@ public class Typeface { nativePtrs[i++] = entry.getValue().native_instance; writeString(namesBytes, entry.getKey()); } - int typefacesBytesCount = nativeWriteTypefaces(null, nativePtrs); // int (typefacesBytesCount), typefaces, namesBytes + final int typefaceBytesCountSize = Integer.BYTES; + int typefacesBytesCount = nativeWriteTypefaces(null, typefaceBytesCountSize, nativePtrs); SharedMemory sharedMemory = SharedMemory.create( - "fontMap", Integer.BYTES + typefacesBytesCount + namesBytes.size()); + "fontMap", typefaceBytesCountSize + typefacesBytesCount + namesBytes.size()); ByteBuffer writableBuffer = sharedMemory.mapReadWrite().order(ByteOrder.BIG_ENDIAN); try { writableBuffer.putInt(typefacesBytesCount); - int writtenBytesCount = nativeWriteTypefaces(writableBuffer.slice(), nativePtrs); + int writtenBytesCount = + nativeWriteTypefaces(writableBuffer, writableBuffer.position(), nativePtrs); if (writtenBytesCount != typefacesBytesCount) { throw new IOException(String.format("Unexpected bytes written: %d, expected: %d", writtenBytesCount, typefacesBytesCount)); @@ -1276,7 +1278,9 @@ public class Typeface { @NonNull ByteBuffer buffer, @NonNull Map out) throws IOException { int typefacesBytesCount = buffer.getInt(); - long[] nativePtrs = nativeReadTypefaces(buffer.slice()); + // Note: Do not call buffer.slice(), as nativeReadTypefaces() expects + // that buffer.address() is page-aligned. + long[] nativePtrs = nativeReadTypefaces(buffer, buffer.position()); if (nativePtrs == null) { throw new IOException("Could not read typefaces"); } @@ -1598,9 +1602,10 @@ public class Typeface { private static native void nativeRegisterGenericFamily(String str, long nativePtr); private static native int nativeWriteTypefaces( - @Nullable ByteBuffer buffer, @NonNull long[] nativePtrs); + @Nullable ByteBuffer buffer, int position, @NonNull long[] nativePtrs); - private static native @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer); + private static native + @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer, int position); private static native void nativeForceSetStaticFinalField(String fieldName, Typeface typeface); diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp index 19efc5f09d11..6325226068dd 100644 --- a/libs/hwui/jni/Typeface.cpp +++ b/libs/hwui/jni/Typeface.cpp @@ -305,7 +305,8 @@ void MinikinFontSkiaFactory::write(minikin::BufferWriter* writer, } } -static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongArray faceHandles) { +static jint Typeface_writeTypefaces(JNIEnv* env, jobject, jobject buffer, jint position, + jlongArray faceHandles) { MinikinFontSkiaFactory::init(); ScopedLongArrayRO faces(env, faceHandles); std::vector typefaces; @@ -314,7 +315,12 @@ static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongA typefaces.push_back(toTypeface(faces[i])); } void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer); - minikin::BufferWriter writer(addr); + if (addr != nullptr && + reinterpret_cast(addr) % minikin::BufferReader::kMaxAlignment != 0) { + ALOGE("addr (%p) must be aligned at kMaxAlignment, but it was not.", addr); + return 0; + } + minikin::BufferWriter writer(addr, position); std::vector> fontCollections; std::unordered_map, size_t> fcToIndex; for (Typeface* typeface : typefaces) { @@ -334,11 +340,18 @@ static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongA return static_cast(writer.size()); } -static jlongArray Typeface_readTypefaces(JNIEnv *env, jobject, jobject buffer) { +static jlongArray Typeface_readTypefaces(JNIEnv* env, jobject, jobject buffer, jint position) { MinikinFontSkiaFactory::init(); void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer); - if (addr == nullptr) return nullptr; - minikin::BufferReader reader(addr); + if (addr == nullptr) { + ALOGE("Passed a null buffer."); + return nullptr; + } + if (reinterpret_cast(addr) % minikin::BufferReader::kMaxAlignment != 0) { + ALOGE("addr (%p) must be aligned at kMaxAlignment, but it was not.", addr); + return nullptr; + } + minikin::BufferReader reader(addr, position); std::vector> fontCollections = minikin::FontCollection::readVector(&reader); uint32_t typefaceCount = reader.read(); @@ -357,7 +370,6 @@ static jlongArray Typeface_readTypefaces(JNIEnv *env, jobject, jobject buffer) { return result; } - static void Typeface_forceSetStaticFinalField(JNIEnv *env, jclass cls, jstring fieldName, jobject typeface) { ScopedUtfChars fieldNameChars(env, fieldName); @@ -417,8 +429,8 @@ static const JNINativeMethod gTypefaceMethods[] = { {"nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes}, {"nativeRegisterGenericFamily", "(Ljava/lang/String;J)V", (void*)Typeface_registerGenericFamily}, - {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces}, - {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces}, + {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;I[J)I", (void*)Typeface_writeTypefaces}, + {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;I)[J", (void*)Typeface_readTypefaces}, {"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V", (void*)Typeface_forceSetStaticFinalField}, {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize}, diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index 2c421f8727a8..f17129c8d953 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -228,7 +228,7 @@ static jlong Font_getReleaseNativeFontFunc(CRITICAL_JNI_PARAMS) { static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontPtr) { FontWrapper* font = reinterpret_cast(fontPtr); minikin::BufferReader reader = font->font->typefaceMetadataReader(); - if (reader.data() != nullptr) { + if (reader.current() != nullptr) { std::string path = std::string(reader.readString()); if (path.empty()) { return nullptr; @@ -270,7 +270,7 @@ static jint Font_getPackedStyle(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { static jint Font_getIndex(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { FontWrapper* font = reinterpret_cast(fontPtr); minikin::BufferReader reader = font->font->typefaceMetadataReader(); - if (reader.data() != nullptr) { + if (reader.current() != nullptr) { reader.skipString(); // fontPath return reader.read(); } else { @@ -283,7 +283,7 @@ static jint Font_getIndex(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { static jint Font_getAxisCount(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { FontWrapper* font = reinterpret_cast(fontPtr); minikin::BufferReader reader = font->font->typefaceMetadataReader(); - if (reader.data() != nullptr) { + if (reader.current() != nullptr) { reader.skipString(); // fontPath reader.skip(); // fontIndex return reader.readArray().second; @@ -298,7 +298,7 @@ static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr, jint inde FontWrapper* font = reinterpret_cast(fontPtr); minikin::BufferReader reader = font->font->typefaceMetadataReader(); minikin::FontVariation var; - if (reader.data() != nullptr) { + if (reader.current() != nullptr) { reader.skipString(); // fontPath reader.skip(); // fontIndex var = reader.readArray().first[index]; -- cgit v1.2.3-59-g8ed1b From 4d73cb10437af19ef9a4d94a92edbb5642db148e Mon Sep 17 00:00:00 2001 From: John Reck Date: Wed, 27 Jul 2022 10:32:52 -0400 Subject: Allow PixelCopy for a window from any View Also make it actually async, and allow the bitmap to be auto-allocated Bug: 195673633 Test: PixelCopyTest CTS suite Change-Id: Ie872f20c809eaaeb8dc32f3ec6347f21a9a7bc1a --- core/api/current.txt | 15 ++ .../java/android/graphics/HardwareRenderer.java | 43 +++- graphics/java/android/view/PixelCopy.java | 251 +++++++++++++++++++-- libs/hwui/CopyRequest.h | 42 ++++ libs/hwui/Readback.cpp | 79 ++----- libs/hwui/Readback.h | 18 +- .../hwui/jni/android_graphics_HardwareRenderer.cpp | 53 ++++- libs/hwui/renderthread/RenderProxy.cpp | 11 +- libs/hwui/renderthread/RenderProxy.h | 6 +- .../tests/common/scenes/MagnifierAnimation.cpp | 51 ++++- libs/hwui/thread/WorkQueue.h | 2 +- 11 files changed, 446 insertions(+), 125 deletions(-) create mode 100644 libs/hwui/CopyRequest.h (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index 7fbf3ece4550..0c2c580fc456 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -49219,6 +49219,10 @@ package android.view { } public final class PixelCopy { + method @NonNull public static android.view.PixelCopy.Request ofSurface(@NonNull android.view.Surface, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @NonNull public static android.view.PixelCopy.Request ofSurface(@NonNull android.view.SurfaceView, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @NonNull public static android.view.PixelCopy.Request ofWindow(@NonNull android.view.Window, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @NonNull public static android.view.PixelCopy.Request ofWindow(@NonNull android.view.View, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); method public static void request(@NonNull android.view.SurfaceView, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler); method public static void request(@NonNull android.view.SurfaceView, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler); method public static void request(@NonNull android.view.Surface, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler); @@ -49233,10 +49237,21 @@ package android.view { field public static final int SUCCESS = 0; // 0x0 } + public static final class PixelCopy.CopyResult { + method @NonNull public android.graphics.Bitmap getBitmap(); + method public int getStatus(); + } + public static interface PixelCopy.OnPixelCopyFinishedListener { method public void onPixelCopyFinished(int); } + public static final class PixelCopy.Request { + method public void request(); + method @NonNull public android.view.PixelCopy.Request setDestinationBitmap(@Nullable android.graphics.Bitmap); + method @NonNull public android.view.PixelCopy.Request setSourceRect(@Nullable android.graphics.Rect); + } + public final class PointerIcon implements android.os.Parcelable { method @NonNull public static android.view.PointerIcon create(@NonNull android.graphics.Bitmap, float, float); method public int describeContents(); diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 7cc22d753f69..0e67f1f4842a 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -1046,14 +1046,39 @@ public class HardwareRenderer { } /** @hide */ - public static int copySurfaceInto(Surface surface, Rect srcRect, Bitmap bitmap) { - if (srcRect == null) { - // Empty rect means entire surface - return nCopySurfaceInto(surface, 0, 0, 0, 0, bitmap.getNativeInstance()); - } else { - return nCopySurfaceInto(surface, srcRect.left, srcRect.top, - srcRect.right, srcRect.bottom, bitmap.getNativeInstance()); + public abstract static class CopyRequest { + protected Bitmap mDestinationBitmap; + final Rect mSrcRect; + + protected CopyRequest(Rect srcRect, Bitmap destinationBitmap) { + mDestinationBitmap = destinationBitmap; + if (srcRect != null) { + mSrcRect = srcRect; + } else { + mSrcRect = new Rect(); + } + } + + /** + * Retrieve the bitmap in which to store the result of the copy request + */ + public long getDestinationBitmap(int srcWidth, int srcHeight) { + if (mDestinationBitmap == null) { + mDestinationBitmap = + Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888); + } + return mDestinationBitmap.getNativeInstance(); } + + /** Called when the copy is completed */ + public abstract void onCopyFinished(int result); + } + + /** @hide */ + public static void copySurfaceInto(Surface surface, CopyRequest copyRequest) { + final Rect srcRect = copyRequest.mSrcRect; + nCopySurfaceInto(surface, srcRect.left, srcRect.top, srcRect.right, srcRect.bottom, + copyRequest); } /** @@ -1464,8 +1489,8 @@ public class HardwareRenderer { private static native void nRemoveObserver(long nativeProxy, long nativeObserver); - private static native int nCopySurfaceInto(Surface surface, - int srcLeft, int srcTop, int srcRight, int srcBottom, long bitmapHandle); + private static native void nCopySurfaceInto(Surface surface, + int srcLeft, int srcTop, int srcRight, int srcBottom, CopyRequest request); private static native Bitmap nCreateHardwareBitmap(long renderNode, int width, int height); diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java index 2797a4daa925..0f82c8fe904a 100644 --- a/graphics/java/android/view/PixelCopy.java +++ b/graphics/java/android/view/PixelCopy.java @@ -20,12 +20,15 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Bitmap; +import android.graphics.HardwareRenderer; import android.graphics.Rect; import android.os.Handler; import android.view.ViewTreeObserver.OnDrawListener; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Provides a mechanisms to issue pixel copy requests to allow for copy @@ -183,12 +186,10 @@ public final class PixelCopy { if (srcRect != null && srcRect.isEmpty()) { throw new IllegalArgumentException("sourceRect is empty"); } - // TODO: Make this actually async and fast and cool and stuff - int result = ThreadedRenderer.copySurfaceInto(source, srcRect, dest); - listenerThread.post(new Runnable() { + HardwareRenderer.copySurfaceInto(source, new HardwareRenderer.CopyRequest(srcRect, dest) { @Override - public void run() { - listener.onPixelCopyFinished(result); + public void onCopyFinished(int result) { + listenerThread.post(() -> listener.onPixelCopyFinished(result)); } }); } @@ -255,6 +256,26 @@ public final class PixelCopy { @NonNull Bitmap dest, @NonNull OnPixelCopyFinishedListener listener, @NonNull Handler listenerThread) { validateBitmapDest(dest); + final Rect insets = new Rect(); + final Surface surface = sourceForWindow(source, insets); + request(surface, adjustSourceRectForInsets(insets, srcRect), dest, listener, + listenerThread); + } + + private static void validateBitmapDest(Bitmap bitmap) { + // TODO: Pre-check max texture dimens if we can + if (bitmap == null) { + throw new IllegalArgumentException("Bitmap cannot be null"); + } + if (bitmap.isRecycled()) { + throw new IllegalArgumentException("Bitmap is recycled"); + } + if (!bitmap.isMutable()) { + throw new IllegalArgumentException("Bitmap is immutable"); + } + } + + private static Surface sourceForWindow(Window source, Rect outInsets) { if (source == null) { throw new IllegalArgumentException("source is null"); } @@ -267,32 +288,222 @@ public final class PixelCopy { if (root != null) { surface = root.mSurface; final Rect surfaceInsets = root.mWindowAttributes.surfaceInsets; - if (srcRect == null) { - srcRect = new Rect(surfaceInsets.left, surfaceInsets.top, - root.mWidth + surfaceInsets.left, root.mHeight + surfaceInsets.top); - } else { - srcRect.offset(surfaceInsets.left, surfaceInsets.top); - } + outInsets.set(surfaceInsets.left, surfaceInsets.top, + root.mWidth + surfaceInsets.left, root.mHeight + surfaceInsets.top); } if (surface == null || !surface.isValid()) { throw new IllegalArgumentException( "Window doesn't have a backing surface!"); } - request(surface, srcRect, dest, listener, listenerThread); + return surface; } - private static void validateBitmapDest(Bitmap bitmap) { - // TODO: Pre-check max texture dimens if we can - if (bitmap == null) { - throw new IllegalArgumentException("Bitmap cannot be null"); + private static Rect adjustSourceRectForInsets(Rect insets, Rect srcRect) { + if (srcRect == null) { + return insets; } - if (bitmap.isRecycled()) { - throw new IllegalArgumentException("Bitmap is recycled"); + if (insets != null) { + srcRect.offset(insets.left, insets.top); } - if (!bitmap.isMutable()) { - throw new IllegalArgumentException("Bitmap is immutable"); + return srcRect; + } + + /** + * Contains the result of a PixelCopy request + */ + public static final class CopyResult { + private int mStatus; + private Bitmap mBitmap; + + private CopyResult(@CopyResultStatus int status, Bitmap bitmap) { + mStatus = status; + mBitmap = bitmap; + } + + /** + * Returns the {@link CopyResultStatus} of the copy request. + */ + public @CopyResultStatus int getStatus() { + return mStatus; + } + + private void validateStatus() { + if (mStatus != SUCCESS) { + throw new IllegalStateException("Copy request didn't succeed, status = " + mStatus); + } + } + + /** + * If the PixelCopy {@link Request} was given a destination bitmap with + * {@link Request#setDestinationBitmap(Bitmap)} then the returned bitmap will be the same + * as the one given. If no destination bitmap was provided, then this + * will contain the automatically allocated Bitmap to hold the result. + * + * @return the Bitmap the copy request was stored in. + * @throws IllegalStateException if {@link #getStatus()} is not SUCCESS + */ + public @NonNull Bitmap getBitmap() { + validateStatus(); + return mBitmap; + } + } + + /** + * A builder to create the complete PixelCopy request, which is then executed by calling + * {@link #request()} + */ + public static final class Request { + private Request(Surface source, Rect sourceInsets, Executor listenerThread, + Consumer listener) { + this.mSource = source; + this.mSourceInsets = sourceInsets; + this.mListenerThread = listenerThread; + this.mListener = listener; + } + + private final Surface mSource; + private final Consumer mListener; + private final Executor mListenerThread; + private final Rect mSourceInsets; + private Rect mSrcRect; + private Bitmap mDest; + + /** + * Sets the region of the source to copy from. By default, the entire source is copied to + * the output. If only a subset of the source is necessary to be copied, specifying a + * srcRect will improve performance by reducing + * the amount of data being copied. + * + * @param srcRect The area of the source to read from. Null or empty will be treated to + * mean the entire source + * @return this + */ + public @NonNull Request setSourceRect(@Nullable Rect srcRect) { + this.mSrcRect = srcRect; + return this; + } + + /** + * Specifies the output bitmap in which to store the result. By default, a Bitmap of format + * {@link android.graphics.Bitmap.Config#ARGB_8888} with a width & height matching that + * of the {@link #setSourceRect(Rect) source area} will be created to place the result. + * + * @param destination The bitmap to store the result, or null to have a bitmap + * automatically created of the appropriate size. If not null, must not + * be {@link Bitmap#isRecycled() recycled} and must be + * {@link Bitmap#isMutable() mutable}. + * @return this + */ + public @NonNull Request setDestinationBitmap(@Nullable Bitmap destination) { + if (destination != null) { + validateBitmapDest(destination); + } + this.mDest = destination; + return this; + } + + /** + * Executes the request. + */ + public void request() { + if (!mSource.isValid()) { + mListenerThread.execute(() -> mListener.accept( + new CopyResult(ERROR_SOURCE_INVALID, null))); + return; + } + HardwareRenderer.copySurfaceInto(mSource, new HardwareRenderer.CopyRequest( + adjustSourceRectForInsets(mSourceInsets, mSrcRect), mDest) { + @Override + public void onCopyFinished(int result) { + mListenerThread.execute(() -> mListener.accept( + new CopyResult(result, mDestinationBitmap))); + } + }); } } + /** + * Creates a PixelCopy request for the given {@link Window} + * @param source The Window to copy from + * @param callbackExecutor The executor to run the callback on + * @param listener The callback for when the copy request is completed + * @return A {@link Request} builder to set the optional params & execute the request + */ + public static @NonNull Request ofWindow(@NonNull Window source, + @NonNull Executor callbackExecutor, + @NonNull Consumer listener) { + final Rect insets = new Rect(); + final Surface surface = sourceForWindow(source, insets); + return new Request(surface, insets, callbackExecutor, listener); + } + + /** + * Creates a PixelCopy request for the {@link Window} that the given {@link View} is + * attached to. + * + * Note that this copy request is not cropped to the area the View occupies by default. If that + * behavior is desired, use {@link View#getLocationInWindow(int[])} combined with + * {@link Request#setSourceRect(Rect)} to set a crop area to restrict the copy operation. + * + * @param source A View that {@link View#isAttachedToWindow() is attached} to a window that + * will be used to retrieve the window to copy from. + * @param callbackExecutor The executor to run the callback on + * @param listener The callback for when the copy request is completed + * @return A {@link Request} builder to set the optional params & execute the request + */ + public static @NonNull Request ofWindow(@NonNull View source, + @NonNull Executor callbackExecutor, + @NonNull Consumer listener) { + if (source == null || !source.isAttachedToWindow()) { + throw new IllegalArgumentException( + "View must not be null & must be attached to window"); + } + final Rect insets = new Rect(); + Surface surface = null; + final ViewRootImpl root = source.getViewRootImpl(); + if (root != null) { + surface = root.mSurface; + insets.set(root.mWindowAttributes.surfaceInsets); + } + if (surface == null || !surface.isValid()) { + throw new IllegalArgumentException( + "Window doesn't have a backing surface!"); + } + return new Request(surface, insets, callbackExecutor, listener); + } + + /** + * Creates a PixelCopy request for the given {@link Surface} + * + * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}. + * @param callbackExecutor The executor to run the callback on + * @param listener The callback for when the copy request is completed + * @return A {@link Request} builder to set the optional params & execute the request + */ + public static @NonNull Request ofSurface(@NonNull Surface source, + @NonNull Executor callbackExecutor, + @NonNull Consumer listener) { + if (source == null || !source.isValid()) { + throw new IllegalArgumentException("Source must not be null & must be valid"); + } + return new Request(source, null, callbackExecutor, listener); + } + + /** + * Creates a PixelCopy request for the {@link Surface} belonging to the + * given {@link SurfaceView} + * + * @param source The SurfaceView to copy from. The backing surface must be + * {@link Surface#isValid() valid} + * @param callbackExecutor The executor to run the callback on + * @param listener The callback for when the copy request is completed + * @return A {@link Request} builder to set the optional params & execute the request + */ + public static @NonNull Request ofSurface(@NonNull SurfaceView source, + @NonNull Executor callbackExecutor, + @NonNull Consumer listener) { + return ofSurface(source.getHolder().getSurface(), callbackExecutor, listener); + } + private PixelCopy() {} } diff --git a/libs/hwui/CopyRequest.h b/libs/hwui/CopyRequest.h new file mode 100644 index 000000000000..5fbd5f900716 --- /dev/null +++ b/libs/hwui/CopyRequest.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Rect.h" +#include "hwui/Bitmap.h" + +namespace android::uirenderer { + +// Keep in sync with PixelCopy.java codes +enum class CopyResult { + Success = 0, + UnknownError = 1, + Timeout = 2, + SourceEmpty = 3, + SourceInvalid = 4, + DestinationInvalid = 5, +}; + +struct CopyRequest { + Rect srcRect; + CopyRequest(Rect srcRect) : srcRect(srcRect) {} + virtual ~CopyRequest() {} + virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) = 0; + virtual void onCopyFinished(CopyResult result) = 0; +}; + +} // namespace android::uirenderer diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp index 1191b92a0aeb..02c2e67a319b 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -49,8 +49,7 @@ namespace uirenderer { #define ARECT_ARGS(r) float((r).left), float((r).top), float((r).right), float((r).bottom) -CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRect, - SkBitmap* bitmap) { +void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr& request) { ATRACE_CALL(); // Setup the source AHardwareBuffer* rawSourceBuffer; @@ -63,30 +62,33 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec // Really this shouldn't ever happen, but better safe than sorry. if (err == UNKNOWN_TRANSACTION) { ALOGW("Readback failed to ANativeWindow_getLastQueuedBuffer2 - who are we talking to?"); - return copySurfaceIntoLegacy(window, inSrcRect, bitmap); + return request->onCopyFinished(CopyResult::SourceInvalid); } ALOGV("Using new path, cropRect=" RECT_STRING ", transform=%x", ARECT_ARGS(cropRect), windowTransform); if (err != NO_ERROR) { ALOGW("Failed to get last queued buffer, error = %d", err); - return CopyResult::UnknownError; + return request->onCopyFinished(CopyResult::SourceInvalid); } if (rawSourceBuffer == nullptr) { ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); - return CopyResult::SourceEmpty; + return request->onCopyFinished(CopyResult::SourceEmpty); } UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer}; AHardwareBuffer_Desc description; AHardwareBuffer_describe(sourceBuffer.get(), &description); if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) { ALOGW("Surface is protected, unable to copy from it"); - return CopyResult::SourceInvalid; + return request->onCopyFinished(CopyResult::SourceInvalid); } - if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) { - ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); - return CopyResult::Timeout; + { + ATRACE_NAME("sync_wait"); + if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) { + ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); + return request->onCopyFinished(CopyResult::Timeout); + } } sk_sp colorSpace = DataSpaceToColorSpace( @@ -95,12 +97,12 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace); if (!image.get()) { - return CopyResult::UnknownError; + return request->onCopyFinished(CopyResult::UnknownError); } sk_sp grContext = mRenderThread.requireGrContext(); - SkRect srcRect = inSrcRect.toSkRect(); + SkRect srcRect = request->srcRect.toSkRect(); SkRect imageSrcRect = SkRect::MakeIWH(description.width, description.height); SkISize imageWH = SkISize::Make(description.width, description.height); @@ -148,10 +150,12 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec ALOGV("intersecting " RECT_STRING " with " RECT_STRING, SK_RECT_ARGS(srcRect), SK_RECT_ARGS(textureRect)); if (!srcRect.intersect(textureRect)) { - return CopyResult::UnknownError; + return request->onCopyFinished(CopyResult::UnknownError); } } + SkBitmap skBitmap = request->getDestinationBitmap(srcRect.width(), srcRect.height()); + SkBitmap* bitmap = &skBitmap; sk_sp tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr); @@ -164,7 +168,7 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr); if (!tmpSurface.get()) { ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap"); - return CopyResult::UnknownError; + return request->onCopyFinished(CopyResult::UnknownError); } } @@ -235,52 +239,13 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec !tmpBitmap.tryAllocPixels(tmpInfo) || !tmpSurface->readPixels(tmpBitmap, 0, 0) || !tmpBitmap.readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) { ALOGW("Unable to convert content into the provided bitmap"); - return CopyResult::UnknownError; + return request->onCopyFinished(CopyResult::UnknownError); } } bitmap->notifyPixelsChanged(); - return CopyResult::Success; -} - -CopyResult Readback::copySurfaceIntoLegacy(ANativeWindow* window, const Rect& srcRect, - SkBitmap* bitmap) { - // Setup the source - AHardwareBuffer* rawSourceBuffer; - int rawSourceFence; - Matrix4 texTransform; - status_t err = ANativeWindow_getLastQueuedBuffer(window, &rawSourceBuffer, &rawSourceFence, - texTransform.data); - base::unique_fd sourceFence(rawSourceFence); - texTransform.invalidateType(); - if (err != NO_ERROR) { - ALOGW("Failed to get last queued buffer, error = %d", err); - return CopyResult::UnknownError; - } - if (rawSourceBuffer == nullptr) { - ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); - return CopyResult::SourceEmpty; - } - - UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer}; - AHardwareBuffer_Desc description; - AHardwareBuffer_describe(sourceBuffer.get(), &description); - if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) { - ALOGW("Surface is protected, unable to copy from it"); - return CopyResult::SourceInvalid; - } - - if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) { - ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); - return CopyResult::Timeout; - } - - sk_sp colorSpace = DataSpaceToColorSpace( - static_cast(ANativeWindow_getBuffersDataSpace(window))); - sk_sp image = - SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace); - return copyImageInto(image, srcRect, bitmap); + return request->onCopyFinished(CopyResult::Success); } CopyResult Readback::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) { @@ -318,14 +283,14 @@ CopyResult Readback::copyImageInto(const sk_sp& image, SkBitmap* bitmap CopyResult Readback::copyImageInto(const sk_sp& image, const Rect& srcRect, SkBitmap* bitmap) { ATRACE_CALL(); + if (!image.get()) { + return CopyResult::UnknownError; + } if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { mRenderThread.requireGlContext(); } else { mRenderThread.requireVkContext(); } - if (!image.get()) { - return CopyResult::UnknownError; - } int imgWidth = image->width(); int imgHeight = image->height(); sk_sp grContext = sk_ref_sp(mRenderThread.getGrContext()); diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h index aa6e43c3bc27..a092d472abf0 100644 --- a/libs/hwui/Readback.h +++ b/libs/hwui/Readback.h @@ -16,12 +16,13 @@ #pragma once +#include + +#include "CopyRequest.h" #include "Matrix.h" #include "Rect.h" #include "renderthread/RenderThread.h" -#include - class SkBitmap; class SkImage; struct SkRect; @@ -35,23 +36,13 @@ namespace uirenderer { class DeferredLayerUpdater; class Layer; -// Keep in sync with PixelCopy.java codes -enum class CopyResult { - Success = 0, - UnknownError = 1, - Timeout = 2, - SourceEmpty = 3, - SourceInvalid = 4, - DestinationInvalid = 5, -}; - class Readback { public: explicit Readback(renderthread::RenderThread& thread) : mRenderThread(thread) {} /** * Copies the surface's most recently queued buffer into the provided bitmap. */ - CopyResult copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap); + void copySurfaceInto(ANativeWindow* window, const std::shared_ptr& request); CopyResult copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap); CopyResult copyImageInto(const sk_sp& image, SkBitmap* bitmap); @@ -59,7 +50,6 @@ public: CopyResult copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap); private: - CopyResult copySurfaceIntoLegacy(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap); CopyResult copyImageInto(const sk_sp& image, const Rect& srcRect, SkBitmap* bitmap); bool copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect, diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index 55b1f23d294a..4f281fcde61d 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -88,6 +88,11 @@ struct { jmethodID onFrameComplete; } gFrameCompleteCallback; +struct { + jmethodID onCopyFinished; + jmethodID getDestinationBitmap; +} gCopyRequest; + static JNIEnv* getenv(JavaVM* vm) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { @@ -672,15 +677,41 @@ static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env, } } -static jint android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, - jobject clazz, jobject jsurface, jint left, jint top, - jint right, jint bottom, jlong bitmapPtr) { - SkBitmap bitmap; - bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); +class CopyRequestAdapter : public CopyRequest { +public: + CopyRequestAdapter(JavaVM* vm, jobject jCopyRequest, Rect srcRect) + : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {} + + virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override { + JNIEnv* env = getenv(mRefHolder.vm()); + jlong bitmapPtr = env->CallLongMethod( + mRefHolder.object(), gCopyRequest.getDestinationBitmap, srcWidth, srcHeight); + SkBitmap bitmap; + bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); + return bitmap; + } + + virtual void onCopyFinished(CopyResult result) override { + JNIEnv* env = getenv(mRefHolder.vm()); + env->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished, + static_cast(result)); + } + +private: + JGlobalRefHolder mRefHolder; +}; + +static void android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env, jobject clazz, + jobject jsurface, jint left, jint top, + jint right, jint bottom, + jobject jCopyRequest) { + JavaVM* vm = nullptr; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); + auto copyRequest = std::make_shared(vm, env->NewGlobalRef(jCopyRequest), + Rect(left, top, right, bottom)); ANativeWindow* window = fromSurface(env, jsurface); - jint result = RenderProxy::copySurfaceInto(window, left, top, right, bottom, &bitmap); + RenderProxy::copySurfaceInto(window, std::move(copyRequest)); ANativeWindow_release(window); - return result; } class ContextFactory : public IContextFactory { @@ -969,7 +1000,8 @@ static const JNINativeMethod gMethods[] = { (void*)android_view_ThreadedRenderer_setFrameCompleteCallback}, {"nAddObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_addObserver}, {"nRemoveObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_removeObserver}, - {"nCopySurfaceInto", "(Landroid/view/Surface;IIIIJ)I", + {"nCopySurfaceInto", + "(Landroid/view/Surface;IIIILandroid/graphics/HardwareRenderer$CopyRequest;)V", (void*)android_view_ThreadedRenderer_copySurfaceInto}, {"nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;", (void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode}, @@ -1042,6 +1074,11 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) { gFrameCompleteCallback.onFrameComplete = GetMethodIDOrDie(env, frameCompleteClass, "onFrameComplete", "()V"); + jclass copyRequest = FindClassOrDie(env, "android/graphics/HardwareRenderer$CopyRequest"); + gCopyRequest.onCopyFinished = GetMethodIDOrDie(env, copyRequest, "onCopyFinished", "(I)V"); + gCopyRequest.getDestinationBitmap = + GetMethodIDOrDie(env, copyRequest, "getDestinationBitmap", "(II)J"); + void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface"); LOG_ALWAYS_FATAL_IF(fromSurface == nullptr, diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index b2ba15cbe526..40a0bac3762d 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -364,12 +364,13 @@ void RenderProxy::setForceDark(bool enable) { mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); }); } -int RenderProxy::copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom, - SkBitmap* bitmap) { +void RenderProxy::copySurfaceInto(ANativeWindow* window, std::shared_ptr&& request) { auto& thread = RenderThread::getInstance(); - return static_cast(thread.queue().runSync([&]() -> auto { - return thread.readback().copySurfaceInto(window, Rect(left, top, right, bottom), bitmap); - })); + ANativeWindow_acquire(window); + thread.queue().post([&thread, window, request = std::move(request)] { + thread.readback().copySurfaceInto(window, request); + ANativeWindow_release(window); + }); } void RenderProxy::prepareToDraw(Bitmap& bitmap) { diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index bbfeeac19d94..2a99a736180f 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -19,13 +19,14 @@ #include #include -#include #include +#include #include #include "../FrameMetricsObserver.h" #include "../IContextFactory.h" #include "ColorMode.h" +#include "CopyRequest.h" #include "DrawFrameTask.h" #include "SwapBehavior.h" #include "hwui/Bitmap.h" @@ -137,8 +138,7 @@ public: void removeFrameMetricsObserver(FrameMetricsObserver* observer); void setForceDark(bool enable); - static int copySurfaceInto(ANativeWindow* window, int left, int top, int right, - int bottom, SkBitmap* bitmap); + static void copySurfaceInto(ANativeWindow* window, std::shared_ptr&& request); static void prepareToDraw(Bitmap& bitmap); static int copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap); diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp index 070a339a86a1..13a438199ae5 100644 --- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp +++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp @@ -25,17 +25,51 @@ class MagnifierAnimation; +using Rect = android::uirenderer::Rect; + static TestScene::Registrar _Magnifier(TestScene::Info{ "magnifier", "A sample magnifier using Readback", TestScene::simpleCreateScene}); +class BlockingCopyRequest : public CopyRequest { + sk_sp mDestination; + std::mutex mLock; + std::condition_variable mCondVar; + CopyResult mResult; + +public: + BlockingCopyRequest(::Rect rect, sk_sp bitmap) + : CopyRequest(rect), mDestination(bitmap) {} + + virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override { + SkBitmap bitmap; + mDestination->getSkBitmap(&bitmap); + return bitmap; + } + + virtual void onCopyFinished(CopyResult result) override { + std::unique_lock _lock{mLock}; + mResult = result; + mCondVar.notify_all(); + } + + CopyResult waitForResult() { + std::unique_lock _lock{mLock}; + mCondVar.wait(_lock); + return mResult; + } +}; + class MagnifierAnimation : public TestScene { public: sp card; sp zoomImageView; + sk_sp magnifier; + std::shared_ptr copyRequest; void createContent(int width, int height, Canvas& canvas) override { magnifier = TestUtils::createBitmap(200, 100); + setupCopyRequest(); SkBitmap temp; magnifier->getSkBitmap(&temp); temp.eraseColor(Color::White); @@ -65,19 +99,20 @@ public: canvas.enableZ(false); } + void setupCopyRequest() { + constexpr int x = 90; + constexpr int y = 325; + copyRequest = std::make_shared( + ::Rect(x, y, x + magnifier->width(), y + magnifier->height()), magnifier); + } + void doFrame(int frameNr) override { int curFrame = frameNr % 150; card->mutateStagingProperties().setTranslationX(curFrame); card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); if (renderTarget) { - SkBitmap temp; - magnifier->getSkBitmap(&temp); - constexpr int x = 90; - constexpr int y = 325; - RenderProxy::copySurfaceInto(renderTarget.get(), x, y, x + magnifier->width(), - y + magnifier->height(), &temp); + RenderProxy::copySurfaceInto(renderTarget.get(), copyRequest); + copyRequest->waitForResult(); } } - - sk_sp magnifier; }; diff --git a/libs/hwui/thread/WorkQueue.h b/libs/hwui/thread/WorkQueue.h index 46b8bc07b432..f2751d2a6cc7 100644 --- a/libs/hwui/thread/WorkQueue.h +++ b/libs/hwui/thread/WorkQueue.h @@ -57,7 +57,7 @@ private: public: WorkQueue(std::function&& wakeFunc, std::mutex& lock) - : mWakeFunc(move(wakeFunc)), mLock(lock) {} + : mWakeFunc(std::move(wakeFunc)), mLock(lock) {} void process() { auto now = clock::now(); -- cgit v1.2.3-59-g8ed1b From c93103c9ebb356eba4a58b2729c42f41142bbc3b Mon Sep 17 00:00:00 2001 From: John Reck Date: Thu, 4 Aug 2022 12:40:45 -0400 Subject: Remove Path's isSimplePath The optimization has bugs and is of questionable value given the current state of JNI. Just remove it. Fixes: 149585703 Test: uirendering CTS, Path graphics test Change-Id: I613e4854695ae6e4ad9ec8b17be44e856ed03860 --- .../src/android/graphics/PathOffsetTest.java | 91 -------------------- .../coretests/src/android/graphics/PathTest.java | 39 --------- graphics/java/android/graphics/BaseCanvas.java | 6 +- .../java/android/graphics/BaseRecordingCanvas.java | 6 +- graphics/java/android/graphics/Path.java | 97 +--------------------- 5 files changed, 5 insertions(+), 234 deletions(-) delete mode 100644 core/tests/coretests/src/android/graphics/PathOffsetTest.java delete mode 100644 core/tests/coretests/src/android/graphics/PathTest.java (limited to 'graphics/java/android') diff --git a/core/tests/coretests/src/android/graphics/PathOffsetTest.java b/core/tests/coretests/src/android/graphics/PathOffsetTest.java deleted file mode 100644 index 6cc42f6a3efc..000000000000 --- a/core/tests/coretests/src/android/graphics/PathOffsetTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.graphics; - -import static org.junit.Assert.assertTrue; - -import android.graphics.Bitmap.Config; -import android.graphics.Path.Direction; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class PathOffsetTest { - - private static final int SQUARE = 10; - private static final int WIDTH = 100; - private static final int HEIGHT = 100; - private static final int START_X = 10; - private static final int START_Y = 20; - private static final int OFFSET_X = 30; - private static final int OFFSET_Y = 40; - - @Test - @SmallTest - public void testPathOffset() { - Path actualPath = new Path(); - actualPath.addRect(START_X, START_Y, START_X + SQUARE, START_Y + SQUARE, Direction.CW); - assertTrue(actualPath.isSimplePath); - actualPath.offset(OFFSET_X, OFFSET_Y); - assertTrue(actualPath.isSimplePath); - - Path expectedPath = new Path(); - expectedPath.addRect(START_X + OFFSET_X, START_Y + OFFSET_Y, START_X + OFFSET_X + SQUARE, - START_Y + OFFSET_Y + SQUARE, Direction.CW); - - assertPaths(actualPath, expectedPath); - } - - @Test - @SmallTest - public void testPathOffsetWithDestination() { - Path initialPath = new Path(); - initialPath.addRect(START_X, START_Y, START_X + SQUARE, START_Y + SQUARE, Direction.CW); - Path actualPath = new Path(); - assertTrue(initialPath.isSimplePath); - assertTrue(actualPath.isSimplePath); - initialPath.offset(OFFSET_X, OFFSET_Y, actualPath); - assertTrue(actualPath.isSimplePath); - - Path expectedPath = new Path(); - expectedPath.addRect(START_X + OFFSET_X, START_Y + OFFSET_Y, START_X + OFFSET_X + SQUARE, - START_Y + OFFSET_Y + SQUARE, Direction.CW); - - assertPaths(actualPath, expectedPath); - } - - private static void assertPaths(Path actual, Path expected) { - Bitmap actualBitmap = drawAndGetBitmap(actual); - Bitmap expectedBitmap = drawAndGetBitmap(expected); - assertTrue(actualBitmap.sameAs(expectedBitmap)); - } - - private static Bitmap drawAndGetBitmap(Path path) { - Bitmap bitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Config.ARGB_8888); - bitmap.eraseColor(Color.BLACK); - Paint paint = new Paint(); - paint.setColor(Color.RED); - Canvas canvas = new Canvas(bitmap); - canvas.drawPath(path, paint); - return bitmap; - } - -} diff --git a/core/tests/coretests/src/android/graphics/PathTest.java b/core/tests/coretests/src/android/graphics/PathTest.java deleted file mode 100644 index b50792ca6b38..000000000000 --- a/core/tests/coretests/src/android/graphics/PathTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.graphics; - -import androidx.test.filters.SmallTest; - -import junit.framework.TestCase; - -public class PathTest extends TestCase { - - @SmallTest - public void testResetPreservesFillType() throws Exception { - Path path = new Path(); - - final Path.FillType defaultFillType = path.getFillType(); - final Path.FillType fillType = Path.FillType.INVERSE_EVEN_ODD; - - // This test is only meaningful if it changes from the default. - assertFalse(fillType.equals(defaultFillType)); - - path.setFillType(fillType); - path.reset(); - assertEquals(path.getFillType(), fillType); - } -} diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index 425a37891afb..a8ab6d9e494e 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -330,11 +330,7 @@ public abstract class BaseCanvas { public void drawPath(@NonNull Path path, @NonNull Paint paint) { throwIfHasHwFeaturesInSwMode(paint); - if (path.isSimplePath && path.rects != null) { - nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance()); - } else { - nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance()); - } + nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance()); } public void drawPoint(float x, float y, @NonNull Paint paint) { diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java index a998ba870f74..d06f665631cc 100644 --- a/graphics/java/android/graphics/BaseRecordingCanvas.java +++ b/graphics/java/android/graphics/BaseRecordingCanvas.java @@ -286,11 +286,7 @@ public class BaseRecordingCanvas extends Canvas { @Override public final void drawPath(@NonNull Path path, @NonNull Paint paint) { - if (path.isSimplePath && path.rects != null) { - nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance()); - } else { - nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance()); - } + nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance()); } @Override diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index e5ef10d1d555..afcaabebb9fb 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -20,8 +20,6 @@ import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Build; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -46,18 +44,6 @@ public class Path { */ public final long mNativePath; - /** - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public boolean isSimplePath = true; - /** - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public Region rects; - private Direction mLastDirection = null; - /** * Create an empty path */ @@ -72,15 +58,7 @@ public class Path { * @param src The path to copy from when initializing the new path */ public Path(@Nullable Path src) { - long valNative = 0; - if (src != null) { - valNative = src.mNativePath; - isSimplePath = src.isSimplePath; - if (src.rects != null) { - rects = new Region(src.rects); - } - } - mNativePath = nInit(valNative); + mNativePath = nInit(src != null ? src.mNativePath : 0); sRegistry.registerNativeAllocation(this, mNativePath); } @@ -89,9 +67,6 @@ public class Path { * This does NOT change the fill-type setting. */ public void reset() { - isSimplePath = true; - mLastDirection = null; - if (rects != null) rects.setEmpty(); // We promised not to change this, so preserve it around the native // call, which does now reset fill type. final FillType fillType = getFillType(); @@ -104,9 +79,6 @@ public class Path { * keeps the internal data structure for faster reuse. */ public void rewind() { - isSimplePath = true; - mLastDirection = null; - if (rects != null) rects.setEmpty(); nRewind(mNativePath); } @@ -116,19 +88,7 @@ public class Path { if (this == src) { return; } - isSimplePath = src.isSimplePath; nSet(mNativePath, src.mNativePath); - if (!isSimplePath) { - return; - } - - if (rects != null && src.rects != null) { - rects.set(src.rects); - } else if (rects != null && src.rects == null) { - rects.setEmpty(); - } else if (src.rects != null) { - rects = new Region(src.rects); - } } /** @@ -192,12 +152,7 @@ public class Path { * @see #op(Path, android.graphics.Path.Op) */ public boolean op(@NonNull Path path1, @NonNull Path path2, @NonNull Op op) { - if (nOp(path1.mNativePath, path2.mNativePath, op.ordinal(), this.mNativePath)) { - isSimplePath = false; - rects = null; - return true; - } - return false; + return nOp(path1.mNativePath, path2.mNativePath, op.ordinal(), this.mNativePath); } /** @@ -378,7 +333,6 @@ public class Path { * @param y The y-coordinate of the end of a line */ public void lineTo(float x, float y) { - isSimplePath = false; nLineTo(mNativePath, x, y); } @@ -393,7 +347,6 @@ public class Path { * this contour, to specify a line */ public void rLineTo(float dx, float dy) { - isSimplePath = false; nRLineTo(mNativePath, dx, dy); } @@ -408,7 +361,6 @@ public class Path { * @param y2 The y-coordinate of the end point on a quadratic curve */ public void quadTo(float x1, float y1, float x2, float y2) { - isSimplePath = false; nQuadTo(mNativePath, x1, y1, x2, y2); } @@ -427,7 +379,6 @@ public class Path { * this contour, for the end point of a quadratic curve */ public void rQuadTo(float dx1, float dy1, float dx2, float dy2) { - isSimplePath = false; nRQuadTo(mNativePath, dx1, dy1, dx2, dy2); } @@ -445,7 +396,6 @@ public class Path { */ public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { - isSimplePath = false; nCubicTo(mNativePath, x1, y1, x2, y2, x3, y3); } @@ -456,7 +406,6 @@ public class Path { */ public void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { - isSimplePath = false; nRCubicTo(mNativePath, x1, y1, x2, y2, x3, y3); } @@ -507,7 +456,6 @@ public class Path { */ public void arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo) { - isSimplePath = false; nArcTo(mNativePath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo); } @@ -516,7 +464,6 @@ public class Path { * first point of the contour, a line segment is automatically added. */ public void close() { - isSimplePath = false; nClose(mNativePath); } @@ -536,18 +483,6 @@ public class Path { final int nativeInt; } - private void detectSimplePath(float left, float top, float right, float bottom, Direction dir) { - if (mLastDirection == null) { - mLastDirection = dir; - } - if (mLastDirection != dir) { - isSimplePath = false; - } else { - if (rects == null) rects = new Region(); - rects.op((int) left, (int) top, (int) right, (int) bottom, Region.Op.UNION); - } - } - /** * Add a closed rectangle contour to the path * @@ -568,7 +503,6 @@ public class Path { * @param dir The direction to wind the rectangle's contour */ public void addRect(float left, float top, float right, float bottom, @NonNull Direction dir) { - detectSimplePath(left, top, right, bottom, dir); nAddRect(mNativePath, left, top, right, bottom, dir.nativeInt); } @@ -588,7 +522,6 @@ public class Path { * @param dir The direction to wind the oval's contour */ public void addOval(float left, float top, float right, float bottom, @NonNull Direction dir) { - isSimplePath = false; nAddOval(mNativePath, left, top, right, bottom, dir.nativeInt); } @@ -601,7 +534,6 @@ public class Path { * @param dir The direction to wind the circle's contour */ public void addCircle(float x, float y, float radius, @NonNull Direction dir) { - isSimplePath = false; nAddCircle(mNativePath, x, y, radius, dir.nativeInt); } @@ -624,7 +556,6 @@ public class Path { */ public void addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle) { - isSimplePath = false; nAddArc(mNativePath, left, top, right, bottom, startAngle, sweepAngle); } @@ -649,7 +580,6 @@ public class Path { */ public void addRoundRect(float left, float top, float right, float bottom, float rx, float ry, @NonNull Direction dir) { - isSimplePath = false; nAddRoundRect(mNativePath, left, top, right, bottom, rx, ry, dir.nativeInt); } @@ -682,7 +612,6 @@ public class Path { if (radii.length < 8) { throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values"); } - isSimplePath = false; nAddRoundRect(mNativePath, left, top, right, bottom, radii, dir.nativeInt); } @@ -693,7 +622,6 @@ public class Path { * @param dx The amount to translate the path in X as it is added */ public void addPath(@NonNull Path src, float dx, float dy) { - isSimplePath = false; nAddPath(mNativePath, src.mNativePath, dx, dy); } @@ -703,7 +631,6 @@ public class Path { * @param src The path that is appended to the current path */ public void addPath(@NonNull Path src) { - isSimplePath = false; nAddPath(mNativePath, src.mNativePath); } @@ -713,7 +640,6 @@ public class Path { * @param src The path to add as a new contour */ public void addPath(@NonNull Path src, @NonNull Matrix matrix) { - if (!src.isSimplePath) isSimplePath = false; nAddPath(mNativePath, src.mNativePath, matrix.ni()); } @@ -741,15 +667,6 @@ public class Path { * @param dy The amount in the Y direction to offset the entire path */ public void offset(float dx, float dy) { - if (isSimplePath && rects == null) { - // nothing to offset - return; - } - if (isSimplePath && dx == Math.rint(dx) && dy == Math.rint(dy)) { - rects.translate((int) dx, (int) dy); - } else { - isSimplePath = false; - } nOffset(mNativePath, dx, dy); } @@ -760,7 +677,6 @@ public class Path { * @param dy The new Y coordinate for the last point */ public void setLastPoint(float dx, float dy) { - isSimplePath = false; nSetLastPoint(mNativePath, dx, dy); } @@ -773,12 +689,7 @@ public class Path { * then the the original path is modified */ public void transform(@NonNull Matrix matrix, @Nullable Path dst) { - long dstNative = 0; - if (dst != null) { - dst.isSimplePath = false; - dstNative = dst.mNativePath; - } - nTransform(mNativePath, matrix.ni(), dstNative); + nTransform(mNativePath, matrix.ni(), dst != null ? dst.mNativePath : 0); } /** @@ -787,7 +698,6 @@ public class Path { * @param matrix The matrix to apply to the path */ public void transform(@NonNull Matrix matrix) { - isSimplePath = false; nTransform(mNativePath, matrix.ni()); } @@ -797,7 +707,6 @@ public class Path { } final long mutateNI() { - isSimplePath = false; return mNativePath; } -- cgit v1.2.3-59-g8ed1b From 29727ab34235335d65529880f2672605d04dff74 Mon Sep 17 00:00:00 2001 From: Kohsuke Yatoh Date: Thu, 4 Aug 2022 06:19:19 +0000 Subject: Remove unused Typeface.getFallback(). The usage was removed in commit d5ecababb4c65db2c7f7fa2d7e5b8ddbbae94cf1 Bug: 174672300 Test: m -j Change-Id: Ie7194d749c14a2c1b5c786bf4bce97bd0eb3a29e --- graphics/java/android/graphics/Typeface.java | 17 ----------------- libs/hwui/jni/Typeface.cpp | 14 -------------- 2 files changed, 31 deletions(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 8ae0d3d48d6d..25e1922fc38e 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -1557,16 +1557,6 @@ public class Typeface { return Arrays.binarySearch(mSupportedAxes, axis) >= 0; } - /** @hide */ - public List getFallback() { - ArrayList families = new ArrayList<>(); - int familySize = nativeGetFamilySize(native_instance); - for (int i = 0; i < familySize; ++i) { - families.add(new FontFamily(nativeGetFamily(native_instance, i))); - } - return families; - } - private static native long nativeCreateFromTypeface(long native_instance, int style); private static native long nativeCreateFromTypefaceWithExactStyle( long native_instance, int weight, boolean italic); @@ -1592,13 +1582,6 @@ public class Typeface { @CriticalNative private static native long nativeGetReleaseFunc(); - @CriticalNative - private static native int nativeGetFamilySize(long naitvePtr); - - @CriticalNative - private static native long nativeGetFamily(long nativePtr, int index); - - private static native void nativeRegisterGenericFamily(String str, long nativePtr); private static native int nativeWriteTypefaces( diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp index 6a7119b821a2..f5ed5689e4e8 100644 --- a/libs/hwui/jni/Typeface.cpp +++ b/libs/hwui/jni/Typeface.cpp @@ -380,18 +380,6 @@ static void Typeface_forceSetStaticFinalField(JNIEnv *env, jclass cls, jstring f env->SetStaticObjectField(cls, fid, typeface); } -// Critical Native -static jint Typeface_getFamilySize(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) { - return toTypeface(faceHandle)->fFontCollection->getFamilyCount(); -} - -// Critical Native -static jlong Typeface_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle, jint index) { - std::shared_ptr family = - toTypeface(faceHandle)->fFontCollection->getFamilyAt(index); - return reinterpret_cast(new FontFamilyWrapper(std::move(family))); -} - // Regular JNI static void Typeface_warmUpCache(JNIEnv* env, jobject, jstring jFilePath) { ScopedUtfChars filePath(env, jFilePath); @@ -431,8 +419,6 @@ static const JNINativeMethod gTypefaceMethods[] = { {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;I)[J", (void*)Typeface_readTypefaces}, {"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V", (void*)Typeface_forceSetStaticFinalField}, - {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize}, - {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily}, {"nativeWarmUpCache", "(Ljava/lang/String;)V", (void*)Typeface_warmUpCache}, {"nativeAddFontCollections", "(J)V", (void*)Typeface_addFontCollection}, {"nativeRegisterLocaleList", "(Ljava/lang/String;)V", (void*)Typeface_registerLocaleList}, -- cgit v1.2.3-59-g8ed1b From f81fbd3eb08adfbf10d40cdff6b87584e1adc131 Mon Sep 17 00:00:00 2001 From: joneckenrode Date: Fri, 12 Aug 2022 17:58:25 +0000 Subject: docs: Edited API documentation related to line-break and line-break word styles. Staged to: https://android.devsite.corp.google.com/reference/android/graphics/text/LineBreakConfig https://android.devsite.corp.google.com/reference/android/graphics/text/LineBreakConfig.Builder https://android.devsite.corp.google.com/reference/android/widget/TextView https://android.devsite.corp.google.com/reference/android/R.attr https://android.devsite.corp.google.com/reference/kotlin/android/R.attr Test: Built API docs Change-Id: I06fdf27ba94d806753a1dcc99873dff24cdae626 --- core/java/android/widget/TextView.java | 63 +++++++++++-------- core/res/res/values/attrs.xml | 16 ++--- .../android/graphics/text/LineBreakConfig.java | 72 ++++++++++++---------- 3 files changed, 86 insertions(+), 65 deletions(-) (limited to 'graphics/java/android') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3f87ec2beae5..c6ac0382fa32 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4887,20 +4887,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Set the line break style for text wrapping. + * Sets the line-break style for text wrapping. * - * The line break style to indicates the line break strategies can be used when - * calculating the text wrapping. The line break style affects rule-based breaking. It - * specifies the strictness of line-breaking rules. - * There are several types for the line break style: - * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}, - * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and - * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. The default values of the line break style - * is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, indicating no breaking rule is specified. - * See - * the line-break property + *

Line-break style specifies the line-break strategies that can be used + * for text wrapping. The line-break style affects rule-based line breaking + * by specifying the strictness of line-breaking rules.

* - * @param lineBreakStyle the line break style for the text. + *

The following are types of line-break styles:

+ * + * + *

The default line-break style is + * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no + * line-breaking rules are used.

+ * + *

See the + * + * line-break property for more information.

+ * + * @param lineBreakStyle The line break style for the text. */ public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) { if (mLineBreakStyle != lineBreakStyle) { @@ -4914,17 +4922,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Set the line break word style for text wrapping. + * Sets the line-break word style for text wrapping. + * + *

The line-break word style affects dictionary-based line breaking by + * providing phrase-based line-breaking opportunities. Use + * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify + * phrase-based line breaking.

+ * + *

The default line-break word style is + * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that + * no line-breaking word style is used.

* - * The line break word style affects dictionary-based breaking and provide phrase-based - * breaking opportunities. The type for the line break word style is - * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}. The default values of the line break - * word style is {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, indicating no breaking rule - * is specified. - * See - * the word-break property + *

See the + * + * word-break property for more information.

* - * @param lineBreakWordStyle the line break word style for the tet + * @param lineBreakWordStyle The line break word style for the text. */ public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { mUserSpeficiedLineBreakwordStyle = true; @@ -4939,18 +4952,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Get the current line break style for text wrapping. + * Gets the current line-break style for text wrapping. * - * @return the current line break style to be used for text wrapping. + * @return The line-break style to be used for text wrapping. */ public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() { return mLineBreakStyle; } /** - * Get the current line word break style for text wrapping. + * Gets the current line-break word style for text wrapping. * - * @return the current line break word style to be used for text wrapping. + * @return The line-break word style to be used for text wrapping. */ public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() { return mLineBreakWordStyle; diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index d43a6c59a477..c7153fcf8609 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5542,22 +5542,22 @@ ignores some hyphen character related typographic features, e.g. kerning. --> - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright (C) 2017 The Android Open Source Project + + + Sample Font + + + Regular + + + Sample Font + + + fallback + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + http://www.apache.org/licenses/LICENSE-2.0 + + + + + + + + + + + + + + + + diff --git a/core/tests/coretests/assets/fonts/fallback_capital.ttf b/core/tests/coretests/assets/fonts/fallback_capital.ttf new file mode 100644 index 000000000000..073761f08341 Binary files /dev/null and b/core/tests/coretests/assets/fonts/fallback_capital.ttf differ diff --git a/core/tests/coretests/assets/fonts/fallback_capital.ttx b/core/tests/coretests/assets/fonts/fallback_capital.ttx new file mode 100644 index 000000000000..710c95e091a7 --- /dev/null +++ b/core/tests/coretests/assets/fonts/fallback_capital.ttx @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright (C) 2017 The Android Open Source Project + + + Sample Font + + + Regular + + + Sample Font + + + fallback_capital + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + http://www.apache.org/licenses/LICENSE-2.0 + + + + + + + + + + + + + + + + diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java index 479e52aab029..d46f7625b596 100644 --- a/core/tests/coretests/src/android/graphics/FontListParserTest.java +++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java @@ -47,6 +47,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Collections; import java.util.List; @SmallTest @@ -59,14 +60,12 @@ public final class FontListParserTest { + "" + " test.ttf" + ""; - FontConfig.FontFamily expected = new FontConfig.FontFamily( - Arrays.asList( - new FontConfig.Font(new File("test.ttf"), null, "test", - new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), - 0, "", null)), - "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); - - FontConfig.FontFamily family = readFamily(xml); + FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList( + Collections.singletonList(new FontConfig.FontFamily( + Arrays.asList(new FontConfig.Font(new File("test.ttf"), null, "test", + new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)), + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif"); + FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -85,7 +84,7 @@ public final class FontListParserTest { new FontConfig.Font(new File("test.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", "serif")), - null, LocaleList.forLanguageTags("en"), VARIANT_DEFAULT); + LocaleList.forLanguageTags("en"), VARIANT_DEFAULT); FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); @@ -102,7 +101,7 @@ public final class FontListParserTest { new FontConfig.Font(new File("test.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)), - null, LocaleList.forLanguageTags("en"), VARIANT_COMPACT); + LocaleList.forLanguageTags("en"), VARIANT_COMPACT); FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); @@ -119,7 +118,7 @@ public final class FontListParserTest { new FontConfig.Font(new File("test.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)), - null, LocaleList.forLanguageTags("en"), VARIANT_ELEGANT); + LocaleList.forLanguageTags("en"), VARIANT_ELEGANT); FontConfig.FontFamily family = readFamily(xml); assertThat(family).isEqualTo(expected); @@ -133,19 +132,16 @@ public final class FontListParserTest { + " weight.ttf" + " italic.ttf" + ""; - FontConfig.FontFamily expected = new FontConfig.FontFamily( - Arrays.asList( - new FontConfig.Font(new File("normal.ttf"), null, "test", - new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), - 0, "", null), - new FontConfig.Font(new File("weight.ttf"), null, "test", - new FontStyle(100, FONT_SLANT_UPRIGHT), - 0, "", null), - new FontConfig.Font(new File("italic.ttf"), null, "test", - new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), - 0, "", null)), - "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); - FontConfig.FontFamily family = readFamily(xml); + FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList( + Collections.singletonList(new FontConfig.FontFamily(Arrays.asList( + new FontConfig.Font(new File("normal.ttf"), null, "test", + new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null), + new FontConfig.Font(new File("weight.ttf"), null, "test", + new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null), + new FontConfig.Font(new File("italic.ttf"), null, "test", + new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null)), + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif"); + FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -162,16 +158,17 @@ public final class FontListParserTest { + " " + " " + ""; - FontConfig.FontFamily expected = new FontConfig.FontFamily( - Arrays.asList( + FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList( + Collections.singletonList(new FontConfig.FontFamily(Arrays.asList( new FontConfig.Font(new File("test-VF.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "'wdth' 100.0,'wght' 200.0", null), new FontConfig.Font(new File("test-VF.ttf"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "'wdth' 400.0,'wght' 700.0", null)), - "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); - FontConfig.FontFamily family = readFamily(xml); + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), + "sans-serif"); + FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -182,16 +179,17 @@ public final class FontListParserTest { + " test.ttc" + " test.ttc" + ""; - FontConfig.FontFamily expected = new FontConfig.FontFamily( - Arrays.asList( + FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList( + Collections.singletonList(new FontConfig.FontFamily(Arrays.asList( new FontConfig.Font(new File("test.ttc"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null), new FontConfig.Font(new File("test.ttc"), null, "test", new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)), - "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); - FontConfig.FontFamily family = readFamily(xml); + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), + "sans-serif"); + FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -202,16 +200,14 @@ public final class FontListParserTest { + " test.ttc" + " test.ttc" + ""; - FontConfig.FontFamily expected = new FontConfig.FontFamily( - Arrays.asList( - new FontConfig.Font(new File("test.ttc"), null, "foo", - new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), - 0, "", null), - new FontConfig.Font(new File("test.ttc"), null, "test", - new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), - 1, "", null)), - "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT); - FontConfig.FontFamily family = readFamily(xml); + FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList( + Collections.singletonList(new FontConfig.FontFamily(Arrays.asList( + new FontConfig.Font(new File("test.ttc"), null, "foo", + new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null), + new FontConfig.Font(new File("test.ttc"), null, "test", + new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)), + LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif"); + FontConfig.NamedFamilyList family = readNamedFamily(xml); assertThat(family).isEqualTo(expected); } @@ -396,4 +392,14 @@ public final class FontListParserTest { parser.nextTag(); return FontListParser.readFamily(parser, "", null, true); } + + private FontConfig.NamedFamilyList readNamedFamily(String xml) + throws IOException, XmlPullParserException { + ByteArrayInputStream buffer = new ByteArrayInputStream( + xml.getBytes(StandardCharsets.UTF_8)); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(buffer, "UTF-8"); + parser.nextTag(); + return FontListParser.readNamedFamily(parser, "", null, true); + } } diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java index 3df2e90241ff..a8a5059eea20 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -28,6 +28,8 @@ import android.content.res.AssetManager; import android.graphics.fonts.FontCustomizationParser; import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; +import android.graphics.text.PositionedGlyphs; +import android.graphics.text.TextRunShaper; import android.text.FontConfig; import android.util.ArrayMap; @@ -64,6 +66,8 @@ public class TypefaceSystemFallbackTest { "b3em.ttf", // Supports "a","b","c". The width of "b" is 3em, others are 1em. "c3em.ttf", // Supports "a","b","c". The width of "c" is 3em, others are 1em. "all2em.ttf", // Supports "a,","b","c". All of them have the same width of 2em. + "fallback.ttf", // SUpports all small alphabets. + "fallback_capital.ttf", // SUpports all capital alphabets. "no_coverage.ttf", // This font doesn't support any characters. }; private static final String TEST_FONTS_XML; @@ -165,7 +169,10 @@ public class TypefaceSystemFallbackTest { Map updatableFontMap = new HashMap<>(); for (File file : new File(TEST_UPDATABLE_FONT_DIR).listFiles()) { - updatableFontMap.put(file.getName(), file); + final String fileName = file.getName(); + final int periodIndex = fileName.lastIndexOf("."); + final String psName = fileName.substring(0, periodIndex); + updatableFontMap.put(psName, file); } FontConfig fontConfig; @@ -710,6 +717,47 @@ public class TypefaceSystemFallbackTest { assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f); } + private String getFontName(Paint paint, String text) { + PositionedGlyphs glyphs = TextRunShaper.shapeTextRun( + text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint); + assertEquals(1, glyphs.glyphCount()); + return glyphs.getFont(0).getFile().getName(); + } + + @Test + public void testBuildSystemFallback__Customization_new_named_familyList() { + final String xml = "" + + "" + + " " + + " fallback_capital.ttf" + + " " + + ""; + final String oemXml = "" + + "" + + " " + + " " + + " b3em.ttf" + + " " + + " " + + " fallback.ttf" + + " " + + " " + + ""; + final ArrayMap fontMap = new ArrayMap<>(); + final ArrayMap fallbackMap = new ArrayMap<>(); + + buildSystemFallback(xml, oemXml, fontMap, fallbackMap); + + final Paint paint = new Paint(); + + Typeface testTypeface = fontMap.get("google-sans"); + assertNotNull(testTypeface); + paint.setTypeface(testTypeface); + assertEquals("b3em.ttf", getFontName(paint, "a")); + assertEquals("fallback.ttf", getFontName(paint, "x")); + assertEquals("fallback_capital.ttf", getFontName(paint, "A")); + } + @Test public void testBuildSystemFallback__Customization_new_named_family_override() { final String xml = "" diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 4bb16c6b8186..674246acafef 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -16,6 +16,8 @@ package android.graphics; +import static android.text.FontConfig.NamedFamilyList; + import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -36,6 +38,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -46,6 +49,7 @@ import java.util.regex.Pattern; * @hide */ public class FontListParser { + private static final String TAG = "FontListParser"; // XML constants for FontFamily. private static final String ATTR_NAME = "name"; @@ -148,27 +152,60 @@ public class FontListParser { boolean allowNonExistingFile) throws XmlPullParserException, IOException { List families = new ArrayList<>(); + List resultNamedFamilies = new ArrayList<>(); List aliases = new ArrayList<>(customization.getAdditionalAliases()); - Map oemNamedFamilies = + Map oemNamedFamilies = customization.getAdditionalNamedFamilies(); + boolean firstFamily = true; parser.require(XmlPullParser.START_TAG, null, "familyset"); while (keepReading(parser)) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { - FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap, - allowNonExistingFile); - if (family == null) { + final String name = parser.getAttributeValue(null, "name"); + if (name == null) { + FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap, + allowNonExistingFile); + if (family == null) { + continue; + } + families.add(family); + + } else { + FontConfig.NamedFamilyList namedFamilyList = readNamedFamily( + parser, fontDir, updatableFontMap, allowNonExistingFile); + if (namedFamilyList == null) { + continue; + } + if (!oemNamedFamilies.containsKey(name)) { + // The OEM customization overrides system named family. Skip if OEM + // customization XML defines the same named family. + resultNamedFamilies.add(namedFamilyList); + } + if (firstFamily) { + // The first font family is used as a fallback family as well. + families.addAll(namedFamilyList.getFamilies()); + } + } + firstFamily = false; + } else if (tag.equals("family-list")) { + FontConfig.NamedFamilyList namedFamilyList = readNamedFamilyList( + parser, fontDir, updatableFontMap, allowNonExistingFile); + if (namedFamilyList == null) { continue; } - String name = family.getName(); - if (name == null || !oemNamedFamilies.containsKey(name)) { + if (!oemNamedFamilies.containsKey(namedFamilyList.getName())) { // The OEM customization overrides system named family. Skip if OEM // customization XML defines the same named family. - families.add(family); + resultNamedFamilies.add(namedFamilyList); } + if (firstFamily) { + // The first font family is used as a fallback family as well. + families.addAll(namedFamilyList.getFamilies()); + } + firstFamily = false; } else if (tag.equals("alias")) { aliases.add(readAlias(parser)); } else { @@ -176,12 +213,12 @@ public class FontListParser { } } - families.addAll(oemNamedFamilies.values()); + resultNamedFamilies.addAll(oemNamedFamilies.values()); // Filters aliases that point to non-existing families. Set namedFamilies = new ArraySet<>(); - for (int i = 0; i < families.size(); ++i) { - String name = families.get(i).getName(); + for (int i = 0; i < resultNamedFamilies.size(); ++i) { + String name = resultNamedFamilies.get(i).getName(); if (name != null) { namedFamilies.add(name); } @@ -194,7 +231,8 @@ public class FontListParser { } } - return new FontConfig(families, filtered, lastModifiedDate, configVersion); + return new FontConfig(families, filtered, resultNamedFamilies, lastModifiedDate, + configVersion); } private static boolean keepReading(XmlPullParser parser) @@ -215,7 +253,6 @@ public class FontListParser { public static @Nullable FontConfig.FontFamily readFamily(XmlPullParser parser, String fontDir, @Nullable Map updatableFontMap, boolean allowNonExistingFile) throws XmlPullParserException, IOException { - final String name = parser.getAttributeValue(null, "name"); final String lang = parser.getAttributeValue("", "lang"); final String variant = parser.getAttributeValue(null, "variant"); final String ignore = parser.getAttributeValue(null, "ignore"); @@ -246,7 +283,68 @@ public class FontListParser { if (skip || fonts.isEmpty()) { return null; } - return new FontConfig.FontFamily(fonts, name, LocaleList.forLanguageTags(lang), intVariant); + return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant); + } + + private static void throwIfAttributeExists(String attrName, XmlPullParser parser) { + if (parser.getAttributeValue(null, attrName) != null) { + throw new IllegalArgumentException(attrName + " cannot be used in FontFamily inside " + + " family or family-list with name attribute."); + } + } + + /** + * Read a font family with name attribute as a single element family-list element. + */ + public static @Nullable FontConfig.NamedFamilyList readNamedFamily( + @NonNull XmlPullParser parser, @NonNull String fontDir, + @Nullable Map updatableFontMap, boolean allowNonExistingFile) + throws XmlPullParserException, IOException { + final String name = parser.getAttributeValue(null, "name"); + throwIfAttributeExists("lang", parser); + throwIfAttributeExists("variant", parser); + throwIfAttributeExists("ignore", parser); + + final FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap, + allowNonExistingFile); + if (family == null) { + return null; + } + return new NamedFamilyList(Collections.singletonList(family), name); + } + + /** + * Read a family-list element + */ + public static @Nullable FontConfig.NamedFamilyList readNamedFamilyList( + @NonNull XmlPullParser parser, @NonNull String fontDir, + @Nullable Map updatableFontMap, boolean allowNonExistingFile) + throws XmlPullParserException, IOException { + final String name = parser.getAttributeValue(null, "name"); + final List familyList = new ArrayList<>(); + while (keepReading(parser)) { + if (parser.getEventType() != XmlPullParser.START_TAG) continue; + final String tag = parser.getName(); + if (tag.equals("family")) { + throwIfAttributeExists("name", parser); + throwIfAttributeExists("lang", parser); + throwIfAttributeExists("variant", parser); + throwIfAttributeExists("ignore", parser); + + final FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap, + allowNonExistingFile); + if (family != null) { + familyList.add(family); + } + } else { + skip(parser); + } + } + + if (familyList.isEmpty()) { + return null; + } + return new FontConfig.NamedFamilyList(familyList, name); } /** Matches leading and trailing XML whitespace. */ diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java index df47f73eb04a..b458dd9021d0 100644 --- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -17,7 +17,7 @@ package android.graphics.fonts; import static android.text.FontConfig.Alias; -import static android.text.FontConfig.FontFamily; +import static android.text.FontConfig.NamedFamilyList; import android.annotation.NonNull; import android.annotation.Nullable; @@ -42,11 +42,14 @@ import java.util.Map; * @hide */ public class FontCustomizationParser { + private static final String TAG = "FontCustomizationParser"; + /** * Represents a customization XML */ public static class Result { - private final Map mAdditionalNamedFamilies; + private final Map mAdditionalNamedFamilies; + private final List mAdditionalAliases; public Result() { @@ -54,13 +57,13 @@ public class FontCustomizationParser { mAdditionalAliases = Collections.emptyList(); } - public Result(Map additionalNamedFamilies, + public Result(Map additionalNamedFamilies, List additionalAliases) { mAdditionalNamedFamilies = additionalNamedFamilies; mAdditionalAliases = additionalAliases; } - public Map getAdditionalNamedFamilies() { + public Map getAdditionalNamedFamilies() { return mAdditionalNamedFamilies; } @@ -85,20 +88,24 @@ public class FontCustomizationParser { return readFamilies(parser, fontDir, updatableFontMap); } - private static Map validateAndTransformToMap(List families) { - HashMap namedFamily = new HashMap<>(); + private static Result validateAndTransformToResult( + List families, List aliases) { + HashMap namedFamily = new HashMap<>(); for (int i = 0; i < families.size(); ++i) { - final FontFamily family = families.get(i); + final NamedFamilyList family = families.get(i); final String name = family.getName(); - if (name == null) { - throw new IllegalArgumentException("new-named-family requires name attribute"); - } - if (namedFamily.put(name, family) != null) { + if (name != null) { + if (namedFamily.put(name, family) != null) { + throw new IllegalArgumentException( + "new-named-family requires unique name attribute"); + } + } else { throw new IllegalArgumentException( - "new-named-family requires unique name attribute"); + "new-named-family requires name attribute or new-default-fallback-family" + + "requires fallackTarget attribute"); } } - return namedFamily; + return new Result(namedFamily, aliases); } private static Result readFamilies( @@ -106,7 +113,7 @@ public class FontCustomizationParser { @NonNull String fontDir, @Nullable Map updatableFontMap ) throws XmlPullParserException, IOException { - List families = new ArrayList<>(); + List families = new ArrayList<>(); List aliases = new ArrayList<>(); parser.require(XmlPullParser.START_TAG, null, "fonts-modification"); while (parser.next() != XmlPullParser.END_TAG) { @@ -114,19 +121,42 @@ public class FontCustomizationParser { String tag = parser.getName(); if (tag.equals("family")) { readFamily(parser, fontDir, families, updatableFontMap); + } else if (tag.equals("family-list")) { + readFamilyList(parser, fontDir, families, updatableFontMap); } else if (tag.equals("alias")) { aliases.add(FontListParser.readAlias(parser)); } else { FontListParser.skip(parser); } } - return new Result(validateAndTransformToMap(families), aliases); + return validateAndTransformToResult(families, aliases); } private static void readFamily( @NonNull XmlPullParser parser, @NonNull String fontDir, - @NonNull List out, + @NonNull List out, + @Nullable Map updatableFontMap) + throws XmlPullParserException, IOException { + final String customizationType = parser.getAttributeValue(null, "customizationType"); + if (customizationType == null) { + throw new IllegalArgumentException("customizationType must be specified"); + } + if (customizationType.equals("new-named-family")) { + NamedFamilyList fontFamily = FontListParser.readNamedFamily( + parser, fontDir, updatableFontMap, false); + if (fontFamily != null) { + out.add(fontFamily); + } + } else { + throw new IllegalArgumentException("Unknown customizationType=" + customizationType); + } + } + + private static void readFamilyList( + @NonNull XmlPullParser parser, + @NonNull String fontDir, + @NonNull List out, @Nullable Map updatableFontMap) throws XmlPullParserException, IOException { final String customizationType = parser.getAttributeValue(null, "customizationType"); @@ -134,7 +164,7 @@ public class FontCustomizationParser { throw new IllegalArgumentException("customizationType must be specified"); } if (customizationType.equals("new-named-family")) { - FontFamily fontFamily = FontListParser.readFamily( + NamedFamilyList fontFamily = FontListParser.readNamedFamilyList( parser, fontDir, updatableFontMap, false); if (fontFamily != null) { out.add(fontFamily); diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index a771a6e35345..bf79b1bedd8e 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -114,17 +114,19 @@ public final class FontFamily { * @return a font family */ public @NonNull FontFamily build() { - return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */); + return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */, + false /* isDefaultFallback */); } /** @hide */ public @NonNull FontFamily build(@NonNull String langTags, int variant, - boolean isCustomFallback) { + boolean isCustomFallback, boolean isDefaultFallback) { final long builderPtr = nInitBuilder(); for (int i = 0; i < mFonts.size(); ++i) { nAddFont(builderPtr, mFonts.get(i).getNativePtr()); } - final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback); + final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback, + isDefaultFallback); final FontFamily family = new FontFamily(ptr); sFamilyRegistory.registerNativeAllocation(family, ptr); return family; @@ -138,7 +140,7 @@ public final class FontFamily { @CriticalNative private static native void nAddFont(long builderPtr, long fontPtr); private static native long nBuild(long builderPtr, String langTags, int variant, - boolean isCustomFallback); + boolean isCustomFallback, boolean isDefaultFallback); @CriticalNative private static native long nGetReleaseNativeFamily(); } diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index 6278c0e23f27..ec8b2d66da7f 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -22,6 +22,7 @@ import android.graphics.FontListParser; import android.graphics.Typeface; import android.text.FontConfig; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -92,9 +93,8 @@ public final class SystemFonts { } private static void pushFamilyToFallback(@NonNull FontConfig.FontFamily xmlFamily, - @NonNull ArrayMap> fallbackMap, + @NonNull ArrayMap fallbackMap, @NonNull Map cache) { - final String languageTags = xmlFamily.getLocaleList().toLanguageTags(); final int variant = xmlFamily.getVariant(); @@ -118,26 +118,29 @@ public final class SystemFonts { } final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( - xmlFamily.getName(), defaultFonts, languageTags, variant, cache); + defaultFonts, languageTags, variant, false, cache); // Insert family into fallback map. for (int i = 0; i < fallbackMap.size(); i++) { - String name = fallbackMap.keyAt(i); + final String name = fallbackMap.keyAt(i); + final NativeFamilyListSet familyListSet = fallbackMap.valueAt(i); + if (familyListSet.seenXmlFamilies.contains(xmlFamily)) { + continue; + } else { + familyListSet.seenXmlFamilies.add(xmlFamily); + } final ArrayList fallback = specificFallbackFonts.get(name); if (fallback == null) { - String familyName = xmlFamily.getName(); - if (defaultFamily != null - // do not add myself to the fallback chain. - && (familyName == null || !familyName.equals(name))) { - fallbackMap.valueAt(i).add(defaultFamily); + if (defaultFamily != null) { + familyListSet.familyList.add(defaultFamily); } } else { - final FontFamily family = createFontFamily( - xmlFamily.getName(), fallback, languageTags, variant, cache); + final FontFamily family = createFontFamily(fallback, languageTags, variant, false, + cache); if (family != null) { - fallbackMap.valueAt(i).add(family); + familyListSet.familyList.add(family); } else if (defaultFamily != null) { - fallbackMap.valueAt(i).add(defaultFamily); + familyListSet.familyList.add(defaultFamily); } else { // There is no valid for for default fallback. Ignore. } @@ -145,10 +148,11 @@ public final class SystemFonts { } } - private static @Nullable FontFamily createFontFamily(@NonNull String familyName, + private static @Nullable FontFamily createFontFamily( @NonNull List fonts, @NonNull String languageTags, @FontConfig.FontFamily.Variant int variant, + boolean isDefaultFallback, @NonNull Map cache) { if (fonts.size() == 0) { return null; @@ -188,23 +192,30 @@ public final class SystemFonts { b.addFont(font); } } - return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */); + return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */, + isDefaultFallback); } - private static void appendNamedFamily(@NonNull FontConfig.FontFamily xmlFamily, + private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList, @NonNull ArrayMap bufferCache, - @NonNull ArrayMap> fallbackListMap) { - final String familyName = xmlFamily.getName(); - final FontFamily family = createFontFamily( - familyName, xmlFamily.getFontList(), - xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(), - bufferCache); - if (family == null) { - return; + @NonNull ArrayMap fallbackListMap) { + final String familyName = namedFamilyList.getName(); + final NativeFamilyListSet familyListSet = new NativeFamilyListSet(); + final List xmlFamilies = namedFamilyList.getFamilies(); + for (int i = 0; i < xmlFamilies.size(); ++i) { + FontConfig.FontFamily xmlFamily = xmlFamilies.get(i); + final FontFamily family = createFontFamily( + xmlFamily.getFontList(), + xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(), + true, // named family is always default + bufferCache); + if (family == null) { + return; + } + familyListSet.familyList.add(family); + familyListSet.seenXmlFamilies.add(xmlFamily); } - final ArrayList fallback = new ArrayList<>(); - fallback.add(family); - fallbackListMap.put(familyName, fallback); + fallbackListMap.put(familyName, familyListSet); } /** @@ -245,10 +256,12 @@ public final class SystemFonts { updatableFontMap, lastModifiedDate, configVersion); } catch (IOException e) { Log.e(TAG, "Failed to open/read system font configurations.", e); - return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0); + return new FontConfig(Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), 0, 0); } catch (XmlPullParserException e) { Log.e(TAG, "Failed to parse the system font configuration.", e); - return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0); + return new FontConfig(Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), 0, 0); } } @@ -261,37 +274,36 @@ public final class SystemFonts { return buildSystemFallback(fontConfig, new ArrayMap<>()); } + private static final class NativeFamilyListSet { + public List familyList = new ArrayList<>(); + public Set seenXmlFamilies = new ArraySet<>(); + } + /** @hide */ @VisibleForTesting public static Map buildSystemFallback(FontConfig fontConfig, ArrayMap outBufferCache) { - final Map fallbackMap = new ArrayMap<>(); - final List xmlFamilies = fontConfig.getFontFamilies(); - final ArrayMap> fallbackListMap = new ArrayMap<>(); - // First traverse families which have a 'name' attribute to create fallback map. - for (final FontConfig.FontFamily xmlFamily : xmlFamilies) { - final String familyName = xmlFamily.getName(); - if (familyName == null) { - continue; - } - appendNamedFamily(xmlFamily, outBufferCache, fallbackListMap); + final ArrayMap fallbackListMap = new ArrayMap<>(); + + final List namedFamilies = fontConfig.getNamedFamilyLists(); + for (int i = 0; i < namedFamilies.size(); ++i) { + FontConfig.NamedFamilyList namedFamilyList = namedFamilies.get(i); + appendNamedFamilyList(namedFamilyList, outBufferCache, fallbackListMap); } - // Then, add fallback fonts to the each fallback map. + // Then, add fallback fonts to the fallback map. + final List xmlFamilies = fontConfig.getFontFamilies(); for (int i = 0; i < xmlFamilies.size(); i++) { final FontConfig.FontFamily xmlFamily = xmlFamilies.get(i); - // The first family (usually the sans-serif family) is always placed immediately - // after the primary family in the fallback. - if (i == 0 || xmlFamily.getName() == null) { - pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); - } + pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache); } // Build the font map and fallback map. + final Map fallbackMap = new ArrayMap<>(); for (int i = 0; i < fallbackListMap.size(); i++) { final String fallbackName = fallbackListMap.keyAt(i); - final List familyList = fallbackListMap.valueAt(i); + final List familyList = fallbackListMap.valueAt(i).familyList; fallbackMap.put(fallbackName, familyList.toArray(new FontFamily[0])); } diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp index c146adac6b69..28e71d74e5b9 100644 --- a/libs/hwui/jni/FontFamily.cpp +++ b/libs/hwui/jni/FontFamily.cpp @@ -85,9 +85,9 @@ static jlong FontFamily_create(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr) { if (builder->fonts.empty()) { return 0; } - std::shared_ptr family = - minikin::FontFamily::create(builder->langId, builder->variant, - std::move(builder->fonts), true /* isCustomFallback */); + std::shared_ptr family = minikin::FontFamily::create( + builder->langId, builder->variant, std::move(builder->fonts), + true /* isCustomFallback */, false /* isDefaultFallback */); if (family->getCoverage().length() == 0) { return 0; } diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp index fbfc07e1f89d..897c4d71c0d5 100644 --- a/libs/hwui/jni/fonts/FontFamily.cpp +++ b/libs/hwui/jni/fonts/FontFamily.cpp @@ -57,7 +57,8 @@ static void FontFamily_Builder_addFont(CRITICAL_JNI_PARAMS_COMMA jlong builderPt // Regular JNI static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, - jstring langTags, jint variant, jboolean isCustomFallback) { + jstring langTags, jint variant, jboolean isCustomFallback, + jboolean isDefaultFallback) { std::unique_ptr builder(toBuilder(builderPtr)); uint32_t localeId; if (langTags == nullptr) { @@ -66,9 +67,9 @@ static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderP ScopedUtfChars str(env, langTags); localeId = minikin::registerLocaleList(str.c_str()); } - std::shared_ptr family = - minikin::FontFamily::create(localeId, static_cast(variant), - std::move(builder->fonts), isCustomFallback); + std::shared_ptr family = minikin::FontFamily::create( + localeId, static_cast(variant), std::move(builder->fonts), + isCustomFallback, isDefaultFallback); if (family->getCoverage().length() == 0) { // No coverage means minikin rejected given font for some reasons. jniThrowException(env, "java/lang/IllegalArgumentException", @@ -116,10 +117,10 @@ static jlong FontFamily_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr, jint /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gFontFamilyBuilderMethods[] = { - { "nInitBuilder", "()J", (void*) FontFamily_Builder_initBuilder }, - { "nAddFont", "(JJ)V", (void*) FontFamily_Builder_addFont }, - { "nBuild", "(JLjava/lang/String;IZ)J", (void*) FontFamily_Builder_build }, - { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc }, + {"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder}, + {"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont}, + {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build}, + {"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc}, }; static const JNINativeMethod gFontFamilyMethods[] = { diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java index 4cd0d6e42121..2ac283370886 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java @@ -163,19 +163,25 @@ public class FontManagerShellCommand extends ShellCommand { // Dump named font family first. List families = fontConfig.getFontFamilies(); - w.println("Named Font Families"); + // Dump FontFamilyList + w.println("Named Family List"); w.increaseIndent(); - for (int i = 0; i < families.size(); ++i) { - final FontConfig.FontFamily family = families.get(i); - - // Here, only dump the named family only. - if (family.getName() == null) continue; - - w.println("Named Family (" + family.getName() + ")"); - final List fonts = family.getFontList(); + List namedFamilyLists = fontConfig.getNamedFamilyLists(); + for (int i = 0; i < namedFamilyLists.size(); ++i) { + final FontConfig.NamedFamilyList namedFamilyList = namedFamilyLists.get(i); + w.println("Named Family (" + namedFamilyList.getName() + ")"); w.increaseIndent(); - for (int j = 0; j < fonts.size(); ++j) { - dumpSingleFontConfig(w, fonts.get(j)); + final List namedFamilies = namedFamilyList.getFamilies(); + for (int j = 0; j < namedFamilies.size(); ++j) { + final FontConfig.FontFamily family = namedFamilies.get(j); + + w.println("Family"); + final List fonts = family.getFontList(); + w.increaseIndent(); + for (int k = 0; k < fonts.size(); ++k) { + dumpSingleFontConfig(w, fonts.get(k)); + } + w.decreaseIndent(); } w.decreaseIndent(); } diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index 6f9360844542..a680f5001479 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -19,7 +19,6 @@ package com.android.server.graphics.fonts; import static com.android.server.graphics.fonts.FontManagerService.SystemFontException; import android.annotation.NonNull; -import android.annotation.Nullable; import android.graphics.fonts.FontManager; import android.graphics.fonts.FontUpdateRequest; import android.graphics.fonts.SystemFonts; @@ -44,6 +43,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -286,7 +286,7 @@ final class UpdatableFontDir { // Before processing font family update, check all family points the available fonts. for (FontUpdateRequest.Family family : familyMap.values()) { - if (resolveFontFiles(family) == null) { + if (resolveFontFilesForNamedFamily(family) == null) { throw new SystemFontException( FontManager.RESULT_ERROR_FONT_NOT_FOUND, "Required fonts are not available"); @@ -498,6 +498,19 @@ final class UpdatableFontDir { } } } + for (int i = 0; i < fontConfig.getNamedFamilyLists().size(); ++i) { + FontConfig.NamedFamilyList namedFamilyList = fontConfig.getNamedFamilyLists().get(i); + for (int j = 0; j < namedFamilyList.getFamilies().size(); ++j) { + FontConfig.FontFamily family = namedFamilyList.getFamilies().get(j); + for (int k = 0; k < family.getFontList().size(); ++k) { + FontConfig.Font font = family.getFontList().get(k); + if (font.getPostScriptName().equals(psName)) { + targetFont = font; + break; + } + } + } + } if (targetFont == null) { return -1; } @@ -553,8 +566,8 @@ final class UpdatableFontDir { } } - @Nullable - private FontConfig.FontFamily resolveFontFiles(FontUpdateRequest.Family fontFamily) { + private FontConfig.NamedFamilyList resolveFontFilesForNamedFamily( + FontUpdateRequest.Family fontFamily) { List fontList = fontFamily.getFonts(); List resolvedFonts = new ArrayList<>(fontList.size()); for (int i = 0; i < fontList.size(); i++) { @@ -567,8 +580,10 @@ final class UpdatableFontDir { resolvedFonts.add(new FontConfig.Font(info.mFile, null, info.getPostScriptName(), font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(), null)); } - return new FontConfig.FontFamily(resolvedFonts, fontFamily.getName(), + FontConfig.FontFamily family = new FontConfig.FontFamily(resolvedFonts, LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT); + return new FontConfig.NamedFamilyList(Collections.singletonList(family), + fontFamily.getName()); } Map getPostScriptMap() { @@ -585,23 +600,24 @@ final class UpdatableFontDir { PersistentSystemFontConfig.Config persistentConfig = readPersistentConfig(); List families = persistentConfig.fontFamilies; - List mergedFamilies = - new ArrayList<>(config.getFontFamilies().size() + families.size()); + List mergedFamilies = + new ArrayList<>(config.getNamedFamilyLists().size() + families.size()); // We should keep the first font family (config.getFontFamilies().get(0)) because it's used // as a fallback font. See SystemFonts.java. - mergedFamilies.addAll(config.getFontFamilies()); + mergedFamilies.addAll(config.getNamedFamilyLists()); // When building Typeface, a latter font family definition will override the previous font // family definition with the same name. An exception is config.getFontFamilies.get(0), // which will be used as a fallback font without being overridden. for (int i = 0; i < families.size(); ++i) { - FontConfig.FontFamily family = resolveFontFiles(families.get(i)); + FontConfig.NamedFamilyList family = resolveFontFilesForNamedFamily(families.get(i)); if (family != null) { mergedFamilies.add(family); } } return new FontConfig( - mergedFamilies, config.getAliases(), mLastModifiedMillis, mConfigVersion); + config.getFontFamilies(), config.getAliases(), mergedFamilies, mLastModifiedMillis, + mConfigVersion); } private PersistentSystemFontConfig.Config readPersistentConfig() { @@ -635,12 +651,12 @@ final class UpdatableFontDir { return mConfigVersion; } - public Map getFontFamilyMap() { + public Map getFontFamilyMap() { PersistentSystemFontConfig.Config curConfig = readPersistentConfig(); - Map familyMap = new HashMap<>(); + Map familyMap = new HashMap<>(); for (int i = 0; i < curConfig.fontFamilies.size(); ++i) { FontUpdateRequest.Family family = curConfig.fontFamilies.get(i); - FontConfig.FontFamily resolvedFamily = resolveFontFiles(family); + FontConfig.NamedFamilyList resolvedFamily = resolveFontFilesForNamedFamily(family); if (resolvedFamily != null) { familyMap.put(family.getName(), resolvedFamily); } diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index 68e5ebf027ad..e9a7d85ae755 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -210,7 +210,8 @@ public final class UpdatableFontDirTest { assertThat(mUpdatableFontFilesDir.list()).hasLength(2); assertNamedFamilyExists(dir.getSystemFontConfig(), "foobar"); assertThat(dir.getFontFamilyMap()).containsKey("foobar"); - FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar"); + assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1); + FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0); assertThat(foobar.getFontList()).hasSize(2); assertThat(foobar.getFontList().get(0).getFile()) .isEqualTo(dir.getPostScriptMap().get("foo")); @@ -326,10 +327,12 @@ public final class UpdatableFontDirTest { new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null); FontConfig.FontFamily family = new FontConfig.FontFamily( - Arrays.asList(fooFont, barFont), "sans-serif", null, + Arrays.asList(fooFont, barFont), null, FontConfig.FontFamily.VARIANT_DEFAULT); - return new FontConfig(Collections.singletonList(family), - Collections.emptyList(), 0, 1); + return new FontConfig(Collections.emptyList(), + Collections.emptyList(), + Collections.singletonList(new FontConfig.NamedFamilyList( + Collections.singletonList(family), "sans-serif")), 0, 1); }; UpdatableFontDir dirForPreparation = new UpdatableFontDir( @@ -411,7 +414,8 @@ public final class UpdatableFontDirTest { assertThat(dir.getPostScriptMap()).containsKey("foo"); assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1); assertThat(dir.getFontFamilyMap()).containsKey("foobar"); - FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar"); + assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1); + FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0); assertThat(foobar.getFontList()).hasSize(1); assertThat(foobar.getFontList().get(0).getFile()) .isEqualTo(dir.getPostScriptMap().get("foo")); @@ -485,10 +489,12 @@ public final class UpdatableFontDirTest { file, null, "bar", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null); FontConfig.FontFamily family = new FontConfig.FontFamily( - Collections.singletonList(font), "sans-serif", null, - FontConfig.FontFamily.VARIANT_DEFAULT); - return new FontConfig(Collections.singletonList(family), - Collections.emptyList(), 0, 1); + Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT); + return new FontConfig( + Collections.emptyList(), + Collections.emptyList(), + Collections.singletonList(new FontConfig.NamedFamilyList( + Collections.singletonList(family), "sans-serif")), 0, 1); }); dir.loadFontFileMap(); @@ -636,9 +642,10 @@ public final class UpdatableFontDirTest { file, null, "test", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null); FontConfig.FontFamily family = new FontConfig.FontFamily( - Collections.singletonList(font), "sans-serif", null, - FontConfig.FontFamily.VARIANT_DEFAULT); - return new FontConfig(Collections.singletonList(family), Collections.emptyList(), 0, 1); + Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT); + return new FontConfig(Collections.emptyList(), Collections.emptyList(), + Collections.singletonList(new FontConfig.NamedFamilyList( + Collections.singletonList(family), "sans-serif")), 0, 1); }); dir.loadFontFileMap(); @@ -873,13 +880,14 @@ public final class UpdatableFontDirTest { + ""))); assertThat(dir.getPostScriptMap()).containsKey("test"); assertThat(dir.getFontFamilyMap()).containsKey("test"); - FontConfig.FontFamily test = dir.getFontFamilyMap().get("test"); + assertThat(dir.getFontFamilyMap().get("test").getFamilies().size()).isEqualTo(1); + FontConfig.FontFamily test = dir.getFontFamilyMap().get("test").getFamilies().get(0); assertThat(test.getFontList()).hasSize(1); assertThat(test.getFontList().get(0).getFile()) .isEqualTo(dir.getPostScriptMap().get("test")); } - @Test + @Test(expected = IllegalArgumentException.class) public void addFontFamily_noName() throws Exception { UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, @@ -891,12 +899,7 @@ public final class UpdatableFontDirTest { newAddFontFamilyRequest("" + " test.ttf" + "")); - try { - dir.update(requests); - fail("Expect NullPointerException"); - } catch (NullPointerException e) { - // Expect - } + dir.update(requests); } @Test @@ -955,17 +958,16 @@ public final class UpdatableFontDirTest { dir.loadFontFileMap(); assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty(); FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0); - assertThat(firstFontFamily.getName()).isNotEmpty(); dir.update(Arrays.asList( newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE), - newAddFontFamilyRequest("" + newAddFontFamilyRequest("" + " test.ttf" + ""))); FontConfig fontConfig = dir.getSystemFontConfig(); assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty(); assertThat(fontConfig.getFontFamilies().get(0)).isEqualTo(firstFontFamily); - FontConfig.FontFamily updated = getLastFamily(fontConfig, firstFontFamily.getName()); + FontConfig.FontFamily updated = getLastFamily(fontConfig, "sans-serif"); assertThat(updated.getFontList()).hasSize(1); assertThat(updated.getFontList().get(0).getFile()) .isEqualTo(dir.getPostScriptMap().get("test")); @@ -1005,7 +1007,9 @@ public final class UpdatableFontDirTest { mParser.setInput(is, "UTF-8"); mParser.nextTag(); - FontConfig.FontFamily fontFamily = FontListParser.readFamily(mParser, "", null, true); + FontConfig.NamedFamilyList namedFamilyList = FontListParser.readNamedFamily( + mParser, "", null, true); + FontConfig.FontFamily fontFamily = namedFamilyList.getFamilies().get(0); List fonts = new ArrayList<>(); for (FontConfig.Font font : fontFamily.getFontList()) { String name = font.getFile().getName(); @@ -1014,7 +1018,8 @@ public final class UpdatableFontDirTest { psName, font.getStyle(), font.getTtcIndex(), font.getFontVariationSettings()); fonts.add(updateFont); } - FontUpdateRequest.Family family = new FontUpdateRequest.Family(fontFamily.getName(), fonts); + FontUpdateRequest.Family family = new FontUpdateRequest.Family( + namedFamilyList.getName(), fonts); return new FontUpdateRequest(family); } @@ -1035,18 +1040,18 @@ public final class UpdatableFontDirTest { // Returns the last family with the given name, which will be used for creating Typeface. private static FontConfig.FontFamily getLastFamily(FontConfig fontConfig, String familyName) { - List fontFamilies = fontConfig.getFontFamilies(); - for (int i = fontFamilies.size() - 1; i >= 0; i--) { - if (familyName.equals(fontFamilies.get(i).getName())) { - return fontFamilies.get(i); + List namedFamilyLists = fontConfig.getNamedFamilyLists(); + for (int i = namedFamilyLists.size() - 1; i >= 0; i--) { + if (familyName.equals(namedFamilyLists.get(i).getName())) { + return namedFamilyLists.get(i).getFamilies().get(0); } } return null; } private static void assertNamedFamilyExists(FontConfig fontConfig, String familyName) { - assertThat(fontConfig.getFontFamilies().stream() - .map(FontConfig.FontFamily::getName) + assertThat(fontConfig.getNamedFamilyLists().stream() + .map(FontConfig.NamedFamilyList::getName) .collect(Collectors.toSet())).contains(familyName); } } diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java index 650686ff3b85..fa5b7c15a6fe 100644 --- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java +++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java @@ -69,6 +69,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +import java.util.stream.Stream; /** * Tests if fonts can be updated by {@link FontManager} API. @@ -246,7 +247,10 @@ public class UpdatableSystemFontTest { @Test public void updateFontFamily() throws Exception { assertThat(updateNotoSerifAs("serif")).isEqualTo(FontManager.RESULT_SUCCESS); - FontConfig.FontFamily family = findFontFamilyOrThrow("serif"); + final FontConfig.NamedFamilyList namedFamilyList = findFontFamilyOrThrow("serif"); + assertThat(namedFamilyList.getFamilies().size()).isEqualTo(1); + final FontConfig.FontFamily family = namedFamilyList.getFamilies().get(0); + assertThat(family.getFontList()).hasSize(2); assertThat(family.getFontList().get(0).getPostScriptName()) .isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME); @@ -265,7 +269,10 @@ public class UpdatableSystemFontTest { public void updateFontFamily_asNewFont() throws Exception { assertThat(updateNotoSerifAs("UpdatableSystemFontTest-serif")) .isEqualTo(FontManager.RESULT_SUCCESS); - FontConfig.FontFamily family = findFontFamilyOrThrow("UpdatableSystemFontTest-serif"); + final FontConfig.NamedFamilyList namedFamilyList = + findFontFamilyOrThrow("UpdatableSystemFontTest-serif"); + assertThat(namedFamilyList.getFamilies().size()).isEqualTo(1); + final FontConfig.FontFamily family = namedFamilyList.getFamilies().get(0); assertThat(family.getFontList()).hasSize(2); assertThat(family.getFontList().get(0).getPostScriptName()) .isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME); @@ -434,9 +441,15 @@ public class UpdatableSystemFontTest { private String getFontPath(String psName) { FontConfig fontConfig = SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig); - return fontConfig.getFontFamilies().stream() + final List namedFamilies = fontConfig.getNamedFamilyLists().stream() + .flatMap(namedFamily -> namedFamily.getFamilies().stream()).toList(); + + return Stream.concat(fontConfig.getFontFamilies().stream(), namedFamilies.stream()) .flatMap(family -> family.getFontList().stream()) - .filter(font -> psName.equals(font.getPostScriptName())) + .filter(font -> { + Log.e("Debug", "PsName = " + font.getPostScriptName()); + return psName.equals(font.getPostScriptName()); + }) // Return the last match, because the latter family takes precedence if two families // have the same name. .reduce((first, second) -> second) @@ -445,10 +458,10 @@ public class UpdatableSystemFontTest { .getAbsolutePath(); } - private FontConfig.FontFamily findFontFamilyOrThrow(String familyName) { + private FontConfig.NamedFamilyList findFontFamilyOrThrow(String familyName) { FontConfig fontConfig = SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig); - return fontConfig.getFontFamilies().stream() + return fontConfig.getNamedFamilyLists().stream() .filter(family -> familyName.equals(family.getName())) // Return the last match, because the latter family takes precedence if two families // have the same name. -- cgit v1.2.3-59-g8ed1b From 741dcc52bd3e2953bc3ca4c2db1d266ade3663fa Mon Sep 17 00:00:00 2001 From: Emilian Peev Date: Mon, 10 Oct 2022 16:18:13 -0700 Subject: Camera: Add support for Jpeg/R Introduce support for the new Jpeg/R format that includes 10-bit recovery map. Bug: 241284696 Test: atest -c -d cts/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java#testJpegR Change-Id: I85f4cf130ab37b351595608943c92ce2844c40cb --- core/api/current.txt | 2 + core/java/android/hardware/DataSpace.java | 14 +++ .../hardware/camera2/CameraCharacteristics.java | 109 +++++++++++++++++++++ .../android/hardware/camera2/CameraDevice.java | 22 +++++ .../camera2/impl/CameraMetadataNative.java | 26 +++++ .../camera2/params/StreamConfigurationMap.java | 88 ++++++++++++++++- graphics/java/android/graphics/ImageFormat.java | 12 ++- media/java/android/media/ImageReader.java | 2 + media/java/android/media/ImageUtils.java | 3 + 9 files changed, 274 insertions(+), 4 deletions(-) (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index 877e2d6b54b4..0f9db81baab9 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -15032,6 +15032,7 @@ package android.graphics { field public static final int FLEX_RGB_888 = 41; // 0x29 field public static final int HEIC = 1212500294; // 0x48454946 field public static final int JPEG = 256; // 0x100 + field public static final int JPEG_R = 4101; // 0x1005 field public static final int NV16 = 16; // 0x10 field public static final int NV21 = 17; // 0x11 field public static final int PRIVATE = 34; // 0x22 @@ -17213,6 +17214,7 @@ package android.hardware { field public static final int DATASPACE_DYNAMIC_DEPTH = 4098; // 0x1002 field public static final int DATASPACE_HEIF = 4100; // 0x1004 field public static final int DATASPACE_JFIF = 146931712; // 0x8c20000 + field public static final int DATASPACE_JPEG_R = 4101; // 0x1005 field public static final int DATASPACE_SCRGB = 411107328; // 0x18810000 field public static final int DATASPACE_SCRGB_LINEAR = 406913024; // 0x18410000 field public static final int DATASPACE_SRGB = 142671872; // 0x8810000 diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java index 15eae0920e7d..0a145746d303 100644 --- a/core/java/android/hardware/DataSpace.java +++ b/core/java/android/hardware/DataSpace.java @@ -408,6 +408,19 @@ public final class DataSpace { */ public static final int DATASPACE_HEIF = 4100; + /** + * ISO/IEC TBD + * + * JPEG image with embedded recovery map following the Jpeg/R specification. + * + *

This value must always remain aligned with the public ImageFormat Jpeg/R definition and is + * valid with formats: + * HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to ISO/IEC TBD. + * The image contains a standard SDR JPEG and a recovery map. Jpeg/R decoders can use the + * map to recover the input image.

+ */ + public static final int DATASPACE_JPEG_R = 4101; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { @@ -626,6 +639,7 @@ public final class DataSpace { DATASPACE_DEPTH, DATASPACE_DYNAMIC_DEPTH, DATASPACE_HEIF, + DATASPACE_JPEG_R, DATASPACE_UNKNOWN, DATASPACE_SCRGB_LINEAR, DATASPACE_SRGB, diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 6e72b5f291f0..a6f7e945bef8 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -5562,6 +5562,115 @@ public final class CameraCharacteristics extends CameraMetadata AUTOMOTIVE_LOCATION = new Key("android.automotive.location", int.class); + /** + *

The available Jpeg/R stream + * configurations that this camera device supports + * (i.e. format, width, height, output/input stream).

+ *

The configurations are listed as (format, width, height, input?) tuples.

+ *

If the camera device supports Jpeg/R, it will support the same stream combinations with + * Jpeg/R as it does with P010. The stream combinations with Jpeg/R (or P010) supported + * by the device is determined by the device's hardware level and capabilities.

+ *

All the static, control, and dynamic metadata tags related to JPEG apply to Jpeg/R formats. + * Configuring JPEG and Jpeg/R streams at the same time is not supported.

+ *

Optional - The value for this key may be {@code null} on some devices.

+ *

Limited capability - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key

+ * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + public static final Key JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS = + new Key("android.jpegr.availableJpegRStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + *

This lists the minimum frame duration for each + * format/size combination for Jpeg/R output formats.

+ *

This should correspond to the frame duration when only that + * stream is active, with all processing (typically in android.*.mode) + * set to either OFF or FAST.

+ *

When multiple streams are used in a request, the minimum frame + * duration will be max(individual stream min durations).

+ *

See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and + * android.scaler.availableStallDurations for more details about + * calculating the max frame rate.

+ *

Units: (format, width, height, ns) x n

+ *

Optional - The value for this key may be {@code null} on some devices.

+ *

Limited capability - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key

+ * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @hide + */ + public static final Key JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS = + new Key("android.jpegr.availableJpegRMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + *

This lists the maximum stall duration for each + * output format/size combination for Jpeg/R streams.

+ *

A stall duration is how much extra time would get added + * to the normal minimum frame duration for a repeating request + * that has streams with non-zero stall.

+ *

This functions similarly to + * android.scaler.availableStallDurations for Jpeg/R + * streams.

+ *

All Jpeg/R output stream formats may have a nonzero stall + * duration.

+ *

Units: (format, width, height, ns) x n

+ *

Optional - The value for this key may be {@code null} on some devices.

+ *

Limited capability - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key

+ * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + public static final Key JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS = + new Key("android.jpegr.availableJpegRStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + *

The available Jpeg/R stream + * configurations that this camera device supports + * (i.e. format, width, height, output/input stream).

+ *

Refer to android.jpegr.availableJpegRStreamConfigurations for details.

+ *

Optional - The value for this key may be {@code null} on some devices.

+ * @hide + */ + public static final Key JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION = + new Key("android.jpegr.availableJpegRStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + *

This lists the minimum frame duration for each + * format/size combination for Jpeg/R output formats for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.

+ *

Refer to android.jpegr.availableJpegRMinFrameDurations for details.

+ *

Units: (format, width, height, ns) x n

+ *

Optional - The value for this key may be {@code null} on some devices.

+ * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION = + new Key("android.jpegr.availableJpegRMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + *

This lists the maximum stall duration for each + * output format/size combination for Jpeg/R streams for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.

+ *

Refer to android.jpegr.availableJpegRStallDurations for details.

+ *

Units: (format, width, height, ns) x n

+ *

Optional - The value for this key may be {@code null} on some devices.

+ * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION = + new Key("android.jpegr.availableJpegRStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 10a7538cf488..bf2e56359785 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -859,6 +859,28 @@ public abstract class CameraDevice implements AutoCloseable { * format {@link android.graphics.ImageFormat#YUV_420_888} with a 10-bit profile * will cause a capture session initialization failure. *

+ *

{@link android.graphics.ImageFormat#JPEG_R} may also be supported if advertised by + * {@link android.hardware.camera2.params.StreamConfigurationMap}. When initializing a capture + * session that includes a Jpeg/R camera output clients must consider the following items w.r.t. + * the 10-bit mandatory stream combination table: + * + *

    + *
  • To generate the compressed Jpeg/R image a single + * {@link android.graphics.ImageFormat#YCBCR_P010} output will be used internally by + * the camera device.
  • + *
  • On camera devices that are able to support concurrent 10 and 8-bit capture requests + * see {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints} + * an extra {@link android.graphics.ImageFormat#JPEG} will also + * be configured internally to help speed up the encoding process.
  • + *
+ * + * Jpeg/R camera outputs will typically be able to support the MAXIMUM device resolution. + * Clients can also call {@link StreamConfigurationMap#getOutputSizes(int)} for a complete list + * supported sizes. + * Camera clients that register a Jpeg/R output within a stream combination that doesn't fit + * in the mandatory stream table above can call + * {@link CameraDevice#isSessionConfigurationSupported} to ensure that this particular + * configuration is supported.

* *

Devices with the STREAM_USE_CASE capability ({@link * CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes {@link diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 012fad52fddf..9a164743bc8d 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -1351,6 +1351,9 @@ public class CameraMetadataNative implements Parcelable { /*heicconfiguration*/ null, /*heicminduration*/ null, /*heicstallduration*/ null, + /*jpegRconfiguration*/ null, + /*jpegRminduration*/ null, + /*jpegRstallduration*/ null, /*highspeedvideoconfigurations*/ null, /*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]); break; @@ -1365,6 +1368,9 @@ public class CameraMetadataNative implements Parcelable { /*heicconfiguration*/ null, /*heicminduration*/ null, /*heicstallduration*/ null, + /*jpegRconfiguration*/ null, + /*jpegRminduration*/ null, + /*jpegRstallduration*/ null, highSpeedVideoConfigurations, /*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]); break; @@ -1379,6 +1385,9 @@ public class CameraMetadataNative implements Parcelable { /*heicconfiguration*/ null, /*heicminduration*/ null, /*heicstallduration*/ null, + /*jpegRconfiguration*/ null, + /*jpegRminduration*/ null, + /*jpegRstallduration*/ null, /*highSpeedVideoConfigurations*/ null, inputOutputFormatsMap, listHighResolution, supportsPrivate[i]); break; @@ -1393,6 +1402,9 @@ public class CameraMetadataNative implements Parcelable { /*heicconfiguration*/ null, /*heicminduration*/ null, /*heicstallduration*/ null, + /*jpegRconfiguration*/ null, + /*jpegRminduration*/ null, + /*jpegRstallduration*/ null, /*highSpeedVideoConfigurations*/ null, /*inputOutputFormatsMap*/ null, listHighResolution, supportsPrivate[i]); } @@ -1546,6 +1558,12 @@ public class CameraMetadataNative implements Parcelable { CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS); StreamConfigurationDuration[] heicStallDurations = getBase( CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS); + StreamConfiguration[] jpegRConfigurations = getBase( + CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS); + StreamConfigurationDuration[] jpegRMinFrameDurations = getBase( + CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS); + StreamConfigurationDuration[] jpegRStallDurations = getBase( + CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS); HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase( CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS); ReprocessFormatsMap inputOutputFormatsMap = getBase( @@ -1557,6 +1575,7 @@ public class CameraMetadataNative implements Parcelable { dynamicDepthConfigurations, dynamicDepthMinFrameDurations, dynamicDepthStallDurations, heicConfigurations, heicMinFrameDurations, heicStallDurations, + jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution); } @@ -1589,6 +1608,12 @@ public class CameraMetadataNative implements Parcelable { CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION); StreamConfigurationDuration[] heicStallDurations = getBase( CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS_MAXIMUM_RESOLUTION); + StreamConfiguration[] jpegRConfigurations = getBase( + CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION); + StreamConfigurationDuration[] jpegRMinFrameDurations = getBase( + CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION); + StreamConfigurationDuration[] jpegRStallDurations = getBase( + CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION); HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase( CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS_MAXIMUM_RESOLUTION); ReprocessFormatsMap inputOutputFormatsMap = getBase( @@ -1601,6 +1626,7 @@ public class CameraMetadataNative implements Parcelable { dynamicDepthConfigurations, dynamicDepthMinFrameDurations, dynamicDepthStallDurations, heicConfigurations, heicMinFrameDurations, heicStallDurations, + jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution, false); } diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index 5981d279227d..cb678b98a998 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -26,6 +26,7 @@ import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.utils.HashCodeHelpers; import android.hardware.camera2.utils.SurfaceUtils; +import android.util.Log; import android.util.Range; import android.util.Size; import android.util.SparseIntArray; @@ -95,6 +96,11 @@ public final class StreamConfigurationMap { * {@link StreamConfigurationDuration} * @param heicStallDurations a non-{@code null} array of heic * {@link StreamConfigurationDuration} + * @param jpegRConfigurations a non-{@code null} array of Jpeg/R {@link StreamConfiguration} + * @param jpegRMinFrameDurations a non-{@code null} array of Jpeg/R + * {@link StreamConfigurationDuration} + * @param jpegRStallDurations a non-{@code null} array of Jpeg/R + * {@link StreamConfigurationDuration} * @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if * camera device does not support high speed video recording * @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE @@ -117,6 +123,9 @@ public final class StreamConfigurationMap { StreamConfiguration[] heicConfigurations, StreamConfigurationDuration[] heicMinFrameDurations, StreamConfigurationDuration[] heicStallDurations, + StreamConfiguration[] jpegRConfigurations, + StreamConfigurationDuration[] jpegRMinFrameDurations, + StreamConfigurationDuration[] jpegRStallDurations, HighSpeedVideoConfiguration[] highSpeedVideoConfigurations, ReprocessFormatsMap inputOutputFormatsMap, boolean listHighResolution) { @@ -125,6 +134,7 @@ public final class StreamConfigurationMap { dynamicDepthConfigurations, dynamicDepthMinFrameDurations, dynamicDepthStallDurations, heicConfigurations, heicMinFrameDurations, heicStallDurations, + jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution, /*enforceImplementationDefined*/ true); } @@ -154,6 +164,11 @@ public final class StreamConfigurationMap { * {@link StreamConfigurationDuration} * @param heicStallDurations a non-{@code null} array of heic * {@link StreamConfigurationDuration} + * @param jpegRConfigurations a non-{@code null} array of Jpeg/R {@link StreamConfiguration} + * @param jpegRMinFrameDurations a non-{@code null} array of Jpeg/R + * {@link StreamConfigurationDuration} + * @param jpegRStallDurations a non-{@code null} array of Jpeg/R + * {@link StreamConfigurationDuration} * @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if * camera device does not support high speed video recording * @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE @@ -178,6 +193,9 @@ public final class StreamConfigurationMap { StreamConfiguration[] heicConfigurations, StreamConfigurationDuration[] heicMinFrameDurations, StreamConfigurationDuration[] heicStallDurations, + StreamConfiguration[] jpegRConfigurations, + StreamConfigurationDuration[] jpegRMinFrameDurations, + StreamConfigurationDuration[] jpegRStallDurations, HighSpeedVideoConfiguration[] highSpeedVideoConfigurations, ReprocessFormatsMap inputOutputFormatsMap, boolean listHighResolution, @@ -242,6 +260,20 @@ public final class StreamConfigurationMap { "heicStallDurations"); } + + if (jpegRConfigurations == null) { + mJpegRConfigurations = new StreamConfiguration[0]; + mJpegRMinFrameDurations = new StreamConfigurationDuration[0]; + mJpegRStallDurations = new StreamConfigurationDuration[0]; + } else { + mJpegRConfigurations = checkArrayElementsNotNull(jpegRConfigurations, + "jpegRConfigurations"); + mJpegRMinFrameDurations = checkArrayElementsNotNull(jpegRMinFrameDurations, + "jpegRFrameDurations"); + mJpegRStallDurations = checkArrayElementsNotNull(jpegRStallDurations, + "jpegRStallDurations"); + } + if (highSpeedVideoConfigurations == null) { mHighSpeedVideoConfigurations = new HighSpeedVideoConfiguration[0]; } else { @@ -305,6 +337,17 @@ public final class StreamConfigurationMap { mHeicOutputFormats.get(config.getFormat()) + 1); } + // For each Jpeg/R format, track how many sizes there are available to configure + for (StreamConfiguration config : mJpegRConfigurations) { + if (!config.isOutput()) { + // Ignoring input Jpeg/R configs + continue; + } + + mJpegROutputFormats.put(config.getFormat(), + mJpegROutputFormats.get(config.getFormat()) + 1); + } + if (configurations != null && enforceImplementationDefined && mOutputFormats.indexOfKey(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) < 0) { throw new AssertionError( @@ -447,6 +490,8 @@ public final class StreamConfigurationMap { return mDynamicDepthOutputFormats.indexOfKey(internalFormat) >= 0; } else if (dataspace == HAL_DATASPACE_HEIF) { return mHeicOutputFormats.indexOfKey(internalFormat) >= 0; + } else if (dataspace == HAL_DATASPACE_JPEG_R) { + return mJpegROutputFormats.indexOfKey(internalFormat) >= 0; } else { return getFormatsMap(/*output*/true).indexOfKey(internalFormat) >= 0; } @@ -561,6 +606,7 @@ public final class StreamConfigurationMap { surfaceDataspace == HAL_DATASPACE_DEPTH ? mDepthConfigurations : surfaceDataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthConfigurations : surfaceDataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations : + surfaceDataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations : mConfigurations; for (StreamConfiguration config : configs) { if (config.getFormat() == surfaceFormat && config.isOutput()) { @@ -597,6 +643,7 @@ public final class StreamConfigurationMap { dataspace == HAL_DATASPACE_DEPTH ? mDepthConfigurations : dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthConfigurations : dataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations : + dataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations : mConfigurations; for (StreamConfiguration config : configs) { if ((config.getFormat() == internalFormat) && config.isOutput() && @@ -1120,6 +1167,9 @@ public final class StreamConfigurationMap { Arrays.equals(mHeicConfigurations, other.mHeicConfigurations) && Arrays.equals(mHeicMinFrameDurations, other.mHeicMinFrameDurations) && Arrays.equals(mHeicStallDurations, other.mHeicStallDurations) && + Arrays.equals(mJpegRConfigurations, other.mJpegRConfigurations) && + Arrays.equals(mJpegRMinFrameDurations, other.mJpegRMinFrameDurations) && + Arrays.equals(mJpegRStallDurations, other.mJpegRStallDurations) && Arrays.equals(mHighSpeedVideoConfigurations, other.mHighSpeedVideoConfigurations); } @@ -1138,6 +1188,7 @@ public final class StreamConfigurationMap { mDynamicDepthConfigurations, mDynamicDepthMinFrameDurations, mDynamicDepthStallDurations, mHeicConfigurations, mHeicMinFrameDurations, mHeicStallDurations, + mJpegRConfigurations, mJpegRMinFrameDurations, mJpegRStallDurations, mHighSpeedVideoConfigurations); } @@ -1161,6 +1212,10 @@ public final class StreamConfigurationMap { if (mHeicOutputFormats.indexOfKey(internalFormat) >= 0) { return format; } + } else if (internalDataspace == HAL_DATASPACE_JPEG_R) { + if (mJpegROutputFormats.indexOfKey(internalFormat) >= 0) { + return format; + } } else { if (mAllOutputFormats.indexOfKey(internalFormat) >= 0) { return format; @@ -1365,6 +1420,7 @@ public final class StreamConfigurationMap { *

  • ImageFormat.DEPTH_POINT_CLOUD => HAL_PIXEL_FORMAT_BLOB *
  • ImageFormat.DEPTH_JPEG => HAL_PIXEL_FORMAT_BLOB *
  • ImageFormat.HEIC => HAL_PIXEL_FORMAT_BLOB + *
  • ImageFormat.JPEG_R => HAL_PIXEL_FORMAT_BLOB *
  • ImageFormat.DEPTH16 => HAL_PIXEL_FORMAT_Y16 * *

    @@ -1391,6 +1447,7 @@ public final class StreamConfigurationMap { case ImageFormat.DEPTH_POINT_CLOUD: case ImageFormat.DEPTH_JPEG: case ImageFormat.HEIC: + case ImageFormat.JPEG_R: return HAL_PIXEL_FORMAT_BLOB; case ImageFormat.DEPTH16: return HAL_PIXEL_FORMAT_Y16; @@ -1414,6 +1471,7 @@ public final class StreamConfigurationMap { *
  • ImageFormat.DEPTH16 => HAL_DATASPACE_DEPTH *
  • ImageFormat.DEPTH_JPEG => HAL_DATASPACE_DYNAMIC_DEPTH *
  • ImageFormat.HEIC => HAL_DATASPACE_HEIF + *
  • ImageFormat.JPEG_R => HAL_DATASPACE_JPEG_R *
  • others => HAL_DATASPACE_UNKNOWN * *

    @@ -1448,6 +1506,8 @@ public final class StreamConfigurationMap { return HAL_DATASPACE_DYNAMIC_DEPTH; case ImageFormat.HEIC: return HAL_DATASPACE_HEIF; + case ImageFormat.JPEG_R: + return HAL_DATASPACE_JPEG_R; default: return HAL_DATASPACE_UNKNOWN; } @@ -1500,14 +1560,15 @@ public final class StreamConfigurationMap { dataspace == HAL_DATASPACE_DEPTH ? mDepthOutputFormats : dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthOutputFormats : dataspace == HAL_DATASPACE_HEIF ? mHeicOutputFormats : + dataspace == HAL_DATASPACE_JPEG_R ? mJpegROutputFormats : highRes ? mHighResOutputFormats : mOutputFormats; int sizesCount = formatsMap.get(format); - if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH || + if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH || dataspace == HAL_DATASPACE_JPEG_R || dataspace == HAL_DATASPACE_DYNAMIC_DEPTH || dataspace == HAL_DATASPACE_HEIF)) && sizesCount == 0) || - (output && (dataspace != HAL_DATASPACE_DEPTH && + (output && (dataspace != HAL_DATASPACE_DEPTH && dataspace != HAL_DATASPACE_JPEG_R && dataspace != HAL_DATASPACE_DYNAMIC_DEPTH && dataspace != HAL_DATASPACE_HEIF) && mAllOutputFormats.get(format) == 0)) { @@ -1521,11 +1582,13 @@ public final class StreamConfigurationMap { (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations : (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations : + (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations : mConfigurations; StreamConfigurationDuration[] minFrameDurations = (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations : (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations : + (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations : mMinFrameDurations; for (StreamConfiguration config : configurations) { @@ -1555,7 +1618,7 @@ public final class StreamConfigurationMap { // Dynamic depth streams can have both fast and also high res modes. if ((sizeIndex != sizesCount) && (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH || - dataspace == HAL_DATASPACE_HEIF)) { + dataspace == HAL_DATASPACE_HEIF) || (dataspace == HAL_DATASPACE_JPEG_R)) { if (sizeIndex > sizesCount) { throw new AssertionError( @@ -1598,6 +1661,9 @@ public final class StreamConfigurationMap { if (mHeicOutputFormats.size() > 0) { formats[i++] = ImageFormat.HEIC; } + if (mJpegROutputFormats.size() > 0) { + formats[i++] = ImageFormat.JPEG_R; + } } if (formats.length != i) { throw new AssertionError("Too few formats " + i + ", expected " + formats.length); @@ -1644,12 +1710,14 @@ public final class StreamConfigurationMap { (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations : + (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations : mMinFrameDurations; case DURATION_STALL: return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthStallDurations : (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthStallDurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicStallDurations : + (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRStallDurations : mStallDurations; default: throw new IllegalArgumentException("duration was invalid"); @@ -1664,6 +1732,7 @@ public final class StreamConfigurationMap { size += mDepthOutputFormats.size(); size += mDynamicDepthOutputFormats.size(); size += mHeicOutputFormats.size(); + size += mJpegROutputFormats.size(); } return size; @@ -1688,6 +1757,7 @@ public final class StreamConfigurationMap { (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations : (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations : (dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations : + (dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations : mConfigurations; for (int i = 0; i < configurations.length; i++) { @@ -1908,6 +1978,8 @@ public final class StreamConfigurationMap { return "PRIVATE"; case ImageFormat.HEIC: return "HEIC"; + case ImageFormat.JPEG_R: + return "JPEG/R"; default: return "UNKNOWN"; } @@ -1948,6 +2020,10 @@ public final class StreamConfigurationMap { * @hide */ public static final int HAL_DATASPACE_HEIF = 0x1003; + /** + * @hide + */ + public static final int HAL_DATASPACE_JPEG_R = 0x1005; private static final long DURATION_20FPS_NS = 50000000L; /** * @see #getDurations(int, int) @@ -1971,6 +2047,10 @@ public final class StreamConfigurationMap { private final StreamConfigurationDuration[] mHeicMinFrameDurations; private final StreamConfigurationDuration[] mHeicStallDurations; + private final StreamConfiguration[] mJpegRConfigurations; + private final StreamConfigurationDuration[] mJpegRMinFrameDurations; + private final StreamConfigurationDuration[] mJpegRStallDurations; + private final HighSpeedVideoConfiguration[] mHighSpeedVideoConfigurations; private final ReprocessFormatsMap mInputOutputFormatsMap; @@ -1992,6 +2072,8 @@ public final class StreamConfigurationMap { private final SparseIntArray mDynamicDepthOutputFormats = new SparseIntArray(); /** internal format -> num heic output sizes mapping, for HAL_DATASPACE_HEIF */ private final SparseIntArray mHeicOutputFormats = new SparseIntArray(); + /** internal format -> num Jpeg/R output sizes mapping, for HAL_DATASPACE_JPEG_R */ + private final SparseIntArray mJpegROutputFormats = new SparseIntArray(); /** High speed video Size -> FPS range count mapping*/ private final HashMap mHighSpeedVideoSizeMap = diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index 68f29278f282..88373e80240a 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -60,7 +60,8 @@ public class ImageFormat { RAW_DEPTH, RAW_DEPTH10, PRIVATE, - HEIC + HEIC, + JPEG_R }) public @interface Format { } @@ -257,6 +258,14 @@ public class ImageFormat { */ public static final int DEPTH_JPEG = 0x69656963; + /** + * Compressed JPEG format that includes an embedded recovery map. + * + *

    JPEG compressed main image along with XMP embedded recovery map + * following ISO TBD.

    + */ + public static final int JPEG_R = 0x1005; + /** *

    Multi-plane Android YUV 420 format

    * @@ -886,6 +895,7 @@ public class ImageFormat { case Y8: case DEPTH_JPEG: case HEIC: + case JPEG_R: return true; } diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index f223bfd357d6..72aaa3554ddb 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -1199,6 +1199,7 @@ public class ImageReader implements AutoCloseable { case ImageFormat.RAW_PRIVATE: case ImageFormat.DEPTH_JPEG: case ImageFormat.HEIC: + case ImageFormat.JPEG_R: width = ImageReader.this.getWidth(); break; default: @@ -1217,6 +1218,7 @@ public class ImageReader implements AutoCloseable { case ImageFormat.RAW_PRIVATE: case ImageFormat.DEPTH_JPEG: case ImageFormat.HEIC: + case ImageFormat.JPEG_R: height = ImageReader.this.getHeight(); break; default: diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java index 2f1a36cba9d0..8f7019d4e494 100644 --- a/media/java/android/media/ImageUtils.java +++ b/media/java/android/media/ImageUtils.java @@ -68,6 +68,7 @@ class ImageUtils { case ImageFormat.RAW_DEPTH10: case ImageFormat.DEPTH_JPEG: case ImageFormat.HEIC: + case ImageFormat.JPEG_R: return 1; case ImageFormat.PRIVATE: return 0; @@ -231,6 +232,7 @@ class ImageUtils { case ImageFormat.DEPTH_POINT_CLOUD: case ImageFormat.DEPTH_JPEG: case ImageFormat.HEIC: + case ImageFormat.JPEG_R: estimatedBytePerPixel = 0.3; break; case ImageFormat.Y8: @@ -304,6 +306,7 @@ class ImageUtils { case ImageFormat.RAW_DEPTH: case ImageFormat.RAW_DEPTH10: case ImageFormat.HEIC: + case ImageFormat.JPEG_R: return new Size(image.getWidth(), image.getHeight()); case ImageFormat.PRIVATE: return new Size(0, 0); -- cgit v1.2.3-59-g8ed1b From 8bbaeb7a49c8961d55d6343a5a7f7752b031087a Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Tue, 20 Dec 2022 23:00:08 +0900 Subject: Fix performance regression of system_server boot time The hash calculation of FontFamily took a time. In this case, just using identity hash should be fine. Bug: 263015762 Test: atest TypefaceSystemFallbackTest FontListParserTest Test: atest UpdatableFontDirTest UpdatableSystemFontTest Test: atest FontManagerTest Change-Id: Id8c39e646f92977867c82a1effb93c925c909ef2 --- graphics/java/android/graphics/fonts/SystemFonts.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index ec8b2d66da7f..8fe28ae731b8 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -22,8 +22,8 @@ import android.graphics.FontListParser; import android.graphics.Typeface; import android.text.FontConfig; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Log; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -117,17 +117,18 @@ public final class SystemFonts { } } + final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily( defaultFonts, languageTags, variant, false, cache); - // Insert family into fallback map. for (int i = 0; i < fallbackMap.size(); i++) { final String name = fallbackMap.keyAt(i); final NativeFamilyListSet familyListSet = fallbackMap.valueAt(i); - if (familyListSet.seenXmlFamilies.contains(xmlFamily)) { + int identityHash = System.identityHashCode(xmlFamily); + if (familyListSet.seenXmlFamilies.get(identityHash, -1) != -1) { continue; } else { - familyListSet.seenXmlFamilies.add(xmlFamily); + familyListSet.seenXmlFamilies.append(identityHash, 1); } final ArrayList fallback = specificFallbackFonts.get(name); if (fallback == null) { @@ -213,7 +214,7 @@ public final class SystemFonts { return; } familyListSet.familyList.add(family); - familyListSet.seenXmlFamilies.add(xmlFamily); + familyListSet.seenXmlFamilies.append(System.identityHashCode(xmlFamily), 1); } fallbackListMap.put(familyName, familyListSet); } @@ -276,7 +277,7 @@ public final class SystemFonts { private static final class NativeFamilyListSet { public List familyList = new ArrayList<>(); - public Set seenXmlFamilies = new ArraySet<>(); + public SparseIntArray seenXmlFamilies = new SparseIntArray(); } /** @hide */ -- cgit v1.2.3-59-g8ed1b From 12d371af853554e30905e0b7922e9d6912e2efab Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Wed, 26 Oct 2022 17:30:26 -0700 Subject: Add BT2020_HLG, BT2020_PQ to ColorSpace.Named enum - Reuse transferParameters class to initialize non ICC curve ColorSpace, e.g., BT2020_HLG, BT2020_PQ. - Add back-compactability between skia and java side for HLG/PQ. - lift up the restriction that BitmapFactory#isPreferredColorSpace is ICC parametric curve. Bug: 241284309 Test: android.graphics.cts.ColorSpaceTest, android.graphics.cts.BitmapTest Change-Id: Ib8d0f53fa4dc71cbd0f4eb239564c8d21f1e2ba7 --- core/api/current.txt | 2 + core/java/android/hardware/DataSpace.java | 4 +- graphics/java/android/graphics/BitmapFactory.java | 16 +- graphics/java/android/graphics/ColorSpace.java | 247 ++++++++++++++++------ libs/hwui/jni/Graphics.cpp | 28 ++- 5 files changed, 222 insertions(+), 75 deletions(-) (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index 877e2d6b54b4..ac3f95a036b3 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -14831,6 +14831,8 @@ package android.graphics { enum_constant public static final android.graphics.ColorSpace.Named ACESCG; enum_constant public static final android.graphics.ColorSpace.Named ADOBE_RGB; enum_constant public static final android.graphics.ColorSpace.Named BT2020; + enum_constant public static final android.graphics.ColorSpace.Named BT2020_HLG; + enum_constant public static final android.graphics.ColorSpace.Named BT2020_PQ; enum_constant public static final android.graphics.ColorSpace.Named BT709; enum_constant public static final android.graphics.ColorSpace.Named CIE_LAB; enum_constant public static final android.graphics.ColorSpace.Named CIE_XYZ; diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java index 15eae0920e7d..7372fb72fab5 100644 --- a/core/java/android/hardware/DataSpace.java +++ b/core/java/android/hardware/DataSpace.java @@ -508,9 +508,7 @@ public final class DataSpace { public static final int DATASPACE_BT2020_HLG = 168165376; /** - * ITU-R Recommendation 2020 (BT.2020) - * - * Ultra High-definition television. + * Perceptual Quantizer encoding. * *

    Composed of the following -

    *
    diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
    index ef1e7bfc6651..701e20c499da 100644
    --- a/graphics/java/android/graphics/BitmapFactory.java
    +++ b/graphics/java/android/graphics/BitmapFactory.java
    @@ -161,11 +161,17 @@ public class BitmapFactory {
              * be thrown by the decode methods when setting a non-RGB color space
              * such as {@link ColorSpace.Named#CIE_LAB Lab}.

    * - *

    The specified color space's transfer function must be + *

    + * Prior to {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * the specified color space's transfer function must be * an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. An * IllegalArgumentException will be thrown by the decode methods * if calling {@link ColorSpace.Rgb#getTransferParameters()} on the - * specified color space returns null.

    + * specified color space returns null. + * + * Starting from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * non ICC parametric curve transfer function is allowed. + * E.g., {@link ColorSpace.Named#BT2020_HLG BT2020_HLG}.

    * *

    After decode, the bitmap's color space is stored in * {@link #outColorSpace}.

    @@ -458,7 +464,11 @@ public class BitmapFactory { throw new IllegalArgumentException("The destination color space must use the " + "RGB color model"); } - if (((ColorSpace.Rgb) opts.inPreferredColorSpace).getTransferParameters() == null) { + if (!opts.inPreferredColorSpace.equals(ColorSpace.get(ColorSpace.Named.BT2020_HLG)) + && !opts.inPreferredColorSpace.equals( + ColorSpace.get(ColorSpace.Named.BT2020_PQ)) + && ((ColorSpace.Rgb) opts.inPreferredColorSpace) + .getTransferParameters() == null) { throw new IllegalArgumentException("The destination color space must use an " + "ICC parametric transfer function"); } diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 31df474eb10c..2427dec169d6 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -199,6 +199,8 @@ public abstract class ColorSpace { private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }; private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f }; + private static final float[] BT2020_PRIMARIES = + { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f }; /** * A gray color space does not have meaningful primaries, so we use this arbitrary set. */ @@ -208,6 +210,12 @@ public abstract class ColorSpace { private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS = new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4); + private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS = + new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f, + 0.28466892f, 0.5599107f, 0.0f, -3.0f, true); + private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS = + new Rgb.TransferParameters(107 / 128.0f, 1.0f, 32 / 2523.0f, + 2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true); // See static initialization block next to #get(Named) private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length]; @@ -703,7 +711,29 @@ public abstract class ColorSpace { * Range\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\) * */ - CIE_LAB + CIE_LAB, + /** + *

    {@link ColorSpace.Rgb RGB} color space BT.2100 standardized as + * Hybrid Log Gamma encoding.

    + * + * + * + * + * + *
    PropertyValue
    NameHybrid Log Gamma encoding
    CIE standard illuminantD65
    Range\([0..1]\)
    + */ + BT2020_HLG, + /** + *

    {@link ColorSpace.Rgb RGB} color space BT.2100 standardized as + * Perceptual Quantizer encoding.

    + * + * + * + * + * + *
    PropertyValue
    NamePerceptual Quantizer encoding
    CIE standard illuminantD65
    Range\([0..1]\)
    + */ + BT2020_PQ // Update the initialization block next to #get(Named) when adding new values } @@ -1534,7 +1564,7 @@ public abstract class ColorSpace { sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal()); sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb( "Rec. ITU-R BT.2020-1", - new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f }, + BT2020_PRIMARIES, ILLUMINANT_D65, null, new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45), @@ -1616,6 +1646,70 @@ public abstract class ColorSpace { "Generic L*a*b*", Named.CIE_LAB.ordinal() ); + sNamedColorSpaces[Named.BT2020_HLG.ordinal()] = new ColorSpace.Rgb( + "Hybrid Log Gamma encoding", + BT2020_PRIMARIES, + ILLUMINANT_D65, + null, + x -> transferHLGOETF(x), + x -> transferHLGEOTF(x), + 0.0f, 1.0f, + BT2020_HLG_TRANSFER_PARAMETERS, + Named.BT2020_HLG.ordinal() + ); + sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_HLG, Named.BT2020_HLG.ordinal()); + sNamedColorSpaces[Named.BT2020_PQ.ordinal()] = new ColorSpace.Rgb( + "Perceptual Quantizer encoding", + BT2020_PRIMARIES, + ILLUMINANT_D65, + null, + x -> transferST2048OETF(x), + x -> transferST2048EOTF(x), + 0.0f, 1.0f, + BT2020_PQ_TRANSFER_PARAMETERS, + Named.BT2020_PQ.ordinal() + ); + sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_PQ, Named.BT2020_PQ.ordinal()); + } + + private static double transferHLGOETF(double x) { + double a = 0.17883277; + double b = 0.28466892; + double c = 0.55991073; + double r = 0.5; + return x > 1.0 ? a * Math.log(x - b) + c : r * Math.sqrt(x); + } + + private static double transferHLGEOTF(double x) { + double a = 0.17883277; + double b = 0.28466892; + double c = 0.55991073; + double r = 0.5; + return x <= 0.5 ? (x * x) / (r * r) : Math.exp((x - c) / a + b); + } + + private static double transferST2048OETF(double x) { + double m1 = (2610.0 / 4096.0) / 4.0; + double m2 = (2523.0 / 4096.0) * 128.0; + double c1 = (3424.0 / 4096.0); + double c2 = (2413.0 / 4096.0) * 32.0; + double c3 = (2392.0 / 4096.0) * 32.0; + + double tmp = Math.pow(x, m1); + tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp); + return Math.pow(tmp, m2); + } + + private static double transferST2048EOTF(double x) { + double m1 = (2610.0 / 4096.0) / 4.0; + double m2 = (2523.0 / 4096.0) * 128.0; + double c1 = (3424.0 / 4096.0); + double c2 = (2413.0 / 4096.0) * 32.0; + double c3 = (2392.0 / 4096.0) * 32.0; + + double tmp = Math.pow(Math.min(Math.max(x, 0.0), 1.0), 1.0 / m2); + tmp = Math.max(tmp - c1, 0.0) / (c2 - c3 * tmp); + return Math.pow(tmp, 1.0 / m1); } // Reciprocal piecewise gamma response @@ -2197,6 +2291,58 @@ public abstract class ColorSpace { /** Variable \(g\) in the equation of the EOTF described above. */ public final double g; + private TransferParameters(double a, double b, double c, double d, double e, + double f, double g, boolean nonCurveTransferParameters) { + // nonCurveTransferParameters correspondes to a "special" transfer function + if (!nonCurveTransferParameters) { + if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c) + || Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f) + || Double.isNaN(g)) { + throw new IllegalArgumentException("Parameters cannot be NaN"); + } + + // Next representable float after 1.0 + // We use doubles here but the representation inside our native code + // is often floats + if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) { + throw new IllegalArgumentException( + "Parameter d must be in the range [0..1], " + "was " + d); + } + + if (d == 0.0 && (a == 0.0 || g == 0.0)) { + throw new IllegalArgumentException( + "Parameter a or g is zero, the transfer function is constant"); + } + + if (d >= 1.0 && c == 0.0) { + throw new IllegalArgumentException( + "Parameter c is zero, the transfer function is constant"); + } + + if ((a == 0.0 || g == 0.0) && c == 0.0) { + throw new IllegalArgumentException("Parameter a or g is zero," + + " and c is zero, the transfer function is constant"); + } + + if (c < 0.0) { + throw new IllegalArgumentException( + "The transfer function must be increasing"); + } + + if (a < 0.0 || g < 0.0) { + throw new IllegalArgumentException( + "The transfer function must be positive or increasing"); + } + } + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + this.g = g; + } + /** *

    Defines the parameters for the ICC parametric curve type 3, as * defined in ICC.1:2004-10, section 10.15.

    @@ -2219,7 +2365,7 @@ public abstract class ColorSpace { * @throws IllegalArgumentException If the parameters form an invalid transfer function */ public TransferParameters(double a, double b, double c, double d, double g) { - this(a, b, c, d, 0.0, 0.0, g); + this(a, b, c, d, 0.0, 0.0, g, false); } /** @@ -2238,51 +2384,7 @@ public abstract class ColorSpace { */ public TransferParameters(double a, double b, double c, double d, double e, double f, double g) { - - if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c) || - Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f) || - Double.isNaN(g)) { - throw new IllegalArgumentException("Parameters cannot be NaN"); - } - - // Next representable float after 1.0 - // We use doubles here but the representation inside our native code is often floats - if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) { - throw new IllegalArgumentException("Parameter d must be in the range [0..1], " + - "was " + d); - } - - if (d == 0.0 && (a == 0.0 || g == 0.0)) { - throw new IllegalArgumentException( - "Parameter a or g is zero, the transfer function is constant"); - } - - if (d >= 1.0 && c == 0.0) { - throw new IllegalArgumentException( - "Parameter c is zero, the transfer function is constant"); - } - - if ((a == 0.0 || g == 0.0) && c == 0.0) { - throw new IllegalArgumentException("Parameter a or g is zero," + - " and c is zero, the transfer function is constant"); - } - - if (c < 0.0) { - throw new IllegalArgumentException("The transfer function must be increasing"); - } - - if (a < 0.0 || g < 0.0) { - throw new IllegalArgumentException("The transfer function must be " + - "positive or increasing"); - } - - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.e = e; - this.f = f; - this.g = g; + this(a, b, c, d, e, f, g, false); } @SuppressWarnings("SimplifiableIfStatement") @@ -2357,6 +2459,36 @@ public abstract class ColorSpace { private static native long nativeCreate(float a, float b, float c, float d, float e, float f, float g, float[] xyz); + private static DoubleUnaryOperator generateOETF(TransferParameters function) { + boolean isNonCurveTransferParameters = function.equals(BT2020_HLG_TRANSFER_PARAMETERS) + || function.equals(BT2020_PQ_TRANSFER_PARAMETERS); + if (isNonCurveTransferParameters) { + return function.f == 0.0 && function.g < 0.0 ? x -> transferHLGOETF(x) + : x -> transferST2048OETF(x); + } else { + return function.e == 0.0 && function.f == 0.0 + ? x -> rcpResponse(x, function.a, function.b, + function.c, function.d, function.g) + : x -> rcpResponse(x, function.a, function.b, function.c, + function.d, function.e, function.f, function.g); + } + } + + private static DoubleUnaryOperator generateEOTF(TransferParameters function) { + boolean isNonCurveTransferParameters = function.equals(BT2020_HLG_TRANSFER_PARAMETERS) + || function.equals(BT2020_PQ_TRANSFER_PARAMETERS); + if (isNonCurveTransferParameters) { + return function.f == 0.0 && function.g < 0.0 ? x -> transferHLGEOTF(x) + : x -> transferST2048EOTF(x); + } else { + return function.e == 0.0 && function.f == 0.0 + ? x -> response(x, function.a, function.b, + function.c, function.d, function.g) + : x -> response(x, function.a, function.b, function.c, + function.d, function.e, function.f, function.g); + } + } + /** *

    Creates a new RGB color space using a 3x3 column-major transform matrix. * The transform matrix must convert from the RGB space to the profile connection @@ -2553,16 +2685,8 @@ public abstract class ColorSpace { @NonNull TransferParameters function, @IntRange(from = MIN_ID, to = MAX_ID) int id) { this(name, primaries, whitePoint, transform, - function.e == 0.0 && function.f == 0.0 ? - x -> rcpResponse(x, function.a, function.b, - function.c, function.d, function.g) : - x -> rcpResponse(x, function.a, function.b, function.c, - function.d, function.e, function.f, function.g), - function.e == 0.0 && function.f == 0.0 ? - x -> response(x, function.a, function.b, - function.c, function.d, function.g) : - x -> response(x, function.a, function.b, function.c, - function.d, function.e, function.f, function.g), + generateOETF(function), + generateEOTF(function), 0.0f, 1.0f, function, id); } @@ -3063,7 +3187,12 @@ public abstract class ColorSpace { */ @Nullable public TransferParameters getTransferParameters() { - return mTransferParameters; + if (mTransferParameters != null + && !mTransferParameters.equals(BT2020_PQ_TRANSFER_PARAMETERS) + && !mTransferParameters.equals(BT2020_HLG_TRANSFER_PARAMETERS)) { + return mTransferParameters; + } + return null; } @Override diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp index 6a3bc8fe1152..c8358497ad62 100644 --- a/libs/hwui/jni/Graphics.cpp +++ b/libs/hwui/jni/Graphics.cpp @@ -576,14 +576,22 @@ jobject GraphicsJNI::getColorSpace(JNIEnv* env, SkColorSpace* decodeColorSpace, LOG_ALWAYS_FATAL_IF(!decodeColorSpace->toXYZD50(&xyzMatrix)); skcms_TransferFunction transferParams; - // We can only handle numerical transfer functions at the moment - LOG_ALWAYS_FATAL_IF(!decodeColorSpace->isNumericalTransferFn(&transferParams)); - - jobject params = env->NewObject(gTransferParameters_class, - gTransferParameters_constructorMethodID, - transferParams.a, transferParams.b, transferParams.c, - transferParams.d, transferParams.e, transferParams.f, - transferParams.g); + decodeColorSpace->transferFn(&transferParams); + auto res = skcms_TransferFunction_getType(&transferParams); + LOG_ALWAYS_FATAL_IF(res == skcms_TFType_HLGinvish || res == skcms_TFType_Invalid); + + jobject params; + if (res == skcms_TFType_PQish || res == skcms_TFType_HLGish) { + params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID, + transferParams.a, transferParams.b, transferParams.c, + transferParams.d, transferParams.e, transferParams.f, + transferParams.g, true); + } else { + params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID, + transferParams.a, transferParams.b, transferParams.c, + transferParams.d, transferParams.e, transferParams.f, + transferParams.g, false); + } jfloatArray xyzArray = env->NewFloatArray(9); jfloat xyz[9] = { @@ -808,8 +816,8 @@ int register_android_graphics_Graphics(JNIEnv* env) gTransferParameters_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ColorSpace$Rgb$TransferParameters")); - gTransferParameters_constructorMethodID = GetMethodIDOrDie(env, gTransferParameters_class, - "", "(DDDDDDD)V"); + gTransferParameters_constructorMethodID = + GetMethodIDOrDie(env, gTransferParameters_class, "", "(DDDDDDDZ)V"); gFontMetrics_class = FindClassOrDie(env, "android/graphics/Paint$FontMetrics"); gFontMetrics_class = MakeGlobalRefOrDie(env, gFontMetrics_class); -- cgit v1.2.3-59-g8ed1b From 4a39f27868d094a1b5c26bb7c531cfab436b0c10 Mon Sep 17 00:00:00 2001 From: Angel Aguayo Date: Wed, 30 Nov 2022 19:34:30 +0000 Subject: Updated Mesh documentation Updated Mesh and MeshSpecification documentation to better describe parameters/important method features. Bug: b/253321460 Test: none Change-Id: I87fb2cdc2ca36f891c339c77b2ed05dee4b84fee --- graphics/java/android/graphics/Mesh.java | 83 ++++++++++++++++------ .../java/android/graphics/MeshSpecification.java | 81 +++++++++++++++++---- 2 files changed, 131 insertions(+), 33 deletions(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index f32e0ee0cc79..1f693166ecf0 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -29,7 +29,8 @@ import java.nio.ShortBuffer; * {@link #makeIndexed(MeshSpecification, Mode, Buffer, int, ShortBuffer, Rect)} methods, * where a {@link MeshSpecification} is required along with various attributes for * detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds - * for the mesh. + * for the mesh. Once generated, a mesh object can be drawn through + * {@link Canvas#drawMesh(Mesh, BlendMode, Paint)} * * @hide */ @@ -53,8 +54,12 @@ public class Mesh { * * @param meshSpec {@link MeshSpecification} used when generating the mesh. * @param mode {@link Mode} enum - * @param vertexBuffer vertex buffer representing through {@link Buffer}. - * @param vertexCount the number of vertices represented in the vertexBuffer. + * @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data + * for all attributes provided within the meshSpec for every vertex. That + * is, a vertex buffer should be (attributes size * number of vertices) in + * length to be valid. Note that currently implementation will have a CPU + * backed buffer generated. + * @param vertexCount the number of vertices represented in the vertexBuffer and mesh. * @param bounds bounds of the mesh object. * @return a new Mesh object. */ @@ -70,13 +75,20 @@ public class Mesh { } /** - * Generates an indexed {@link Mesh} object. + * Generates a {@link Mesh} object. * * @param meshSpec {@link MeshSpecification} used when generating the mesh. * @param mode {@link Mode} enum - * @param vertexBuffer vertex buffer representing through {@link Buffer}. - * @param vertexCount the number of vertices represented in the vertexBuffer. - * @param indexBuffer index buffer representing through {@link ShortBuffer}. + * @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data + * for all attributes provided within the meshSpec for every vertex. That + * is, a vertex buffer should be (attributes size * number of vertices) in + * length to be valid. Note that currently implementation will have a CPU + * backed buffer generated. + * @param vertexCount the number of vertices represented in the vertexBuffer and mesh. + * @param indexBuffer index buffer representing through {@link ShortBuffer}. Indices are + * required to be 16 bits, so ShortBuffer is necessary. Note that + * currently implementation will have a CPU + * backed buffer generated. * @param bounds bounds of the mesh object. * @return a new Mesh object. */ @@ -93,7 +105,10 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform color value corresponding to the shader assigned to the mesh. If the shader + * does not have a uniform with that name or if the uniform is declared with a type other than + * vec3 or vec4 and corresponding layout(color) annotation then an IllegalArgumentExcepton is + * thrown. * * @param uniformName name matching the color uniform declared in the shader program. * @param color the provided sRGB color will be converted into the shader program's output @@ -104,7 +119,10 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform color value corresponding to the shader assigned to the mesh. If the shader + * does not have a uniform with that name or if the uniform is declared with a type other than + * vec3 or vec4 and corresponding layout(color) annotation then an IllegalArgumentExcepton is + * thrown. * * @param uniformName name matching the color uniform declared in the shader program. * @param color the provided sRGB color will be converted into the shader program's output @@ -116,7 +134,10 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform color value corresponding to the shader assigned to the mesh. If the shader + * does not have a uniform with that name or if the uniform is declared with a type other than + * vec3 or vec4 and corresponding layout(color) annotation then an IllegalArgumentExcepton is + * thrown. * * @param uniformName name matching the color uniform declared in the shader program. * @param color the provided sRGB color will be converted into the shader program's output @@ -132,7 +153,9 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does + * not have a uniform with that name or if the uniform is declared with a type other than a + * float or float[1] then an IllegalArgumentException is thrown. * * @param uniformName name matching the float uniform declared in the shader program. * @param value float value corresponding to the float uniform with the given name. @@ -142,7 +165,9 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does + * not have a uniform with that name or if the uniform is declared with a type other than a + * vec2 or float[2] then an IllegalArgumentException is thrown. * * @param uniformName name matching the float uniform declared in the shader program. * @param value1 first float value corresponding to the float uniform with the given name. @@ -153,7 +178,9 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does + * not have a uniform with that name or if the uniform is declared with a type other than a + * vec3 or float[3] then an IllegalArgumentException is thrown. * * @param uniformName name matching the float uniform declared in the shader program. * @param value1 first float value corresponding to the float uniform with the given name. @@ -166,7 +193,9 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does + * not have a uniform with that name or if the uniform is declared with a type other than a + * vec4 or float[4] then an IllegalArgumentException is thrown. * * @param uniformName name matching the float uniform declared in the shader program. * @param value1 first float value corresponding to the float uniform with the given name. @@ -180,7 +209,10 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does + * not have a uniform with that name or if the uniform is declared with a type other than a + * float (for N=1), vecN, or float[N], where N is the length of the values param, then an + * IllegalArgumentException is thrown. * * @param uniformName name matching the float uniform declared in the shader program. * @param values float value corresponding to the vec4 float uniform with the given name. @@ -210,7 +242,9 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does + * not have a uniform with that name or if the uniform is declared with a type other than int + * or int[1] then an IllegalArgumentException is thrown. * * @param uniformName name matching the int uniform delcared in the shader program. * @param value value corresponding to the int uniform with the given name. @@ -220,7 +254,9 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does + * not have a uniform with that name or if the uniform is declared with a type other than ivec2 + * or int[2] then an IllegalArgumentException is thrown. * * @param uniformName name matching the int uniform delcared in the shader program. * @param value1 first value corresponding to the int uniform with the given name. @@ -231,7 +267,9 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does + * not have a uniform with that name or if the uniform is declared with a type other than ivec3 + * or int[3] then an IllegalArgumentException is thrown. * * @param uniformName name matching the int uniform delcared in the shader program. * @param value1 first value corresponding to the int uniform with the given name. @@ -243,7 +281,9 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does + * not have a uniform with that name or if the uniform is declared with a type other than ivec4 + * or int[4] then an IllegalArgumentException is thrown. * * @param uniformName name matching the int uniform delcared in the shader program. * @param value1 first value corresponding to the int uniform with the given name. @@ -256,7 +296,10 @@ public class Mesh { } /** - * Sets the uniform color value corresponding to the shader assigned to the mesh. + * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does + * not have a uniform with that name or if the uniform is declared with a type other than an + * int (for N=1), ivecN, or int[N], where N is the length of the values param, then an + * IllegalArgumentException is thrown. * * @param uniformName name matching the int uniform delcared in the shader program. * @param values int values corresponding to the vec4 int uniform with the given name. diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java index 45c13affac14..dd8fb7ab2370 100644 --- a/graphics/java/android/graphics/MeshSpecification.java +++ b/graphics/java/android/graphics/MeshSpecification.java @@ -25,7 +25,7 @@ import libcore.util.NativeAllocationRegistry; * generates a {@link MeshSpecification} via the Make method, where multiple parameters to set up * the mesh are supplied, including attributes, vertex stride, varyings, and * vertex/fragment shaders. There are also additional methods to provide an optional - * {@link ColorSpace} as well as an alpha type. + * {@link ColorSpace} as well as an {@link AlphaType}. * * Note that there are several limitations on various mesh specifications: * 1. The max amount of attributes allowed is 8. @@ -43,15 +43,30 @@ public class MeshSpecification { /** * Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)} - * to determine alpha type + * to determine alpha type. Describes how to interpret the alpha component of a pixel. */ @IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT}) public @interface AlphaType { } + /** + * uninitialized. + */ public static final int UNKNOWN = 0; + + /** + * Pixel is opaque. + */ public static final int OPAQUE = 1; + + /** + * Pixel components are premultiplied by alpha. + */ public static final int PREMUL = 2; + + /** + * Pixel components are independent of alpha. + */ public static final int UNPREMULT = 3; /** @@ -61,15 +76,41 @@ public class MeshSpecification { public @interface Type { } + /** + * Represents one float. Its equivalent shader type is float. + */ public static final int FLOAT = 0; + + /** + * Represents two floats. Its equivalent shader type is float2. + */ public static final int FLOAT2 = 1; + + /** + * Represents three floats. Its equivalent shader type is float3. + */ public static final int FLOAT3 = 2; + + /** + * Represents four floats. Its equivalent shader type is float4. + */ public static final int FLOAT4 = 3; + + /** + * Represents four bytes. Its equivalent shader type is half4. + */ public static final int UBYTE4 = 4; /** * Data class to represent a single attribute in a shader. Note that type parameter must be * one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}. + * + * Note that offset is the offset in number of bytes. For example, if we had two attributes + * + * Float3 att1 + * Float att2 + * + * att1 would have an offset of 0, while att2 would have an offset of 12 bytes. */ public static class Attribute { @Type @@ -106,14 +147,19 @@ public class MeshSpecification { } /** - * Creates a {@link MeshSpecification} object. + * Creates a {@link MeshSpecification} object for use within {@link Mesh}. * * @param attributes list of attributes represented by {@link Attribute}. Can hold a max of * 8. - * @param vertexStride length of vertex stride. Max of 1024 is accepted. + * @param vertexStride length of vertex stride in bytes. This should be the size of a single + * vertex' attributes. Max of 1024 is accepted. * @param varyings List of varyings represented by {@link Varying}. Can hold a max of 6. - * @param vertexShader vertex shader to be supplied to the mesh. - * @param fragmentShader fragment shader to be suppied to the mesh. + * Note that `position` is provided by default, does not need to be + * provided in the list, and does not count towards + * the 6 varyings allowed. + * @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position + * varying is set within the shader to get proper results. + * @param fragmentShader fragment shader to be supplied to the mesh. * @return {@link MeshSpecification} object for use when creating {@link Mesh} */ public static MeshSpecification make(Attribute[] attributes, int vertexStride, @@ -131,10 +177,14 @@ public class MeshSpecification { * * @param attributes list of attributes represented by {@link Attribute}. Can hold a max of * 8. - * @param vertexStride length of vertex stride. Max of 1024 is accepted. - * @param varyings List of varyings represented by {@link Varying}. Can hold a max of - * 6. - * @param vertexShader vertex shader to be supplied to the mesh. + * @param vertexStride length of vertex stride in bytes. This should be the size of a single + * vertex' attributes. Max of 1024 is accepted. + * @param varyings List of varyings represented by {@link Varying}. Can hold a max of 6. + * Note that `position` is provided by default, does not need to be + * provided in the list, and does not count towards + * the 6 varyings allowed. + * @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position + * varying is set within the shader to get proper results. * @param fragmentShader fragment shader to be supplied to the mesh. * @param colorSpace {@link ColorSpace} to tell what color space to work in. * @return {@link MeshSpecification} object for use when creating {@link Mesh} @@ -154,10 +204,15 @@ public class MeshSpecification { * * @param attributes list of attributes represented by {@link Attribute}. Can hold a max of * 8. - * @param vertexStride length of vertex stride. Max of 1024 is accepted. + * @param vertexStride length of vertex stride in bytes. This should be the size of a single + * vertex' attributes. Max of 1024 is accepted. * @param varyings List of varyings represented by {@link Varying}. Can hold a max of 6. - * @param vertexShader vertex shader code to be supplied to the mesh. - * @param fragmentShader fragment shader code to be suppied to the mesh. + * Note that `position` is provided by default, does not need to be + * provided in the list, and does not count towards + * the 6 varyings allowed. + * @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position + * varying is set within the shader to get proper results. + * @param fragmentShader fragment shader to be supplied to the mesh. * @param colorSpace {@link ColorSpace} to tell what color space to work in. * @param alphaType Describes how to interpret the alpha component for a pixel. Must be * one of {@link AlphaType} values. -- cgit v1.2.3-59-g8ed1b From c321395b106b863720355050fb6d5e3a208926a0 Mon Sep 17 00:00:00 2001 From: Angel Aguayo Date: Fri, 2 Dec 2022 01:39:03 +0000 Subject: Prepare for publicly exposing drawMesh API Fixes API issues to keep it consistent with the framework. Bug: b/253321460, b/261768939 Test: HwAccelerationTest MeshActivity Change-Id: I436d06bc8189fcea97e0c0842e68872afe05e022 --- graphics/java/android/graphics/BaseCanvas.java | 11 +++- .../java/android/graphics/BaseRecordingCanvas.java | 5 +- graphics/java/android/graphics/Mesh.java | 77 +++++++++++++++------- .../java/android/graphics/MeshSpecification.java | 55 ++++++++++------ libs/hwui/jni/Mesh.cpp | 57 +++++++++------- libs/hwui/jni/Mesh.h | 1 + libs/hwui/jni/MeshSpecification.cpp | 1 - .../src/com/android/test/hwui/MeshActivity.java | 18 ++--- 8 files changed, 146 insertions(+), 79 deletions(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index e62ac466ac43..2f396c026706 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -668,12 +668,21 @@ public abstract class BaseCanvas { } /** + * Draws a mesh object to the screen. + * + * @param mesh {@link Mesh} object that will be drawn to the screen + * @param blendMode {@link BlendMode} used to blend mesh primitives with the Paint color/shader + * @param paint {@link Paint} used to provide a color/shader/blend mode. + * * @hide */ - public void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) { + public void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) { if (!isHardwareAccelerated() && onHwFeatureInSwMode()) { throw new RuntimeException("software rendering doesn't support meshes"); } + if (blendMode == null) { + blendMode = BlendMode.MODULATE; + } nDrawMesh(this.mNativeCanvasWrapper, mesh.getNativeWrapperInstance(), blendMode.getXfermode().porterDuffMode, paint.getNativeInstance()); } diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java index eeff694eb3ac..2ec4524e1241 100644 --- a/graphics/java/android/graphics/BaseRecordingCanvas.java +++ b/graphics/java/android/graphics/BaseRecordingCanvas.java @@ -607,7 +607,10 @@ public class BaseRecordingCanvas extends Canvas { } @Override - public final void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) { + public final void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) { + if (blendMode == null) { + blendMode = BlendMode.MODULATE; + } nDrawMesh(mNativeCanvasWrapper, mesh.getNativeWrapperInstance(), blendMode.getXfermode().porterDuffMode, paint.getNativeInstance()); } diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index 1f693166ecf0..e186386a21be 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -16,6 +16,9 @@ package android.graphics; +import android.annotation.IntDef; +import android.annotation.NonNull; + import libcore.util.NativeAllocationRegistry; import java.nio.Buffer; @@ -25,8 +28,8 @@ import java.nio.ShortBuffer; * Class representing a mesh object. * * This class generates Mesh objects via the - * {@link #make(MeshSpecification, Mode, Buffer, int, Rect)} and - * {@link #makeIndexed(MeshSpecification, Mode, Buffer, int, ShortBuffer, Rect)} methods, + * {@link #make(MeshSpecification, int, Buffer, int, Rect)} and + * {@link #makeIndexed(MeshSpecification, int, Buffer, int, ShortBuffer, Rect)} methods, * where a {@link MeshSpecification} is required along with various attributes for * detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds * for the mesh. Once generated, a mesh object can be drawn through @@ -39,9 +42,20 @@ public class Mesh { private boolean mIsIndexed; /** - * Enum to determine how the mesh is represented. + * Determines how the mesh is represented and will be drawn. + */ + @IntDef({TRIANGLES, TRIANGLE_STRIP}) + private @interface Mode {} + + /** + * The mesh will be drawn with triangles without utilizing shared vertices. + */ + public static final int TRIANGLES = 0; + + /** + * The mesh will be drawn with triangles utilizing shared vertices. */ - public enum Mode {Triangles, TriangleStrip} + public static final int TRIANGLE_STRIP = 1; private static class MeshHolder { public static final NativeAllocationRegistry MESH_SPECIFICATION_REGISTRY = @@ -53,7 +67,8 @@ public class Mesh { * Generates a {@link Mesh} object. * * @param meshSpec {@link MeshSpecification} used when generating the mesh. - * @param mode {@link Mode} enum + * @param mode Determines what mode to draw the mesh in. Must be one of + * {@link Mesh#TRIANGLES} or {@link Mesh#TRIANGLE_STRIP} * @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data * for all attributes provided within the meshSpec for every vertex. That * is, a vertex buffer should be (attributes size * number of vertices) in @@ -63,9 +78,13 @@ public class Mesh { * @param bounds bounds of the mesh object. * @return a new Mesh object. */ - public static Mesh make(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer, - int vertexCount, Rect bounds) { - long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer, + @NonNull + public static Mesh make(@NonNull MeshSpecification meshSpec, @Mode int mode, + @NonNull Buffer vertexBuffer, int vertexCount, @NonNull Rect bounds) { + if (mode != TRIANGLES && mode != TRIANGLE_STRIP) { + throw new IllegalArgumentException("Invalid value passed in for mode parameter"); + } + long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode, vertexBuffer, vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), bounds.left, bounds.top, bounds.right, bounds.bottom); if (nativeMesh == 0) { @@ -78,7 +97,8 @@ public class Mesh { * Generates a {@link Mesh} object. * * @param meshSpec {@link MeshSpecification} used when generating the mesh. - * @param mode {@link Mode} enum + * @param mode Determines what mode to draw the mesh in. Must be one of + * {@link Mesh#TRIANGLES} or {@link Mesh#TRIANGLE_STRIP} * @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data * for all attributes provided within the meshSpec for every vertex. That * is, a vertex buffer should be (attributes size * number of vertices) in @@ -92,9 +112,14 @@ public class Mesh { * @param bounds bounds of the mesh object. * @return a new Mesh object. */ - public static Mesh makeIndexed(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer, - int vertexCount, ShortBuffer indexBuffer, Rect bounds) { - long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer, + @NonNull + public static Mesh makeIndexed(@NonNull MeshSpecification meshSpec, @Mode int mode, + @NonNull Buffer vertexBuffer, int vertexCount, @NonNull ShortBuffer indexBuffer, + @NonNull Rect bounds) { + if (mode != TRIANGLES && mode != TRIANGLE_STRIP) { + throw new IllegalArgumentException("Invalid value passed in for mode parameter"); + } + long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode, vertexBuffer, vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), indexBuffer, indexBuffer.isDirect(), indexBuffer.capacity(), indexBuffer.position(), bounds.left, bounds.top, bounds.right, bounds.bottom); @@ -114,7 +139,7 @@ public class Mesh { * @param color the provided sRGB color will be converted into the shader program's output * colorspace and be available as a vec4 uniform in the program. */ - public void setColorUniform(String uniformName, int color) { + public void setColorUniform(@NonNull String uniformName, int color) { setUniform(uniformName, Color.valueOf(color).getComponents(), true); } @@ -128,7 +153,7 @@ public class Mesh { * @param color the provided sRGB color will be converted into the shader program's output * colorspace and be available as a vec4 uniform in the program. */ - public void setColorUniform(String uniformName, long color) { + public void setColorUniform(@NonNull String uniformName, long color) { Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)); setUniform(uniformName, exSRGB.getComponents(), true); } @@ -143,7 +168,7 @@ public class Mesh { * @param color the provided sRGB color will be converted into the shader program's output * colorspace and will be made available as a vec4 uniform in the program. */ - public void setColorUniform(String uniformName, Color color) { + public void setColorUniform(@NonNull String uniformName, @NonNull Color color) { if (color == null) { throw new NullPointerException("The color parameter must not be null"); } @@ -160,7 +185,7 @@ public class Mesh { * @param uniformName name matching the float uniform declared in the shader program. * @param value float value corresponding to the float uniform with the given name. */ - public void setFloatUniform(String uniformName, float value) { + public void setFloatUniform(@NonNull String uniformName, float value) { setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1); } @@ -173,7 +198,7 @@ public class Mesh { * @param value1 first float value corresponding to the float uniform with the given name. * @param value2 second float value corresponding to the float uniform with the given name. */ - public void setFloatUniform(String uniformName, float value1, float value2) { + public void setFloatUniform(@NonNull String uniformName, float value1, float value2) { setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2); } @@ -188,7 +213,8 @@ public class Mesh { * @param value3 third float value corresponding to the float unifiform with the given * name. */ - public void setFloatUniform(String uniformName, float value1, float value2, float value3) { + public void setFloatUniform( + @NonNull String uniformName, float value1, float value2, float value3) { setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3); } @@ -204,7 +230,7 @@ public class Mesh { * @param value4 fourth float value corresponding to the float uniform with the given name. */ public void setFloatUniform( - String uniformName, float value1, float value2, float value3, float value4) { + @NonNull String uniformName, float value1, float value2, float value3, float value4) { setFloatUniform(uniformName, value1, value2, value3, value4, 4); } @@ -217,7 +243,7 @@ public class Mesh { * @param uniformName name matching the float uniform declared in the shader program. * @param values float value corresponding to the vec4 float uniform with the given name. */ - public void setFloatUniform(String uniformName, float[] values) { + public void setFloatUniform(@NonNull String uniformName, @NonNull float[] values) { setUniform(uniformName, values, false); } @@ -249,7 +275,7 @@ public class Mesh { * @param uniformName name matching the int uniform delcared in the shader program. * @param value value corresponding to the int uniform with the given name. */ - public void setIntUniform(String uniformName, int value) { + public void setIntUniform(@NonNull String uniformName, int value) { setIntUniform(uniformName, value, 0, 0, 0, 1); } @@ -262,7 +288,7 @@ public class Mesh { * @param value1 first value corresponding to the int uniform with the given name. * @param value2 second value corresponding to the int uniform with the given name. */ - public void setIntUniform(String uniformName, int value1, int value2) { + public void setIntUniform(@NonNull String uniformName, int value1, int value2) { setIntUniform(uniformName, value1, value2, 0, 0, 2); } @@ -276,7 +302,7 @@ public class Mesh { * @param value2 second value corresponding to the int uniform with the given name. * @param value3 third value corresponding to the int uniform with the given name. */ - public void setIntUniform(String uniformName, int value1, int value2, int value3) { + public void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3) { setIntUniform(uniformName, value1, value2, value3, 0, 3); } @@ -291,7 +317,8 @@ public class Mesh { * @param value3 third value corresponding to the int uniform with the given name. * @param value4 fourth value corresponding to the int uniform with the given name. */ - public void setIntUniform(String uniformName, int value1, int value2, int value3, int value4) { + public void setIntUniform( + @NonNull String uniformName, int value1, int value2, int value3, int value4) { setIntUniform(uniformName, value1, value2, value3, value4, 4); } @@ -304,7 +331,7 @@ public class Mesh { * @param uniformName name matching the int uniform delcared in the shader program. * @param values int values corresponding to the vec4 int uniform with the given name. */ - public void setIntUniform(String uniformName, int[] values) { + public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) { if (uniformName == null) { throw new NullPointerException("The uniformName parameter must not be null"); } diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java index dd8fb7ab2370..6ef35960a73b 100644 --- a/graphics/java/android/graphics/MeshSpecification.java +++ b/graphics/java/android/graphics/MeshSpecification.java @@ -17,15 +17,18 @@ package android.graphics; import android.annotation.IntDef; +import android.annotation.NonNull; import libcore.util.NativeAllocationRegistry; +import java.util.List; + /** * Class responsible for holding specifications for {@link Mesh} creations. This class * generates a {@link MeshSpecification} via the Make method, where multiple parameters to set up * the mesh are supplied, including attributes, vertex stride, varyings, and * vertex/fragment shaders. There are also additional methods to provide an optional - * {@link ColorSpace} as well as an {@link AlphaType}. + * {@link ColorSpace} as well as an alpha type. * * Note that there are several limitations on various mesh specifications: * 1. The max amount of attributes allowed is 8. @@ -42,12 +45,11 @@ public class MeshSpecification { long mNativeMeshSpec; /** - * Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)} + * Constants for {@link #make(List, int, List, String, String)} * to determine alpha type. Describes how to interpret the alpha component of a pixel. */ @IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT}) - public @interface AlphaType { - } + private @interface AlphaType {} /** * uninitialized. @@ -73,8 +75,7 @@ public class MeshSpecification { * Constants for {@link Attribute} and {@link Varying} for determining the data type. */ @IntDef({FLOAT, FLOAT2, FLOAT3, FLOAT4, UBYTE4}) - public @interface Type { - } + private @interface Type {} /** * Represents one float. Its equivalent shader type is float. @@ -118,7 +119,7 @@ public class MeshSpecification { private int mOffset; private String mName; - public Attribute(@Type int type, int offset, String name) { + public Attribute(@Type int type, int offset, @NonNull String name) { mType = type; mOffset = offset; mName = name; @@ -134,7 +135,7 @@ public class MeshSpecification { private int mType; private String mName; - public Varying(@Type int type, String name) { + public Varying(@Type int type, @NonNull String name) { mType = type; mName = name; } @@ -162,10 +163,13 @@ public class MeshSpecification { * @param fragmentShader fragment shader to be supplied to the mesh. * @return {@link MeshSpecification} object for use when creating {@link Mesh} */ - public static MeshSpecification make(Attribute[] attributes, int vertexStride, - Varying[] varyings, String vertexShader, String fragmentShader) { - long nativeMeshSpec = - nativeMake(attributes, vertexStride, varyings, vertexShader, fragmentShader); + @NonNull + public static MeshSpecification make(@NonNull List attributes, int vertexStride, + @NonNull List varyings, @NonNull String vertexShader, + @NonNull String fragmentShader) { + long nativeMeshSpec = nativeMake(attributes.toArray(new Attribute[attributes.size()]), + vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader, + fragmentShader); if (nativeMeshSpec == 0) { throw new IllegalArgumentException("MeshSpecification construction failed"); } @@ -189,9 +193,12 @@ public class MeshSpecification { * @param colorSpace {@link ColorSpace} to tell what color space to work in. * @return {@link MeshSpecification} object for use when creating {@link Mesh} */ - public static MeshSpecification make(Attribute[] attributes, int vertexStride, - Varying[] varyings, String vertexShader, String fragmentShader, ColorSpace colorSpace) { - long nativeMeshSpec = nativeMakeWithCS(attributes, vertexStride, varyings, vertexShader, + @NonNull + public static MeshSpecification make(@NonNull List attributes, int vertexStride, + @NonNull List varyings, @NonNull String vertexShader, + @NonNull String fragmentShader, @NonNull ColorSpace colorSpace) { + long nativeMeshSpec = nativeMakeWithCS(attributes.toArray(new Attribute[attributes.size()]), + vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader, fragmentShader, colorSpace.getNativeInstance()); if (nativeMeshSpec == 0) { throw new IllegalArgumentException("MeshSpecification construction failed"); @@ -215,14 +222,22 @@ public class MeshSpecification { * @param fragmentShader fragment shader to be supplied to the mesh. * @param colorSpace {@link ColorSpace} to tell what color space to work in. * @param alphaType Describes how to interpret the alpha component for a pixel. Must be - * one of {@link AlphaType} values. + * one of + * {@link MeshSpecification#UNKNOWN}, + * {@link MeshSpecification#OPAQUE}, + * {@link MeshSpecification#PREMUL}, or + * {@link MeshSpecification#UNPREMULT} * @return {@link MeshSpecification} object for use when creating {@link Mesh} */ - public static MeshSpecification make(Attribute[] attributes, int vertexStride, - Varying[] varyings, String vertexShader, String fragmentShader, ColorSpace colorSpace, + @NonNull + public static MeshSpecification make(@NonNull List attributes, int vertexStride, + @NonNull List varyings, @NonNull String vertexShader, + @NonNull String fragmentShader, @NonNull ColorSpace colorSpace, @AlphaType int alphaType) { - long nativeMeshSpec = nativeMakeWithAlpha(attributes, vertexStride, varyings, vertexShader, - fragmentShader, colorSpace.getNativeInstance(), alphaType); + long nativeMeshSpec = + nativeMakeWithAlpha(attributes.toArray(new Attribute[attributes.size()]), + vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader, + fragmentShader, colorSpace.getNativeInstance(), alphaType); if (nativeMeshSpec == 0) { throw new IllegalArgumentException("MeshSpecification construction failed"); } diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp index 7b9a93fe0fba..3aac48dd08b1 100644 --- a/libs/hwui/jni/Mesh.cpp +++ b/libs/hwui/jni/Mesh.cpp @@ -44,10 +44,16 @@ static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject verte sk_sp skVertexBuffer = genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect); auto skRect = SkRect::MakeLTRB(left, top, right, bottom); - auto mesh = SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, - vertexOffset, nullptr, skRect) - .mesh; - auto meshPtr = std::make_unique(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)}); + auto meshResult = SkMesh::Make( + skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset, + SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect); + + if (!meshResult.error.isEmpty()) { + jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str()); + } + + auto meshPtr = std::make_unique( + MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)}); return reinterpret_cast(meshPtr.release()); } @@ -61,11 +67,17 @@ static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobjec sk_sp skIndexBuffer = genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect); auto skRect = SkRect::MakeLTRB(left, top, right, bottom); - auto mesh = SkMesh::MakeIndexed(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, - vertexOffset, skIndexBuffer, indexCount, indexOffset, nullptr, - skRect) - .mesh; - auto meshPtr = std::make_unique(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)}); + + auto meshResult = SkMesh::MakeIndexed( + skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset, + skIndexBuffer, indexCount, indexOffset, + SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect); + + if (!meshResult.error.isEmpty()) { + jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str()); + } + auto meshPtr = std::make_unique( + MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)}); return reinterpret_cast(meshPtr.release()); } @@ -139,22 +151,22 @@ static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder, } } -static void updateFloatUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName, +static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName, jfloat value1, jfloat value2, jfloat value3, jfloat value4, jint count) { - auto* builder = reinterpret_cast(uniBuilder); + auto* wrapper = reinterpret_cast(meshWrapper); ScopedUtfChars name(env, uniformName); const float values[4] = {value1, value2, value3, value4}; - nativeUpdateFloatUniforms(env, builder, name.c_str(), values, count, false); + nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), values, count, false); } -static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring jUniformName, +static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName, jfloatArray jvalues, jboolean isColor) { - auto builder = reinterpret_cast(uniBuilder); + auto wrapper = reinterpret_cast(meshWrapper); ScopedUtfChars name(env, jUniformName); AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess); - nativeUpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(), - isColor); + nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(), + autoValues.length(), isColor); } static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder, @@ -171,20 +183,21 @@ static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder, } } -static void updateIntUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName, +static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName, jint value1, jint value2, jint value3, jint value4, jint count) { - auto builder = reinterpret_cast(uniBuilder); + auto wrapper = reinterpret_cast(meshWrapper); ScopedUtfChars name(env, uniformName); const int values[4] = {value1, value2, value3, value4}; - nativeUpdateIntUniforms(env, builder, name.c_str(), values, count); + nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), values, count); } -static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName, +static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName, jintArray values) { - auto builder = reinterpret_cast(uniBuilder); + auto wrapper = reinterpret_cast(meshWrapper); ScopedUtfChars name(env, uniformName); AutoJavaIntArray autoValues(env, values, 0); - nativeUpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length()); + nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(), + autoValues.length()); } static void MeshWrapper_destroy(MeshWrapper* wrapper) { diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h index aa014a5e4f61..7a73f2d5c470 100644 --- a/libs/hwui/jni/Mesh.h +++ b/libs/hwui/jni/Mesh.h @@ -239,6 +239,7 @@ public: explicit MeshUniformBuilder(sk_sp meshSpec) { fMeshSpec = sk_sp(meshSpec); + fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize())); } sk_sp fUniforms; diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp index 619a3ed552d8..ae9792df3d82 100644 --- a/libs/hwui/jni/MeshSpecification.cpp +++ b/libs/hwui/jni/MeshSpecification.cpp @@ -78,7 +78,6 @@ static jlong Make(JNIEnv* env, jobject thiz, jobjectArray attributeArray, jint v auto meshSpecResult = SkMeshSpecification::Make(attributes, vertexStride, varyings, SkString(skVertexShader.c_str()), SkString(skFragmentShader.c_str())); - if (meshSpecResult.specification.get() == nullptr) { jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str()); } diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java index efe242ce728e..d3a5885b324d 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java @@ -32,6 +32,7 @@ import android.view.View; import java.nio.FloatBuffer; import java.nio.ShortBuffer; +import java.util.ArrayList; public class MeshActivity extends Activity { @Override @@ -64,7 +65,9 @@ public class MeshActivity extends Activity { vertexBuffer.put(5, 400.0f); vertexBuffer.rewind(); Mesh mesh = Mesh.make( - meshSpec, Mesh.Mode.Triangles, vertexBuffer, 3, new Rect(0, 0, 1000, 1000)); + meshSpec, Mesh.TRIANGLES, vertexBuffer, 3, new Rect(0, 0, 1000, 1000)); + + canvas.drawMesh(mesh, BlendMode.COLOR, new Paint()); int numTriangles = 100; // number of triangles plus first 2 vertices @@ -95,12 +98,10 @@ public class MeshActivity extends Activity { } iVertexBuffer.rewind(); indexBuffer.rewind(); - Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.Mode.Triangles, iVertexBuffer, 102, - indexBuffer, new Rect(0, 0, 1000, 1000)); - + Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.TRIANGLES, iVertexBuffer, 102, indexBuffer, + new Rect(0, 0, 1000, 1000)); Paint paint = new Paint(); paint.setColor(Color.RED); - canvas.drawMesh(mesh, BlendMode.COLOR, new Paint()); canvas.drawMesh(mesh2, BlendMode.COLOR, paint); } @@ -114,10 +115,9 @@ public class MeshActivity extends Activity { + " color = vec4(1.0, 0.0, 0.0, 1.0);" + " return varyings.position;\n" + "}"; - Attribute[] attList = - new Attribute[] {new Attribute(MeshSpecification.FLOAT2, 0, "position")}; - Varying[] varyList = - new MeshSpecification.Varying[] {}; + ArrayList attList = new ArrayList<>(); + attList.add(new Attribute(MeshSpecification.FLOAT2, 0, "position")); + ArrayList varyList = new ArrayList<>(); return MeshSpecification.make(attList, 8, varyList, vs, fs); } } -- cgit v1.2.3-59-g8ed1b From 5d3141ffeccd01061c5e74f95c77aca6ccd36dc7 Mon Sep 17 00:00:00 2001 From: Angel Aguayo Date: Tue, 20 Dec 2022 20:44:34 +0000 Subject: Publicly Expose drawMesh APIs Bug: b/253321460 Test: None Change-Id: I65486ba995577391a2886fe0d5f472c2a34480da --- core/api/current.txt | 45 ++++++++++++++++++++++ graphics/java/android/graphics/BaseCanvas.java | 2 - graphics/java/android/graphics/Mesh.java | 2 - .../java/android/graphics/MeshSpecification.java | 2 - 4 files changed, 45 insertions(+), 6 deletions(-) (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index 74b3ba93564a..9c4cc2e20233 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -14631,6 +14631,7 @@ package android.graphics { method public void drawLine(float, float, float, float, @NonNull android.graphics.Paint); method public void drawLines(@NonNull @Size(multiple=4) float[], int, int, @NonNull android.graphics.Paint); method public void drawLines(@NonNull @Size(multiple=4) float[], @NonNull android.graphics.Paint); + method public void drawMesh(@NonNull android.graphics.Mesh, android.graphics.BlendMode, @NonNull android.graphics.Paint); method public void drawOval(@NonNull android.graphics.RectF, @NonNull android.graphics.Paint); method public void drawOval(float, float, float, float, @NonNull android.graphics.Paint); method public void drawPaint(@NonNull android.graphics.Paint); @@ -15206,6 +15207,49 @@ package android.graphics { enum_constant public static final android.graphics.Matrix.ScaleToFit START; } + public class Mesh { + method @NonNull public static android.graphics.Mesh make(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull android.graphics.Rect); + method @NonNull public static android.graphics.Mesh makeIndexed(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull java.nio.ShortBuffer, @NonNull android.graphics.Rect); + method public void setColorUniform(@NonNull String, int); + method public void setColorUniform(@NonNull String, long); + method public void setColorUniform(@NonNull String, @NonNull android.graphics.Color); + method public void setFloatUniform(@NonNull String, float); + method public void setFloatUniform(@NonNull String, float, float); + method public void setFloatUniform(@NonNull String, float, float, float); + method public void setFloatUniform(@NonNull String, float, float, float, float); + method public void setFloatUniform(@NonNull String, @NonNull float[]); + method public void setIntUniform(@NonNull String, int); + method public void setIntUniform(@NonNull String, int, int); + method public void setIntUniform(@NonNull String, int, int, int); + method public void setIntUniform(@NonNull String, int, int, int, int); + method public void setIntUniform(@NonNull String, @NonNull int[]); + field public static final int TRIANGLES = 0; // 0x0 + field public static final int TRIANGLE_STRIP = 1; // 0x1 + } + + public class MeshSpecification { + method @NonNull public static android.graphics.MeshSpecification make(@NonNull java.util.List, int, @NonNull java.util.List, @NonNull String, @NonNull String); + method @NonNull public static android.graphics.MeshSpecification make(@NonNull java.util.List, int, @NonNull java.util.List, @NonNull String, @NonNull String, @NonNull android.graphics.ColorSpace); + method @NonNull public static android.graphics.MeshSpecification make(@NonNull java.util.List, int, @NonNull java.util.List, @NonNull String, @NonNull String, @NonNull android.graphics.ColorSpace, int); + field public static final int FLOAT = 0; // 0x0 + field public static final int FLOAT2 = 1; // 0x1 + field public static final int FLOAT3 = 2; // 0x2 + field public static final int FLOAT4 = 3; // 0x3 + field public static final int OPAQUE = 1; // 0x1 + field public static final int PREMUL = 2; // 0x2 + field public static final int UBYTE4 = 4; // 0x4 + field public static final int UNKNOWN = 0; // 0x0 + field public static final int UNPREMULT = 3; // 0x3 + } + + public static class MeshSpecification.Attribute { + ctor public MeshSpecification.Attribute(int, int, @NonNull String); + } + + public static class MeshSpecification.Varying { + ctor public MeshSpecification.Varying(int, @NonNull String); + } + @Deprecated public class Movie { method @Deprecated public static android.graphics.Movie decodeByteArray(byte[], int, int); method @Deprecated public static android.graphics.Movie decodeFile(String); @@ -15721,6 +15765,7 @@ package android.graphics { } public final class RecordingCanvas extends android.graphics.Canvas { + method public final void drawMesh(@NonNull android.graphics.Mesh, android.graphics.BlendMode, @NonNull android.graphics.Paint); } public final class Rect implements android.os.Parcelable { diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index 2f396c026706..00ffd09eb29e 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -673,8 +673,6 @@ public abstract class BaseCanvas { * @param mesh {@link Mesh} object that will be drawn to the screen * @param blendMode {@link BlendMode} used to blend mesh primitives with the Paint color/shader * @param paint {@link Paint} used to provide a color/shader/blend mode. - * - * @hide */ public void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) { if (!isHardwareAccelerated() && onHwFeatureInSwMode()) { diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index e186386a21be..a599b2c17604 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -34,8 +34,6 @@ import java.nio.ShortBuffer; * detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds * for the mesh. Once generated, a mesh object can be drawn through * {@link Canvas#drawMesh(Mesh, BlendMode, Paint)} - * - * @hide */ public class Mesh { private long mNativeMeshWrapper; diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java index 6ef35960a73b..52b40029a0eb 100644 --- a/graphics/java/android/graphics/MeshSpecification.java +++ b/graphics/java/android/graphics/MeshSpecification.java @@ -38,8 +38,6 @@ import java.util.List; * * These should be kept in mind when generating a mesh specification, as exceeding them will * lead to errors. - * - * @hide */ public class MeshSpecification { long mNativeMeshSpec; -- cgit v1.2.3-59-g8ed1b From 9cda0d98c19c46e52c104c470aa0a0c4ad4b6429 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Wed, 21 Dec 2022 12:32:29 -0800 Subject: Update javadoc for new added ColorSpace. - The color space with non ICC transfer function is allowed. Bug: 241284309 Test: builds Change-Id: I64e4b887599ed60d79ed48e9d3c1f6748f12930f --- graphics/java/android/graphics/Bitmap.java | 13 +++++++++---- graphics/java/android/graphics/ImageDecoder.java | 9 +++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 318cd32d19fe..3c654d6dec48 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -1810,10 +1810,15 @@ public final class Bitmap implements Parcelable { * {@code ColorSpace}.

    * * @throws IllegalArgumentException If the specified color space is {@code null}, not - * {@link ColorSpace.Model#RGB RGB}, has a transfer function that is not an - * {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or whose - * components min/max values reduce the numerical range compared to the - * previously assigned color space. + * {@link ColorSpace.Model#RGB RGB}, or whose components min/max values reduce + * the numerical range compared to the previously assigned color space. + * Prior to {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * IllegalArgumentException will also be thrown + * if the specified color space has a transfer function that is not an + * {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. Starting from + * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the color spaces with non + * ICC parametric curve transfer function are allowed. + * E.g., {@link ColorSpace.Named#BT2020_HLG BT2020_HLG}. * * @throws IllegalArgumentException If the {@code Config} (returned by {@link #getConfig()}) * is {@link Config#ALPHA_8}. diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 239621eeed1e..51f99ec637da 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -1682,11 +1682,16 @@ public final class ImageDecoder implements AutoCloseable { * {@link #decodeBitmap decodeBitmap} when setting a non-RGB color space * such as {@link ColorSpace.Named#CIE_LAB Lab}.

    * - *

    The specified color space's transfer function must be + *

    Prior to {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * the specified color space's transfer function must be * an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. An * IllegalArgumentException will be thrown by the decode methods * if calling {@link ColorSpace.Rgb#getTransferParameters()} on the - * specified color space returns null.

    + * specified color space returns null. + * Starting from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * the color spaces with non ICC parametric curve transfer function are allowed. + * E.g., {@link ColorSpace.Named#BT2020_HLG BT2020_HLG}. + *

    * *

    Like all setters on ImageDecoder, this must be called inside * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.

    -- cgit v1.2.3-59-g8ed1b From ac5f755472e02f039f947ccfd5f5282e0ac80fe3 Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Mon, 19 Dec 2022 22:03:27 +0000 Subject: Send load up hint on view inflation In order for HWUI to keep up with sudden changes in workload, it needs to send hints in advance of view inflation in order to compensate for these increases. This patch allows HWUI to inform PowerHAL about upcoming spikes in workload. Bug: b/261130508 Test: manual Change-Id: Ie0bb80d021dee13067d1960276065f6f2e0af34d --- core/java/android/view/LayoutInflater.java | 8 ++++++++ core/java/android/view/ThreadedRenderer.java | 7 +++++++ core/java/android/view/ViewRootImpl.java | 12 ++++++++++++ graphics/java/android/graphics/HardwareRenderer.java | 11 +++++++++++ libs/hwui/jni/android_graphics_HardwareRenderer.cpp | 7 +++++++ libs/hwui/renderthread/CanvasContext.cpp | 4 ++++ libs/hwui/renderthread/CanvasContext.h | 2 ++ libs/hwui/renderthread/HintSessionWrapper.cpp | 5 +++++ libs/hwui/renderthread/HintSessionWrapper.h | 1 + libs/hwui/renderthread/RenderProxy.cpp | 4 ++++ libs/hwui/renderthread/RenderProxy.h | 1 + 11 files changed, 62 insertions(+) (limited to 'graphics/java/android') diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index df78827534a6..99a7fe598936 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -640,6 +640,10 @@ public abstract class LayoutInflater { mConstructorArgs[0] = inflaterContext; View result = root; + if (root != null && root.getViewRootImpl() != null) { + root.getViewRootImpl().notifyRendererOfExpensiveFrame(); + } + try { advanceToRootNode(parser); final String name = parser.getName(); @@ -662,6 +666,10 @@ public abstract class LayoutInflater { // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, inflaterContext, attrs); + if (root == null && temp != null && temp.getViewRootImpl() != null) { + temp.getViewRootImpl().notifyRendererOfExpensiveFrame(); + } + ViewGroup.LayoutParams params = null; if (root != null) { diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 164a49464051..9c6e823b549e 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -594,6 +594,13 @@ public final class ThreadedRenderer extends HardwareRenderer { } } + @Override + public void notifyExpensiveFrame() { + if (isEnabled()) { + super.notifyExpensiveFrame(); + } + } + /** * Updates the light position based on the position of the window. * diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ea7a64ef5c24..30e98a4e64f7 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2334,6 +2334,18 @@ public final class ViewRootImpl implements ViewParent, } } + /** + * Notifies the HardwareRenderer of an expensive upcoming frame, to + * allow better handling of power and scheduling requirements. + * + * @hide + */ + void notifyRendererOfExpensiveFrame() { + if (mAttachInfo.mThreadedRenderer != null) { + mAttachInfo.mThreadedRenderer.notifyExpensiveFrame(); + } + } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) void scheduleTraversals() { if (!mTraversalScheduled) { diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index c7c97e0b82b9..0892a9b14738 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -991,6 +991,15 @@ public class HardwareRenderer { nNotifyCallbackPending(mNativeProxy); } + /** + * Notifies the hardware renderer about upcoming expensive frames. + * + * @hide + */ + public void notifyExpensiveFrame() { + nNotifyExpensiveFrame(mNativeProxy); + } + /** * b/68769804, b/66945974: For low FPS experiments. * @@ -1551,4 +1560,6 @@ public class HardwareRenderer { private static native void nSetRtAnimationsEnabled(boolean rtAnimationsEnabled); private static native void nNotifyCallbackPending(long nativeProxy); + + private static native void nNotifyExpensiveFrame(long nativeProxy); } diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index 0663121a4027..e457c311f689 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -865,6 +865,11 @@ static void android_view_ThreadedRenderer_notifyCallbackPending(JNIEnv*, jclass, proxy->notifyCallbackPending(); } +static void android_view_ThreadedRenderer_notifyExpensiveFrame(JNIEnv*, jclass, jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast(proxyPtr); + proxy->notifyExpensiveFrame(); +} + // Plumbs the display density down to DeviceInfo. static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass, jint densityDpi) { // Convert from dpi to density-independent pixels. @@ -1044,6 +1049,8 @@ static const JNINativeMethod gMethods[] = { (void*)android_view_ThreadedRenderer_setRtAnimationsEnabled}, {"nNotifyCallbackPending", "(J)V", (void*)android_view_ThreadedRenderer_notifyCallbackPending}, + {"nNotifyExpensiveFrame", "(J)V", + (void*)android_view_ThreadedRenderer_notifyExpensiveFrame}, }; static JavaVM* mJvm = nullptr; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 64839d0147f8..b92c90edd1ca 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -1006,6 +1006,10 @@ void CanvasContext::sendLoadResetHint() { mHintSessionWrapper.sendLoadResetHint(); } +void CanvasContext::sendLoadIncreaseHint() { + mHintSessionWrapper.sendLoadIncreaseHint(); +} + void CanvasContext::setSyncDelayDuration(nsecs_t duration) { mSyncDelayDuration = duration; } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index e875c42e9eba..d29e12c38d9f 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -218,6 +218,8 @@ public: void sendLoadResetHint(); + void sendLoadIncreaseHint(); + void setSyncDelayDuration(nsecs_t duration); private: diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp index 94c9d94a7c26..dece548e9dc1 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.cpp +++ b/libs/hwui/renderthread/HintSessionWrapper.cpp @@ -158,6 +158,11 @@ void HintSessionWrapper::sendLoadResetHint() { mLastFrameNotification = now; } +void HintSessionWrapper::sendLoadIncreaseHint() { + if (!useHintSession()) return; + gAPH_sendHintFn(mHintSession, static_cast(SessionHint::CPU_LOAD_UP)); +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h index fcbc10185255..c0f7a57bed75 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.h +++ b/libs/hwui/renderthread/HintSessionWrapper.h @@ -33,6 +33,7 @@ public: void updateTargetWorkDuration(long targetDurationNanos); void reportActualWorkDuration(long actualDurationNanos); void sendLoadResetHint(); + void sendLoadIncreaseHint(); private: bool useHintSession(); diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 07f5a78fc1ec..04a6cbd1a577 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -242,6 +242,10 @@ void RenderProxy::notifyCallbackPending() { mRenderThread.queue().post([this]() { mContext->sendLoadResetHint(); }); } +void RenderProxy::notifyExpensiveFrame() { + mRenderThread.queue().post([this]() { mContext->sendLoadIncreaseHint(); }); +} + void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) { mRenderThread.queue().runSync([&]() { std::lock_guard lock(mRenderThread.getJankDataMutex()); diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index a21faa86ed0f..1064225b69ae 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -110,6 +110,7 @@ public: void stopDrawing(); void notifyFramePending(); void notifyCallbackPending(); + void notifyExpensiveFrame(); void dumpProfileInfo(int fd, int dumpFlags); // Not exported, only used for testing -- cgit v1.2.3-59-g8ed1b From 4cf47f1b79d4b9ec4cc9dd19cb9a550d0bb21280 Mon Sep 17 00:00:00 2001 From: Chet Haase Date: Wed, 4 Jan 2023 20:56:32 +0000 Subject: Change behavior for next() and hasNext() for empty paths Original behavior treated DONE like any other path verb, and threw an exception when querying an iterator that had already issued VERB_DONE. Also, hasNext() returned false after when next() would result in an exception. New behavior treats DONE as just informational: - An empty iterator, or one with no more verbs to iterate on, will always return VERB_DONE for next() - hasNext() will only return true when there are non-DONE verbs left to return from next(). Also, clarified docs for the verbs. Bug: 264437928 Test: PathTest and PathIteratorTest, including new test for hasNext() Change-Id: Icc0517ac3c2a346a3271ff2f78e7629e4ebb5d0a --- graphics/java/android/graphics/PathIterator.java | 26 +++++++++--------------- 1 file changed, 10 insertions(+), 16 deletions(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java index 33b9a475ad21..a77457dd2431 100644 --- a/graphics/java/android/graphics/PathIterator.java +++ b/graphics/java/android/graphics/PathIterator.java @@ -29,8 +29,6 @@ import libcore.util.NativeAllocationRegistry; import java.lang.annotation.Retention; import java.util.ConcurrentModificationException; import java.util.Iterator; -import java.util.NoSuchElementException; - /** * PathIterator can be used to query a given {@link Path} object, to discover its @@ -54,7 +52,8 @@ public class PathIterator implements Iterator { /** * The Verb indicates the operation for a given segment of a path. These * operations correspond exactly to the primitive operations on {@link Path}, such as - * {@link Path#moveTo(float, float)} and {@link Path#lineTo(float, float)}. + * {@link Path#moveTo(float, float)} and {@link Path#lineTo(float, float)}, except for + * {@link #VERB_DONE}, which means that there are no more operations in this path. */ @Retention(SOURCE) @IntDef({VERB_MOVE, VERB_LINE, VERB_QUAD, VERB_CONIC, VERB_CUBIC, VERB_CLOSE, VERB_DONE}) @@ -109,7 +108,6 @@ public class PathIterator implements Iterator { * @throws ArrayIndexOutOfBoundsException if the points array is too small * @throws ConcurrentModificationException if the underlying path was modified * since this iterator was created. - * @throws NoSuchElementException if the iteration has no more elements */ @NonNull public @Verb int next(@NonNull float[] points, int offset) { @@ -126,8 +124,8 @@ public class PathIterator implements Iterator { /** * Returns true if the there are more elements in this iterator to be returned. * A return value of false means there are no more elements, and an - * ensuing call to {@link #next()} or {@link #next(float[], int)} )} will throw a - * {@link NoSuchElementException}. + * ensuing call to {@link #next()} or {@link #next(float[], int)} )} will return + * {@link #VERB_DONE}. * * @return true if there are more elements to be iterated through, false otherwise * @throws ConcurrentModificationException if the underlying path was modified @@ -135,14 +133,10 @@ public class PathIterator implements Iterator { */ @Override public boolean hasNext() { - try { - if (mCachedVerb == -1) { - mCachedVerb = nextInternal(); - } - return true; - } catch (NoSuchElementException e) { - return false; + if (mCachedVerb == -1) { + mCachedVerb = nextInternal(); } + return mCachedVerb != VERB_DONE; } /** @@ -171,12 +165,13 @@ public class PathIterator implements Iterator { * is helfpul for managing the cached segment used by {@link #hasNext()}. * * @return the segment to be returned by {@link #next()} - * @throws NoSuchElementException if the iteration has no more elements + * @throws ConcurrentModificationException if the underlying path was modified + * since this iterator was created. */ @NonNull private @Verb int nextInternal() { if (mDone) { - throw new NoSuchElementException("No more path segments to iterate"); + return VERB_DONE; } if (mPathGenerationId != mPath.getGenerationId()) { throw new ConcurrentModificationException( @@ -198,7 +193,6 @@ public class PathIterator implements Iterator { * requires a little more manual effort to use. * * @return the next segment in this iterator - * @throws NoSuchElementException if the iteration has no more elements * @throws ConcurrentModificationException if the underlying path was modified * since this iterator was created. */ -- cgit v1.2.3-59-g8ed1b From 7612c613c9a5d41892a201eafd4949424497fb5f Mon Sep 17 00:00:00 2001 From: Nader Jawad Date: Tue, 15 Nov 2022 10:55:54 -0800 Subject: Created HardwareBufferRenderer to support rendering into HardwareBuffer targets. Relnote: "Created HardwareBufferRenderer API to handle rendering a single frame into a HardwareBuffer target." Refactored dlsym logic for dynamically resolving AHardwareBuffer methods to be shared across multiple locations. Bug: 255692581 Test: Created HardwareBufferRendererTests Change-Id: I749b5d763a9ee580abc2d6cc87bd94a46b7abdd9 --- core/api/current.txt | 23 ++ core/java/android/hardware/SyncFence.java | 22 +- core/jni/LayoutlibLoader.cpp | 1 + .../android/graphics/HardwareBufferRenderer.java | 390 +++++++++++++++++++++ libs/hwui/Android.bp | 2 + libs/hwui/apex/jni_runtime.cpp | 3 + libs/hwui/jni/Bitmap.cpp | 46 +-- libs/hwui/jni/GraphicsJNI.h | 31 +- libs/hwui/jni/HardwareBufferHelpers.cpp | 68 ++++ libs/hwui/jni/HardwareBufferHelpers.h | 38 ++ libs/hwui/jni/JvmErrorReporter.h | 44 +++ .../android_graphics_HardwareBufferRenderer.cpp | 177 ++++++++++ .../hwui/jni/android_graphics_HardwareRenderer.cpp | 65 +--- libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp | 67 +++- libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h | 16 +- libs/hwui/pipeline/skia/SkiaPipeline.cpp | 25 ++ libs/hwui/pipeline/skia/SkiaPipeline.h | 11 + libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp | 48 ++- libs/hwui/pipeline/skia/SkiaVulkanPipeline.h | 24 +- libs/hwui/renderthread/CanvasContext.cpp | 20 +- libs/hwui/renderthread/CanvasContext.h | 12 +- libs/hwui/renderthread/DrawFrameTask.cpp | 14 +- libs/hwui/renderthread/DrawFrameTask.h | 13 + .../hwui/renderthread/HardwareBufferRenderParams.h | 62 ++++ libs/hwui/renderthread/IRenderPipeline.h | 18 +- libs/hwui/renderthread/RenderProxy.cpp | 16 + libs/hwui/renderthread/RenderProxy.h | 4 +- libs/hwui/tests/unit/CanvasContextTests.cpp | 2 +- tests/HwAccelerationTest/AndroidManifest.xml | 9 + .../test/hwui/HardwareBufferRendererActivity.java | 86 +++++ 30 files changed, 1200 insertions(+), 157 deletions(-) create mode 100644 graphics/java/android/graphics/HardwareBufferRenderer.java create mode 100644 libs/hwui/jni/HardwareBufferHelpers.cpp create mode 100644 libs/hwui/jni/HardwareBufferHelpers.h create mode 100644 libs/hwui/jni/JvmErrorReporter.h create mode 100644 libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp create mode 100644 libs/hwui/renderthread/HardwareBufferRenderParams.h create mode 100644 tests/HwAccelerationTest/src/com/android/test/hwui/HardwareBufferRendererActivity.java (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index 5dd1b3938f40..31659e435915 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -15080,6 +15080,29 @@ package android.graphics { ctor @Deprecated public EmbossMaskFilter(float[], float, float, float); } + public class HardwareBufferRenderer implements java.lang.AutoCloseable { + ctor public HardwareBufferRenderer(@NonNull android.hardware.HardwareBuffer); + method public void close(); + method public boolean isClosed(); + method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest obtainRenderRequest(); + method public void setContentRoot(@Nullable android.graphics.RenderNode); + method public void setLightSourceAlpha(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float); + method public void setLightSourceGeometry(float, float, @FloatRange(from=0.0f) float, @FloatRange(from=0.0f) float); + } + + public final class HardwareBufferRenderer.RenderRequest { + method public void draw(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest setBufferTransform(int); + method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest setColorSpace(@Nullable android.graphics.ColorSpace); + } + + public static final class HardwareBufferRenderer.RenderResult { + method @NonNull public android.hardware.SyncFence getFence(); + method public int getStatus(); + field public static final int ERROR_UNKNOWN = 1; // 0x1 + field public static final int SUCCESS = 0; // 0x0 + } + public class HardwareRenderer { ctor public HardwareRenderer(); method public void clearContent(); diff --git a/core/java/android/hardware/SyncFence.java b/core/java/android/hardware/SyncFence.java index 166001347bd4..d6052cd4c67f 100644 --- a/core/java/android/hardware/SyncFence.java +++ b/core/java/android/hardware/SyncFence.java @@ -87,8 +87,8 @@ public final class SyncFence implements AutoCloseable, Parcelable { // is well worth making. private final Runnable mCloser; - private SyncFence(@NonNull ParcelFileDescriptor wrapped) { - mNativePtr = nCreate(wrapped.detachFd()); + private SyncFence(int fileDescriptor) { + mNativePtr = nCreate(fileDescriptor); mCloser = sRegistry.registerNativeAllocation(this, mNativePtr); } @@ -136,14 +136,26 @@ public final class SyncFence implements AutoCloseable, Parcelable { } /** - * Create a new SyncFence wrapped around another descriptor. By default, all method calls are - * delegated to the wrapped descriptor. + * Create a new SyncFence wrapped around another {@link ParcelFileDescriptor}. By default, all + * method calls are delegated to the wrapped descriptor. This takes ownership of the + * {@link ParcelFileDescriptor}. * * @param wrapped The descriptor to be wrapped. * @hide */ public static @NonNull SyncFence create(@NonNull ParcelFileDescriptor wrapped) { - return new SyncFence(wrapped); + return new SyncFence(wrapped.detachFd()); + } + + /** + * Create a new SyncFence wrapped around another descriptor. The returned {@link SyncFence} + * instance takes ownership of the file descriptor. + * + * @param fileDescriptor The descriptor to be wrapped. + * @hide + */ + public static @NonNull SyncFence adopt(int fileDescriptor) { + return new SyncFence(fileDescriptor); } /** diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index 93ba23bfdf84..d7cbf74d421a 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -102,6 +102,7 @@ extern int register_android_view_KeyCharacterMap(JNIEnv* env); extern int register_android_view_KeyEvent(JNIEnv* env); extern int register_android_view_MotionEvent(JNIEnv* env); extern int register_android_view_ThreadedRenderer(JNIEnv* env); +extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); extern int register_android_view_VelocityTracker(JNIEnv* env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); diff --git a/graphics/java/android/graphics/HardwareBufferRenderer.java b/graphics/java/android/graphics/HardwareBufferRenderer.java new file mode 100644 index 000000000000..361dc594f2c6 --- /dev/null +++ b/graphics/java/android/graphics/HardwareBufferRenderer.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import android.annotation.FloatRange; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.ColorSpace.Named; +import android.hardware.HardwareBuffer; +import android.hardware.SyncFence; +import android.view.SurfaceControl; + +import libcore.util.NativeAllocationRegistry; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + *

    Creates an instance of a hardware-accelerated renderer. This is used to render a scene built + * from {@link RenderNode}s to an output {@link HardwareBuffer}. There can be as many + * HardwareBufferRenderer instances as desired.

    + * + *

    Resources & lifecycle

    + * + *

    All HardwareBufferRenderer and {@link HardwareRenderer} instances share a common render + * thread. Therefore HardwareBufferRenderer will share common resources and GPU utilization with + * hardware accelerated rendering initiated by the UI thread of an application. + * The render thread contains the GPU context & resources necessary to do GPU-accelerated + * rendering. As such, the first HardwareBufferRenderer created comes with the cost of also creating + * the associated GPU contexts, however each incremental HardwareBufferRenderer thereafter is fairly + * cheap. The expected usage is to have a HardwareBufferRenderer instance for every active {@link + * HardwareBuffer}.

    + * + * This is useful in situations where a scene built with {@link RenderNode}s can be consumed + * directly by the system compositor through + * {@link SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}. + * + * HardwareBufferRenderer will never clear contents before each draw invocation so previous contents + * in the {@link HardwareBuffer} target will be preserved across renders. + */ +public class HardwareBufferRenderer implements AutoCloseable { + + private static final ColorSpace DEFAULT_COLORSPACE = ColorSpace.get(Named.SRGB); + + private static class HardwareBufferRendererHolder { + public static final NativeAllocationRegistry REGISTRY = + NativeAllocationRegistry.createMalloced( + HardwareBufferRenderer.class.getClassLoader(), nGetFinalizer()); + } + + private final HardwareBuffer mHardwareBuffer; + private final RenderRequest mRenderRequest; + private final RenderNode mRootNode; + private final Runnable mCleaner; + + private long mProxy; + + /** + * Creates a new instance of {@link HardwareBufferRenderer} with the provided {@link + * HardwareBuffer} as the output of the rendered scene. + */ + public HardwareBufferRenderer(@NonNull HardwareBuffer buffer) { + RenderNode rootNode = RenderNode.adopt(nCreateRootRenderNode()); + rootNode.setClipToBounds(false); + mProxy = nCreateHardwareBufferRenderer(buffer, rootNode.mNativeRenderNode); + mCleaner = HardwareBufferRendererHolder.REGISTRY.registerNativeAllocation(this, mProxy); + mRenderRequest = new RenderRequest(); + mRootNode = rootNode; + mHardwareBuffer = buffer; + } + + /** + * Sets the content root to render. It is not necessary to call this whenever the content + * recording changes. Any mutations to the RenderNode content, or any of the RenderNodes + * contained within the content node, will be applied whenever a new {@link RenderRequest} is + * issued via {@link #obtainRenderRequest()} and {@link RenderRequest#draw(Executor, + * Consumer)}. + * + * @param content The content to set as the root RenderNode. If null the content root is removed + * and the renderer will draw nothing. + */ + public void setContentRoot(@Nullable RenderNode content) { + RecordingCanvas canvas = mRootNode.beginRecording(); + if (content != null) { + canvas.drawRenderNode(content); + } + mRootNode.endRecording(); + } + + /** + * Returns a {@link RenderRequest} that can be used to render into the provided {@link + * HardwareBuffer}. This is used to synchronize the RenderNode content provided by {@link + * #setContentRoot(RenderNode)}. + * + * @return An instance of {@link RenderRequest}. The instance may be reused for every frame, so + * the caller should not hold onto it for longer than a single render request. + */ + @NonNull + public RenderRequest obtainRenderRequest() { + mRenderRequest.reset(); + return mRenderRequest; + } + + /** + * Returns if the {@link HardwareBufferRenderer} has already been closed. That is + * {@link HardwareBufferRenderer#close()} has been invoked. + * @return True if the {@link HardwareBufferRenderer} has been closed, false otherwise. + */ + public boolean isClosed() { + return mProxy == 0L; + } + + /** + * Releases the resources associated with this {@link HardwareBufferRenderer} instance. **Note** + * this does not call {@link HardwareBuffer#close()} on the provided {@link HardwareBuffer} + * instance + */ + @Override + public void close() { + // Note we explicitly call this only here to clean-up potential animator state + // This is not done as part of the NativeAllocationRegistry as it would invoke animator + // callbacks on the wrong thread + nDestroyRootRenderNode(mRootNode.mNativeRenderNode); + if (mProxy != 0L) { + mCleaner.run(); + mProxy = 0L; + } + } + + /** + * Sets the center of the light source. The light source point controls the directionality and + * shape of shadows rendered by RenderNode Z & elevation. + * + *

    The light source should be setup both as part of initial configuration, and whenever + * the window moves to ensure the light source stays anchored in display space instead of in + * window space. + * + *

    This must be set at least once along with {@link #setLightSourceAlpha(float, float)} + * before shadows will work. + * + * @param lightX The X position of the light source. If unsure, a reasonable default + * is 'displayWidth / 2f - windowLeft'. + * @param lightY The Y position of the light source. If unsure, a reasonable default + * is '0 - windowTop' + * @param lightZ The Z position of the light source. Must be >= 0. If unsure, a reasonable + * default is 600dp. + * @param lightRadius The radius of the light source. Smaller radius will have sharper edges, + * larger radius will have softer shadows. If unsure, a reasonable default is 800 dp. + */ + public void setLightSourceGeometry( + float lightX, + float lightY, + @FloatRange(from = 0f) float lightZ, + @FloatRange(from = 0f) float lightRadius + ) { + validateFinite(lightX, "lightX"); + validateFinite(lightY, "lightY"); + validatePositive(lightZ, "lightZ"); + validatePositive(lightRadius, "lightRadius"); + nSetLightGeometry(mProxy, lightX, lightY, lightZ, lightRadius); + } + + /** + * Configures the ambient & spot shadow alphas. This is the alpha used when the shadow has max + * alpha, and ramps down from the values provided to zero. + * + *

    These values are typically provided by the current theme, see + * {@link android.R.attr#spotShadowAlpha} and {@link android.R.attr#ambientShadowAlpha}. + * + *

    This must be set at least once along with + * {@link #setLightSourceGeometry(float, float, float, float)} before shadows will work. + * + * @param ambientShadowAlpha The alpha for the ambient shadow. If unsure, a reasonable default + * is 0.039f. + * @param spotShadowAlpha The alpha for the spot shadow. If unsure, a reasonable default is + * 0.19f. + */ + public void setLightSourceAlpha(@FloatRange(from = 0.0f, to = 1.0f) float ambientShadowAlpha, + @FloatRange(from = 0.0f, to = 1.0f) float spotShadowAlpha) { + validateAlpha(ambientShadowAlpha, "ambientShadowAlpha"); + validateAlpha(spotShadowAlpha, "spotShadowAlpha"); + nSetLightAlpha(mProxy, ambientShadowAlpha, spotShadowAlpha); + } + + /** + * Class that contains data regarding the result of the render request. + * Consumers are to wait on the provided {@link SyncFence} before consuming the HardwareBuffer + * provided to {@link HardwareBufferRenderer} as well as verify that the status returned by + * {@link RenderResult#getStatus()} returns {@link RenderResult#SUCCESS}. + */ + public static final class RenderResult { + + /** + * Render request was completed successfully + */ + public static final int SUCCESS = 0; + + /** + * Render request failed with an unknown error + */ + public static final int ERROR_UNKNOWN = 1; + + /** @hide **/ + @IntDef(value = {SUCCESS, ERROR_UNKNOWN}) + @Retention(RetentionPolicy.SOURCE) + public @interface RenderResultStatus{} + + private final SyncFence mFence; + private final int mResultStatus; + + private RenderResult(@NonNull SyncFence fence, @RenderResultStatus int resultStatus) { + mFence = fence; + mResultStatus = resultStatus; + } + + @NonNull + public SyncFence getFence() { + return mFence; + } + + @RenderResultStatus + public int getStatus() { + return mResultStatus; + } + } + + /** + * Sets the parameters that can be used to control a render request for a {@link + * HardwareBufferRenderer}. This is not thread-safe and must not be held on to for longer than a + * single request. + */ + public final class RenderRequest { + + private ColorSpace mColorSpace = DEFAULT_COLORSPACE; + private int mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY; + + private RenderRequest() { } + + /** + * Syncs the RenderNode tree to the render thread and requests content to be drawn. This + * {@link RenderRequest} instance should no longer be used after calling this method. The + * system internally may reuse instances of {@link RenderRequest} to reduce allocation + * churn. + * + * @param executor Executor used to deliver callbacks + * @param renderCallback Callback invoked when rendering is complete. This includes a + * {@link RenderResult} that provides a {@link SyncFence} that should be waited upon for + * completion before consuming the rendered output in the provided {@link HardwareBuffer} + * instance. + * + * @throws IllegalStateException if attempt to draw is made when + * {@link HardwareBufferRenderer#isClosed()} returns true + */ + public void draw( + @NonNull Executor executor, + @NonNull Consumer renderCallback + ) { + Consumer wrapped = consumable -> executor.execute( + () -> renderCallback.accept(consumable)); + if (!isClosed()) { + nRender( + mProxy, + mTransform, + mHardwareBuffer.getWidth(), + mHardwareBuffer.getHeight(), + mColorSpace.getNativeInstance(), + wrapped); + } else { + throw new IllegalStateException("Attempt to draw with a HardwareBufferRenderer " + + "instance that has already been closed"); + } + } + + private void reset() { + mColorSpace = DEFAULT_COLORSPACE; + mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY; + } + + /** + * Configures the color space which the content should be rendered in. This affects + * how the framework will interpret the color at each pixel. The color space provided here + * must be non-null, RGB based and leverage an ICC parametric curve. The min/max values + * of the components should not reduce the numerical range compared to the previously + * assigned color space. If left unspecified, the default color space of SRGB will be used. + * + * @param colorSpace The color space the content should be rendered in. If null is provided + * the default of SRGB will be used. + */ + @NonNull + public RenderRequest setColorSpace(@Nullable ColorSpace colorSpace) { + if (colorSpace == null) { + mColorSpace = DEFAULT_COLORSPACE; + } else { + mColorSpace = colorSpace; + } + return this; + } + + /** + * Specifies a transform to be applied before content is rendered. This is useful + * for pre-rotating content for the current display orientation to increase performance + * of displaying the associated buffer. This transformation will also adjust the light + * source position for the specified rotation. + * @see SurfaceControl.Transaction#setBufferTransform(SurfaceControl, int) + */ + @NonNull + public RenderRequest setBufferTransform( + @SurfaceControl.BufferTransform int bufferTransform) { + boolean validTransform = bufferTransform == SurfaceControl.BUFFER_TRANSFORM_IDENTITY + || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_90 + || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_180 + || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_270; + if (validTransform) { + mTransform = bufferTransform; + } else { + throw new IllegalArgumentException("Invalid transform provided, must be one of" + + "the SurfaceControl.BufferTransform values"); + } + return this; + } + } + + /** + * @hide + */ + /* package */ + static native int nRender(long renderer, int transform, int width, int height, long colorSpace, + Consumer callback); + + private static native long nCreateRootRenderNode(); + + private static native void nDestroyRootRenderNode(long rootRenderNode); + + private static native long nCreateHardwareBufferRenderer(HardwareBuffer buffer, + long rootRenderNode); + + private static native void nSetLightGeometry(long bufferRenderer, float lightX, float lightY, + float lightZ, float radius); + + private static native void nSetLightAlpha(long nativeProxy, float ambientShadowAlpha, + float spotShadowAlpha); + + private static native long nGetFinalizer(); + + // Called by native + private static void invokeRenderCallback( + @NonNull Consumer callback, + int fd, + int status + ) { + callback.accept(new RenderResult(SyncFence.adopt(fd), status)); + } + + private static void validateAlpha(float alpha, String argumentName) { + if (!(alpha >= 0.0f && alpha <= 1.0f)) { + throw new IllegalArgumentException(argumentName + " must be a valid alpha, " + + alpha + " is not in the range of 0.0f to 1.0f"); + } + } + + private static void validateFinite(float f, String argumentName) { + if (!Float.isFinite(f)) { + throw new IllegalArgumentException(argumentName + " must be finite, given=" + f); + } + } + + private static void validatePositive(float f, String argumentName) { + if (!(Float.isFinite(f) && f >= 0.0f)) { + throw new IllegalArgumentException(argumentName + + " must be a finite positive, given=" + f); + } + } +} diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 3e3d77b89a9a..59e4b7acdba7 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -329,6 +329,7 @@ cc_defaults { "jni/android_util_PathParser.cpp", "jni/Bitmap.cpp", + "jni/HardwareBufferHelpers.cpp", "jni/BitmapFactory.cpp", "jni/ByteBufferStreamAdaptor.cpp", "jni/Camera.cpp", @@ -397,6 +398,7 @@ cc_defaults { "jni/AnimatedImageDrawable.cpp", "jni/android_graphics_TextureLayer.cpp", "jni/android_graphics_HardwareRenderer.cpp", + "jni/android_graphics_HardwareBufferRenderer.cpp", "jni/BitmapRegionDecoder.cpp", "jni/GIFMovie.cpp", "jni/GraphicsStatsService.cpp", diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp index e6cfa7bcaf70..f57d80c496ad 100644 --- a/libs/hwui/apex/jni_runtime.cpp +++ b/libs/hwui/apex/jni_runtime.cpp @@ -83,6 +83,7 @@ extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_view_DisplayListCanvas(JNIEnv* env); extern int register_android_view_RenderNode(JNIEnv* env); extern int register_android_view_ThreadedRenderer(JNIEnv* env); +extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); #ifdef NDEBUG #define REG_JNI(name) { name } @@ -151,6 +152,8 @@ extern int register_android_view_ThreadedRenderer(JNIEnv* env); REG_JNI(register_android_util_PathParser), REG_JNI(register_android_view_RenderNode), REG_JNI(register_android_view_DisplayListCanvas), + REG_JNI(register_android_graphics_HardwareBufferRenderer), + REG_JNI(register_android_view_ThreadedRenderer), }; diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index 94cea65897cf..540abec38ebc 100755 --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -2,48 +2,41 @@ #define LOG_TAG "Bitmap" #include "Bitmap.h" +#include +#include + +#include "CreateJavaOutputStreamAdaptor.h" #include "GraphicsJNI.h" +#include "HardwareBufferHelpers.h" #include "SkBitmap.h" #include "SkBlendMode.h" #include "SkCanvas.h" #include "SkColor.h" #include "SkColorSpace.h" #include "SkData.h" -#include "SkImageEncoder.h" #include "SkImageInfo.h" #include "SkPaint.h" -#include "SkPixelRef.h" #include "SkPixmap.h" #include "SkPoint.h" #include "SkRefCnt.h" #include "SkStream.h" #include "SkTypes.h" -#include "SkWebpEncoder.h" - - #include "android_nio_utils.h" -#include "CreateJavaOutputStreamAdaptor.h" -#include -#include -#include #ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread #include #include #include #include -#include -#include #include -#include #include #include #endif #include #include + #include -#include #define DEBUG_PARCEL 0 @@ -1197,18 +1190,11 @@ static jobject Bitmap_copyPreserveInternalConfig(JNIEnv* env, jobject, jlong bit return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(false)); } -#ifdef __ANDROID__ // Layoutlib does not support graphic buffer -typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject); -AHB_from_HB AHardwareBuffer_fromHardwareBuffer; - -typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*); -AHB_to_HB AHardwareBuffer_toHardwareBuffer; -#endif - static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer, jlong colorSpacePtr) { #ifdef __ANDROID__ // Layoutlib does not support graphic buffer - AHardwareBuffer* buffer = AHardwareBuffer_fromHardwareBuffer(env, hardwareBuffer); + AHardwareBuffer* buffer = uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer( + env, hardwareBuffer); sk_sp bitmap = Bitmap::createFrom(buffer, GraphicsJNI::getNativeColorSpace(colorSpacePtr)); if (!bitmap.get()) { @@ -1231,7 +1217,8 @@ static jobject Bitmap_getHardwareBuffer(JNIEnv* env, jobject, jlong bitmapPtr) { } Bitmap& bitmap = bitmapHandle->bitmap(); - return AHardwareBuffer_toHardwareBuffer(env, bitmap.hardwareBuffer()); + return uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer( + env, bitmap.hardwareBuffer()); #else return nullptr; #endif @@ -1329,18 +1316,7 @@ int register_android_graphics_Bitmap(JNIEnv* env) gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J"); gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V"); gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V"); - -#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel - void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); - AHardwareBuffer_fromHardwareBuffer = - (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer"); - LOG_ALWAYS_FATAL_IF(AHardwareBuffer_fromHardwareBuffer == nullptr, - "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!"); - - AHardwareBuffer_toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer"); - LOG_ALWAYS_FATAL_IF(AHardwareBuffer_toHardwareBuffer == nullptr, - " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!"); -#endif + uirenderer::HardwareBufferHelpers::init(); return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods, NELEM(gBitmapMethods)); } diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h index 085a905abaf8..c4a61ccd1e5f 100644 --- a/libs/hwui/jni/GraphicsJNI.h +++ b/libs/hwui/jni/GraphicsJNI.h @@ -2,19 +2,18 @@ #define _ANDROID_GRAPHICS_GRAPHICS_JNI_H_ #include +#include +#include -#include "Bitmap.h" #include "BRDAllocator.h" +#include "Bitmap.h" #include "SkBitmap.h" #include "SkCodec.h" -#include "SkPixelRef.h" +#include "SkColorSpace.h" #include "SkMallocPixelRef.h" +#include "SkPixelRef.h" #include "SkPoint.h" #include "SkRect.h" -#include "SkColorSpace.h" -#include -#include - #include "graphics_jni_helpers.h" class SkCanvas; @@ -335,6 +334,26 @@ private: int fLen; }; +class JGlobalRefHolder { +public: + JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {} + + virtual ~JGlobalRefHolder() { + GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject); + mObject = nullptr; + } + + jobject object() { return mObject; } + JavaVM* vm() { return mVm; } + +private: + JGlobalRefHolder(const JGlobalRefHolder&) = delete; + void operator=(const JGlobalRefHolder&) = delete; + + JavaVM* mVm; + jobject mObject; +}; + void doThrowNPE(JNIEnv* env); void doThrowAIOOBE(JNIEnv* env); // Array Index Out Of Bounds Exception void doThrowIAE(JNIEnv* env, const char* msg = NULL); // Illegal Argument diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp new file mode 100644 index 000000000000..7e3f771b6b3d --- /dev/null +++ b/libs/hwui/jni/HardwareBufferHelpers.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "HardwareBufferHelpers.h" + +#include +#include + +#ifdef __ANDROID__ +typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject); +typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*); +static AHB_from_HB fromHardwareBuffer = nullptr; +static AHB_to_HB toHardwareBuffer = nullptr; +#endif + +void android::uirenderer::HardwareBufferHelpers::init() { +#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel + void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); + fromHardwareBuffer = (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer"); + LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr, + "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!"); + + toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer"); + LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr, + " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!"); +#endif +} + +AHardwareBuffer* android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer( + JNIEnv* env, jobject hardwarebuffer) { +#ifdef __ANDROID__ + LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr, + "Failed to find symbol AHardwareBuffer_fromHardwareBuffer, did you forget " + "to call HardwareBufferHelpers::init?"); + return fromHardwareBuffer(env, hardwarebuffer); +#else + ALOGE("ERROR attempting to invoke AHardwareBuffer_fromHardwareBuffer on non Android " + "configuration"); + return nullptr; +#endif +} + +jobject android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer( + JNIEnv* env, AHardwareBuffer* ahardwarebuffer) { +#ifdef __ANDROID__ + LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr, + "Failed to find symbol AHardwareBuffer_toHardwareBuffer, did you forget to " + "call HardwareBufferHelpers::init?"); + return toHardwareBuffer(env, ahardwarebuffer); +#else + ALOGE("ERROR attempting to invoke AHardwareBuffer_toHardwareBuffer on non Android " + "configuration"); + return nullptr; +#endif +} \ No newline at end of file diff --git a/libs/hwui/jni/HardwareBufferHelpers.h b/libs/hwui/jni/HardwareBufferHelpers.h new file mode 100644 index 000000000000..326babfb0b34 --- /dev/null +++ b/libs/hwui/jni/HardwareBufferHelpers.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HARDWAREBUFFER_JNI_HELPERS_H +#define HARDWAREBUFFER_JNI_HELPERS_H + +#include +#include + +namespace android { +namespace uirenderer { + +class HardwareBufferHelpers { +public: + static void init(); + static AHardwareBuffer* AHardwareBuffer_fromHardwareBuffer(JNIEnv*, jobject); + static jobject AHardwareBuffer_toHardwareBuffer(JNIEnv*, AHardwareBuffer*); + +private: + HardwareBufferHelpers() = default; // not to be instantiated +}; + +} // namespace uirenderer +} // namespace android + +#endif // HARDWAREBUFFER_JNI_HELPERS_H \ No newline at end of file diff --git a/libs/hwui/jni/JvmErrorReporter.h b/libs/hwui/jni/JvmErrorReporter.h new file mode 100644 index 000000000000..5e10b9d93275 --- /dev/null +++ b/libs/hwui/jni/JvmErrorReporter.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef JVMERRORREPORTER_H +#define JVMERRORREPORTER_H + +#include +#include +#include + +#include "GraphicsJNI.h" + +namespace android { +namespace uirenderer { + +class JvmErrorReporter : public android::uirenderer::ErrorHandler { +public: + JvmErrorReporter(JNIEnv* env) { env->GetJavaVM(&mVm); } + + virtual void onError(const std::string& message) override { + JNIEnv* env = GraphicsJNI::getJNIEnv(); + jniThrowException(env, "java/lang/IllegalStateException", message.c_str()); + } + +private: + JavaVM* mVm; +}; + +} // namespace uirenderer +} // namespace android + +#endif // JVMERRORREPORTER_H diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp new file mode 100644 index 000000000000..4886fdd7ac67 --- /dev/null +++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "HardwareBufferRenderer" +#define ATRACE_TAG ATRACE_TAG_VIEW + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "HardwareBufferHelpers.h" +#include "JvmErrorReporter.h" + +namespace android { + +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; + +struct { + jclass clazz; + jmethodID invokeRenderCallback; +} gHardwareBufferRendererClassInfo; + +static RenderCallback createRenderCallback(JNIEnv* env, jobject releaseCallback) { + if (releaseCallback == nullptr) return nullptr; + + JavaVM* vm = nullptr; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); + auto globalCallbackRef = + std::make_shared(vm, env->NewGlobalRef(releaseCallback)); + return [globalCallbackRef](android::base::unique_fd&& fd, int status) { + GraphicsJNI::getJNIEnv()->CallStaticVoidMethod( + gHardwareBufferRendererClassInfo.clazz, + gHardwareBufferRendererClassInfo.invokeRenderCallback, globalCallbackRef->object(), + reinterpret_cast(fd.release()), reinterpret_cast(status)); + }; +} + +static long android_graphics_HardwareBufferRenderer_createRootNode(JNIEnv* env, jobject) { + auto* node = new RootRenderNode(std::make_unique(env)); + node->incStrong(nullptr); + node->setName("RootRenderNode"); + return reinterpret_cast(node); +} + +static void android_graphics_hardwareBufferRenderer_destroyRootNode(JNIEnv*, jobject, + jlong renderNodePtr) { + auto* node = reinterpret_cast(renderNodePtr); + node->destroy(); +} + +static long android_graphics_HardwareBufferRenderer_create(JNIEnv* env, jobject, jobject buffer, + jlong renderNodePtr) { + auto* hardwareBuffer = HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(env, buffer); + auto* rootRenderNode = reinterpret_cast(renderNodePtr); + ContextFactoryImpl factory(rootRenderNode); + auto* proxy = new RenderProxy(true, rootRenderNode, &factory); + proxy->setHardwareBuffer(hardwareBuffer); + return (jlong)proxy; +} + +static void HardwareBufferRenderer_destroy(jobject renderProxy) { + auto* proxy = reinterpret_cast(renderProxy); + delete proxy; +} + +static SkMatrix createMatrixFromBufferTransform(SkScalar width, SkScalar height, int transform) { + auto matrix = SkMatrix(); + switch (transform) { + case ANATIVEWINDOW_TRANSFORM_ROTATE_90: + matrix.setRotate(90); + matrix.postTranslate(width, 0); + break; + case ANATIVEWINDOW_TRANSFORM_ROTATE_180: + matrix.setRotate(180); + matrix.postTranslate(width, height); + break; + case ANATIVEWINDOW_TRANSFORM_ROTATE_270: + matrix.setRotate(270); + matrix.postTranslate(0, width); + break; + default: + ALOGE("Invalid transform provided. Transform should be validated from" + "the java side. Leveraging identity transform as a fallback"); + [[fallthrough]]; + case ANATIVEWINDOW_TRANSFORM_IDENTITY: + break; + } + return matrix; +} + +static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, jobject renderProxy, + jint transform, jint width, jint height, + jlong colorspacePtr, jobject consumer) { + auto* proxy = reinterpret_cast(renderProxy); + auto skWidth = static_cast(width); + auto skHeight = static_cast(height); + auto matrix = createMatrixFromBufferTransform(skWidth, skHeight, transform); + auto colorSpace = GraphicsJNI::getNativeColorSpace(colorspacePtr); + proxy->setHardwareBufferRenderParams( + HardwareBufferRenderParams(matrix, colorSpace, createRenderCallback(env, consumer))); + return proxy->syncAndDrawFrame(); +} + +static void android_graphics_HardwareBufferRenderer_setLightGeometry(JNIEnv*, jobject, + jobject renderProxyPtr, + jfloat lightX, jfloat lightY, + jfloat lightZ, + jfloat lightRadius) { + auto* proxy = reinterpret_cast(renderProxyPtr); + proxy->setLightGeometry((Vector3){lightX, lightY, lightZ}, lightRadius); +} + +static void android_graphics_HardwareBufferRenderer_setLightAlpha(JNIEnv* env, jobject, + jobject renderProxyPtr, + jfloat ambientShadowAlpha, + jfloat spotShadowAlpha) { + auto* proxy = reinterpret_cast(renderProxyPtr); + proxy->setLightAlpha((uint8_t)(255 * ambientShadowAlpha), (uint8_t)(255 * spotShadowAlpha)); +} + +static jlong android_graphics_HardwareBufferRenderer_getFinalizer() { + return static_cast(reinterpret_cast(&HardwareBufferRenderer_destroy)); +} + +// ---------------------------------------------------------------------------- +// JNI Glue +// ---------------------------------------------------------------------------- + +const char* const kClassPathName = "android/graphics/HardwareBufferRenderer"; + +static const JNINativeMethod gMethods[] = { + {"nCreateHardwareBufferRenderer", "(Landroid/hardware/HardwareBuffer;J)J", + (void*)android_graphics_HardwareBufferRenderer_create}, + {"nRender", "(JIIIJLjava/util/function/Consumer;)I", + (void*)android_graphics_HardwareBufferRenderer_render}, + {"nCreateRootRenderNode", "()J", + (void*)android_graphics_HardwareBufferRenderer_createRootNode}, + {"nSetLightGeometry", "(JFFFF)V", + (void*)android_graphics_HardwareBufferRenderer_setLightGeometry}, + {"nSetLightAlpha", "(JFF)V", (void*)android_graphics_HardwareBufferRenderer_setLightAlpha}, + {"nGetFinalizer", "()J", (void*)android_graphics_HardwareBufferRenderer_getFinalizer}, + {"nDestroyRootRenderNode", "(J)V", + (void*)android_graphics_hardwareBufferRenderer_destroyRootNode}}; + +int register_android_graphics_HardwareBufferRenderer(JNIEnv* env) { + jclass hardwareBufferRendererClazz = + FindClassOrDie(env, "android/graphics/HardwareBufferRenderer"); + gHardwareBufferRendererClassInfo.clazz = hardwareBufferRendererClazz; + gHardwareBufferRendererClassInfo.invokeRenderCallback = + GetStaticMethodIDOrDie(env, hardwareBufferRendererClazz, "invokeRenderCallback", + "(Ljava/util/function/Consumer;II)V"); + HardwareBufferHelpers::init(); + return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); +} + +} // namespace android \ No newline at end of file diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index 0663121a4027..47e2edb2ed0f 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -56,6 +56,7 @@ #include #include +#include "JvmErrorReporter.h" #include "android_graphics_HardwareRendererObserver.h" namespace android { @@ -93,35 +94,12 @@ struct { jmethodID getDestinationBitmap; } gCopyRequest; -static JNIEnv* getenv(JavaVM* vm) { - JNIEnv* env; - if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { - LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm); - } - return env; -} - typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface); ANW_fromSurface fromSurface; -class JvmErrorReporter : public ErrorHandler { -public: - JvmErrorReporter(JNIEnv* env) { - env->GetJavaVM(&mVm); - } - - virtual void onError(const std::string& message) override { - JNIEnv* env = getenv(mVm); - jniThrowException(env, "java/lang/IllegalStateException", message.c_str()); - } -private: - JavaVM* mVm; -}; - class FrameCommitWrapper : public LightRefBase { public: explicit FrameCommitWrapper(JNIEnv* env, jobject jobject) { - env->GetJavaVM(&mVm); mObject = env->NewGlobalRef(jobject); LOG_ALWAYS_FATAL_IF(!mObject, "Failed to make global ref"); } @@ -131,19 +109,18 @@ public: void onFrameCommit(bool didProduceBuffer) { if (mObject) { ATRACE_FORMAT("frameCommit success=%d", didProduceBuffer); - getenv(mVm)->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit, - didProduceBuffer); + GraphicsJNI::getJNIEnv()->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit, + didProduceBuffer); releaseObject(); } } private: - JavaVM* mVm; jobject mObject; void releaseObject() { if (mObject) { - getenv(mVm)->DeleteGlobalRef(mObject); + GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject); mObject = nullptr; } } @@ -443,26 +420,6 @@ static void android_view_ThreadedRenderer_forceDrawNextFrame(JNIEnv* env, jobjec proxy->forceDrawNextFrame(); } -class JGlobalRefHolder { -public: - JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {} - - virtual ~JGlobalRefHolder() { - getenv(mVm)->DeleteGlobalRef(mObject); - mObject = nullptr; - } - - jobject object() { return mObject; } - JavaVM* vm() { return mVm; } - -private: - JGlobalRefHolder(const JGlobalRefHolder&) = delete; - void operator=(const JGlobalRefHolder&) = delete; - - JavaVM* mVm; - jobject mObject; -}; - using TextureMap = std::unordered_map>; struct PictureCaptureState { @@ -578,7 +535,7 @@ static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* auto pictureState = std::make_shared(); proxy->setPictureCapturedCallback([globalCallbackRef, pictureState](sk_sp&& picture) { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); Picture* wrapper = new PictureWrapper{std::move(picture), pictureState}; env->CallStaticVoidMethod(gHardwareRenderer.clazz, gHardwareRenderer.invokePictureCapturedCallback, @@ -600,7 +557,7 @@ static void android_view_ThreadedRenderer_setASurfaceTransactionCallback( vm, env->NewGlobalRef(aSurfaceTransactionCallback)); proxy->setASurfaceTransactionCallback( [globalCallbackRef](int64_t transObj, int64_t scObj, int64_t frameNr) -> bool { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); jboolean ret = env->CallBooleanMethod( globalCallbackRef->object(), gASurfaceTransactionCallback.onMergeTransaction, @@ -622,7 +579,7 @@ static void android_view_ThreadedRenderer_setPrepareSurfaceControlForWebviewCall auto globalCallbackRef = std::make_shared(vm, env->NewGlobalRef(callback)); proxy->setPrepareSurfaceControlForWebviewCallback([globalCallbackRef]() { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); env->CallVoidMethod(globalCallbackRef->object(), gPrepareSurfaceControlForWebviewCallback.prepare); }); @@ -641,7 +598,7 @@ static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env, env->NewGlobalRef(frameCallback)); proxy->setFrameCallback([globalCallbackRef](int32_t syncResult, int64_t frameNr) -> std::function { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); ScopedLocalRef frameCommitCallback( env, env->CallObjectMethod( globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw, @@ -680,7 +637,7 @@ static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env, auto globalCallbackRef = std::make_shared(vm, env->NewGlobalRef(callback)); proxy->setFrameCompleteCallback([globalCallbackRef]() { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); env->CallVoidMethod(globalCallbackRef->object(), gFrameCompleteCallback.onFrameComplete); }); @@ -693,7 +650,7 @@ public: : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {} virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override { - JNIEnv* env = getenv(mRefHolder.vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); jlong bitmapPtr = env->CallLongMethod( mRefHolder.object(), gCopyRequest.getDestinationBitmap, srcWidth, srcHeight); SkBitmap bitmap; @@ -702,7 +659,7 @@ public: } virtual void onCopyFinished(CopyResult result) override { - JNIEnv* env = getenv(mRefHolder.vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); env->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished, static_cast(result)); } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 19cd7bdd6fcb..202a62cf320c 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -55,7 +55,9 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() { MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { // In case the surface was destroyed (e.g. a previous trimMemory call) we // need to recreate it here. - if (!isSurfaceReady() && mNativeWindow) { + if (mHardwareBuffer) { + mRenderThread.requireGlContext(); + } else if (!isSurfaceReady() && mNativeWindow) { setSurface(mNativeWindow.get(), mSwapBehavior); } @@ -67,17 +69,24 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { } Frame SkiaOpenGLPipeline::getFrame() { - LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, - "drawRenderNode called on a context with no surface!"); - return mEglManager.beginFrame(mEglSurface); + if (mHardwareBuffer) { + AHardwareBuffer_Desc description; + AHardwareBuffer_describe(mHardwareBuffer, &description); + return Frame(description.width, description.height, 0); + } else { + LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, + "drawRenderNode called on a context with no surface!"); + return mEglManager.beginFrame(mEglSurface); + } } IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector>& renderNodes, FrameInfoVisualizer* profiler) { - if (!isCapturingSkp()) { + const std::vector>& renderNodes, FrameInfoVisualizer* profiler, + const HardwareBufferRenderParams& bufferParams) { + if (!isCapturingSkp() && !mHardwareBuffer) { mEglManager.damageFrame(frame, dirty); } @@ -104,13 +113,25 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( SkSurfaceProps props(0, kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); - sk_sp surface(SkSurface::MakeFromBackendRenderTarget( - mRenderThread.getGrContext(), backendRT, this->getSurfaceOrigin(), colorType, - mSurfaceColorSpace, &props)); + sk_sp surface; + SkMatrix preTransform; + if (mHardwareBuffer) { + surface = getBufferSkSurface(bufferParams); + preTransform = bufferParams.getTransform(); + } else { + surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT, + getSurfaceOrigin(), colorType, + mSurfaceColorSpace, &props); + preTransform = SkMatrix::I(); + } - LightingInfo::updateLighting(lightGeometry, lightInfo); + SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y); + LightGeometry localGeometry = lightGeometry; + localGeometry.center.x = lightCenter.fX; + localGeometry.center.y = lightCenter.fY; + LightingInfo::updateLighting(localGeometry, lightInfo); renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, - SkMatrix::I()); + preTransform); // Draw visual debugging features if (CC_UNLIKELY(Properties::showDirtyRegions || @@ -142,6 +163,10 @@ bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect // metrics the frame was swapped at this point currentFrameInfo->markSwapBuffers(); + if (mHardwareBuffer) { + return false; + } + *requireSwap = drew || mEglManager.damageRequiresSwap(); if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) { @@ -197,6 +222,26 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh return false; } +[[nodiscard]] android::base::unique_fd SkiaOpenGLPipeline::flush() { + int fence = -1; + EGLSyncKHR sync = EGL_NO_SYNC_KHR; + mEglManager.createReleaseFence(true, &sync, &fence); + // If a sync object is returned here then the device does not support native + // fences, we block on the returned sync and return -1 as a file descriptor + if (sync != EGL_NO_SYNC_KHR) { + EGLDisplay display = mEglManager.eglDisplay(); + EGLint result = eglClientWaitSyncKHR(display, sync, 0, 1000000000); + if (result == EGL_FALSE) { + ALOGE("EglManager::createReleaseFence: error waiting for previous fence: %#x", + eglGetError()); + } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { + ALOGE("EglManager::createReleaseFence: timeout waiting for previous fence"); + } + eglDestroySyncKHR(display, sync); + } + return android::base::unique_fd(fence); +} + bool SkiaOpenGLPipeline::isSurfaceReady() { return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE); } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index a80c613697f2..940d6bfdb83c 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -21,6 +21,7 @@ #include "SkiaPipeline.h" #include "renderstate/RenderState.h" +#include "renderthread/HardwareBufferRenderParams.h" namespace android { @@ -36,19 +37,18 @@ public: renderthread::MakeCurrentResult makeCurrent() override; renderthread::Frame getFrame() override; - renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame, - const SkRect& screenDirty, const SkRect& dirty, - const LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, - const LightInfo& lightInfo, - const std::vector >& renderNodes, - FrameInfoVisualizer* profiler) override; + renderthread::IRenderPipeline::DrawResult draw( + const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, + const std::vector >& renderNodes, FrameInfoVisualizer* profiler, + const renderthread::HardwareBufferRenderParams& bufferParams) override; GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; } bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override; + [[nodiscard]] android::base::unique_fd flush() override; void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index c546adaaf779..1a336c5855d9 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -604,6 +604,31 @@ void SkiaPipeline::dumpResourceCacheUsage() const { ALOGD("%s", log.c_str()); } +void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) { + if (mHardwareBuffer) { + AHardwareBuffer_release(mHardwareBuffer); + mHardwareBuffer = nullptr; + } + + if (buffer) { + AHardwareBuffer_acquire(buffer); + mHardwareBuffer = buffer; + } +} + +sk_sp SkiaPipeline::getBufferSkSurface( + const renderthread::HardwareBufferRenderParams& bufferParams) { + auto bufferColorSpace = bufferParams.getColorSpace(); + if (mBufferSurface == nullptr || mBufferColorSpace == nullptr || + !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) { + mBufferSurface = SkSurface::MakeFromAHardwareBuffer( + mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin, + bufferColorSpace, nullptr, true); + mBufferColorSpace = bufferColorSpace; + } + return mBufferSurface; +} + void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { mColorMode = colorMode; switch (colorMode) { diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index 7887d1ae2117..4f9334654c9b 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -20,9 +20,11 @@ #include #include #include + #include "Lighting.h" #include "hwui/AnimatedImageDrawable.h" #include "renderthread/CanvasContext.h" +#include "renderthread/HardwareBufferRenderParams.h" #include "renderthread/IRenderPipeline.h" class SkFILEWStream; @@ -73,11 +75,20 @@ public: mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None; } + virtual void setHardwareBuffer(AHardwareBuffer* buffer) override; + bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; } + protected: + sk_sp getBufferSkSurface( + const renderthread::HardwareBufferRenderParams& bufferParams); void dumpResourceCacheUsage() const; renderthread::RenderThread& mRenderThread; + AHardwareBuffer* mHardwareBuffer = nullptr; + sk_sp mBufferSurface = nullptr; + sk_sp mBufferColorSpace = nullptr; + ColorMode mColorMode = ColorMode::Default; SkColorType mSurfaceColorType; sk_sp mSurfaceColorSpace; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index f10bca675bf7..b94b6cf0546a 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -57,37 +57,55 @@ VulkanManager& SkiaVulkanPipeline::vulkanManager() { MakeCurrentResult SkiaVulkanPipeline::makeCurrent() { // In case the surface was destroyed (e.g. a previous trimMemory call) we // need to recreate it here. - if (!isSurfaceReady() && mNativeWindow) { + if (mHardwareBuffer) { + mRenderThread.requireVkContext(); + } else if (!isSurfaceReady() && mNativeWindow) { setSurface(mNativeWindow.get(), SwapBehavior::kSwap_default); } return isContextReady() ? MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed; } Frame SkiaVulkanPipeline::getFrame() { - LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, "getFrame() called on a context with no surface!"); - return vulkanManager().dequeueNextBuffer(mVkSurface); + if (mHardwareBuffer) { + AHardwareBuffer_Desc description; + AHardwareBuffer_describe(mHardwareBuffer, &description); + return Frame(description.width, description.height, 0); + } else { + LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, + "getFrame() called on a context with no surface!"); + return vulkanManager().dequeueNextBuffer(mVkSurface); + } } IRenderPipeline::DrawResult SkiaVulkanPipeline::draw( const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector>& renderNodes, FrameInfoVisualizer* profiler) { - sk_sp backBuffer = mVkSurface->getCurrentSkSurface(); + const std::vector>& renderNodes, FrameInfoVisualizer* profiler, + const HardwareBufferRenderParams& bufferParams) { + sk_sp backBuffer; + SkMatrix preTransform; + if (mHardwareBuffer) { + backBuffer = getBufferSkSurface(bufferParams); + preTransform = bufferParams.getTransform(); + } else { + backBuffer = mVkSurface->getCurrentSkSurface(); + preTransform = mVkSurface->getCurrentPreTransform(); + } + if (backBuffer.get() == nullptr) { return {false, -1}; } // update the coordinates of the global light position based on surface rotation - SkPoint lightCenter = mVkSurface->getCurrentPreTransform().mapXY(lightGeometry.center.x, - lightGeometry.center.y); + SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y); LightGeometry localGeometry = lightGeometry; localGeometry.center.x = lightCenter.fX; localGeometry.center.y = lightCenter.fY; LightingInfo::updateLighting(localGeometry, lightInfo); renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer, - mVkSurface->getCurrentPreTransform()); + preTransform); // Draw visual debugging features if (CC_UNLIKELY(Properties::showDirtyRegions || @@ -116,12 +134,16 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw( bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) { - *requireSwap = drew; - // Even if we decided to cancel the frame, from the perspective of jank // metrics the frame was swapped at this point currentFrameInfo->markSwapBuffers(); + if (mHardwareBuffer) { + return false; + } + + *requireSwap = drew; + if (*requireSwap) { vulkanManager().swapBuffers(mVkSurface, screenDirty); } @@ -137,6 +159,12 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { void SkiaVulkanPipeline::onStop() {} +[[nodiscard]] android::base::unique_fd SkiaVulkanPipeline::flush() { + int fence = -1; + vulkanManager().createReleaseFence(&fence, mRenderThread.getGrContext()); + return android::base::unique_fd(fence); +} + // We can safely ignore the swap behavior because VkManager will always operate // in a mode equivalent to EGLManager::SwapBehavior::kBufferAge bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior /*swapBehavior*/) { diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index f3d36131a22f..2c7b268e8174 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -16,14 +16,13 @@ #pragma once +#include "SkRefCnt.h" #include "SkiaPipeline.h" +#include "renderstate/RenderState.h" +#include "renderthread/HardwareBufferRenderParams.h" #include "renderthread/VulkanManager.h" #include "renderthread/VulkanSurface.h" -#include "renderstate/RenderState.h" - -#include "SkRefCnt.h" - class SkBitmap; struct SkRect; @@ -38,18 +37,18 @@ public: renderthread::MakeCurrentResult makeCurrent() override; renderthread::Frame getFrame() override; - renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame, - const SkRect& screenDirty, const SkRect& dirty, - const LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, - const LightInfo& lightInfo, - const std::vector >& renderNodes, - FrameInfoVisualizer* profiler) override; + renderthread::IRenderPipeline::DrawResult draw( + const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, + const std::vector >& renderNodes, FrameInfoVisualizer* profiler, + const renderthread::HardwareBufferRenderParams& bufferParams) override; GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; } bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; + [[nodiscard]] android::base::unique_fd flush() override; + bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override; void onStop() override; bool isSurfaceReady() override; @@ -64,7 +63,6 @@ protected: private: renderthread::VulkanManager& vulkanManager(); - renderthread::VulkanSurface* mVkSurface = nullptr; sp mNativeWindow; }; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 78ae5cf3d93b..b769f8d15044 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -153,6 +153,7 @@ void CanvasContext::removeRenderNode(RenderNode* node) { void CanvasContext::destroy() { stopDrawing(); + setHardwareBuffer(nullptr); setSurface(nullptr); setSurfaceControl(nullptr); freePrefetchedLayers(); @@ -176,6 +177,19 @@ static void setBufferCount(ANativeWindow* window) { native_window_set_buffer_count(window, bufferCount); } +void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) { + if (mHardwareBuffer) { + AHardwareBuffer_release(mHardwareBuffer); + mHardwareBuffer = nullptr; + } + + if (buffer) { + AHardwareBuffer_acquire(buffer); + mHardwareBuffer = buffer; + } + mRenderPipeline->setHardwareBuffer(mHardwareBuffer); +} + void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) { ATRACE_CALL(); @@ -257,7 +271,7 @@ void CanvasContext::setStopped(bool stopped) { mRenderThread.removeFrameCallback(this); mRenderPipeline->onStop(); mRenderThread.cacheManager().onContextStopped(this); - } else if (mIsDirty && hasSurface()) { + } else if (mIsDirty && hasOutputTarget()) { mRenderThread.postFrameCallback(this); } } @@ -390,7 +404,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy mIsDirty = true; - if (CC_UNLIKELY(!hasSurface())) { + if (CC_UNLIKELY(!hasOutputTarget())) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); info.out.canDrawThisFrame = false; return; @@ -535,7 +549,7 @@ void CanvasContext::draw() { std::scoped_lock lock(mFrameMetricsReporterMutex); drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds, mOpaque, - mLightInfo, mRenderNodes, &(profiler())); + mLightInfo, mRenderNodes, &(profiler()), mBufferParams); } uint64_t frameCompleteNr = getFrameNumber(); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index e875c42e9eba..3f796d9b7b65 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -125,12 +125,13 @@ public: // Won't take effect until next EGLSurface creation void setSwapBehavior(SwapBehavior swapBehavior); + void setHardwareBuffer(AHardwareBuffer* buffer); void setSurface(ANativeWindow* window, bool enableTimeout = true); void setSurfaceControl(ASurfaceControl* surfaceControl); bool pauseSurface(); void setStopped(bool stopped); - bool isStopped() { return mStopped || !hasSurface(); } - bool hasSurface() const { return mNativeSurface.get(); } + bool isStopped() { return mStopped || !hasOutputTarget(); } + bool hasOutputTarget() const { return mNativeSurface.get() || mHardwareBuffer; } void allocateBuffers(); void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); @@ -206,6 +207,10 @@ public: mASurfaceTransactionCallback = callback; } + void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) { + mBufferParams = params; + } + bool mergeTransaction(ASurfaceTransaction* transaction, ASurfaceControl* control); void setPrepareSurfaceControlForWebviewCallback(const std::function& callback) { @@ -258,6 +263,9 @@ private: int32_t mLastFrameHeight = 0; RenderThread& mRenderThread; + + AHardwareBuffer* mHardwareBuffer = nullptr; + HardwareBufferRenderParams mBufferParams; std::unique_ptr mNativeSurface; // The SurfaceControl reference is passed from ViewRootImpl, can be set to // NULL to remove the reference diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index 1cc82fd0ff64..b06c5dd9ad36 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -26,6 +26,7 @@ #include "../Properties.h" #include "../RenderNode.h" #include "CanvasContext.h" +#include "HardwareBufferRenderParams.h" #include "RenderThread.h" namespace android { @@ -91,6 +92,9 @@ void DrawFrameTask::run() { mContext->setSyncDelayDuration(systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued); + auto hardwareBufferParams = mHardwareBufferParams; + mContext->setHardwareBufferRenderParams(hardwareBufferParams); + IRenderPipeline* pipeline = mContext->getRenderPipeline(); bool canUnblockUiThread; bool canDrawThisFrame; { @@ -150,6 +154,11 @@ void DrawFrameTask::run() { if (!canUnblockUiThread) { unblockUiThread(); } + + if (pipeline->hasHardwareBuffer()) { + auto fence = pipeline->flush(); + hardwareBufferParams.invokeRenderCallback(std::move(fence), 0); + } } bool DrawFrameTask::syncFrameState(TreeInfo& info) { @@ -175,8 +184,9 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { // This is after the prepareTree so that any pending operations // (RenderNode tree state, prefetched layers, etc...) will be flushed. - if (CC_UNLIKELY(!mContext->hasSurface() || !canDraw)) { - if (!mContext->hasSurface()) { + bool hasTarget = mContext->hasOutputTarget(); + if (CC_UNLIKELY(!hasTarget || !canDraw)) { + if (!hasTarget) { mSyncResult |= SyncResult::LostSurfaceRewardIfFound; } else { // If we have a surface but can't draw we must be stopped diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index fafab24cbce7..c5c5fe280743 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -27,8 +27,16 @@ #include "../Rect.h" #include "../TreeInfo.h" #include "RenderTask.h" +#include "SkColorSpace.h" +#include "SwapBehavior.h" +#include "utils/TimeUtils.h" +#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration +#include +#endif +#include "HardwareBufferRenderParams.h" namespace android { + namespace uirenderer { class DeferredLayerUpdater; @@ -88,6 +96,10 @@ public: void forceDrawNextFrame() { mForceDrawFrame = true; } + void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) { + mHardwareBufferParams = params; + } + private: void postAndWait(); bool syncFrameState(TreeInfo& info); @@ -111,6 +123,7 @@ private: int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE]; + HardwareBufferRenderParams mHardwareBufferParams; std::function(int32_t, int64_t)> mFrameCallback; std::function mFrameCommitCallback; std::function mFrameCompleteCallback; diff --git a/libs/hwui/renderthread/HardwareBufferRenderParams.h b/libs/hwui/renderthread/HardwareBufferRenderParams.h new file mode 100644 index 000000000000..91fe3f6cf273 --- /dev/null +++ b/libs/hwui/renderthread/HardwareBufferRenderParams.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HARDWAREBUFFERRENDERER_H_ +#define HARDWAREBUFFERRENDERER_H_ + +#include +#include + +#include "SkColorSpace.h" +#include "SkMatrix.h" +#include "SkSurface.h" + +namespace android { +namespace uirenderer { +namespace renderthread { + +using namespace android::uirenderer::renderthread; + +using RenderCallback = std::function; + +class RenderProxy; + +class HardwareBufferRenderParams { +public: + HardwareBufferRenderParams() = default; + HardwareBufferRenderParams(const SkMatrix& transform, const sk_sp& colorSpace, + RenderCallback&& callback) + : mTransform(transform) + , mColorSpace(colorSpace) + , mRenderCallback(std::move(callback)) {} + const SkMatrix& getTransform() const { return mTransform; } + sk_sp getColorSpace() const { return mColorSpace; } + + void invokeRenderCallback(android::base::unique_fd&& fenceFd, int status) { + if (mRenderCallback) { + std::invoke(mRenderCallback, std::move(fenceFd), status); + } + } + +private: + SkMatrix mTransform = SkMatrix::I(); + sk_sp mColorSpace = SkColorSpace::MakeSRGB(); + RenderCallback mRenderCallback = nullptr; +}; + +} // namespace renderthread +} // namespace uirenderer +} // namespace android +#endif // HARDWAREBUFFERRENDERER_H_ diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index 35e370f52fda..715c17dfc895 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -16,17 +16,19 @@ #pragma once +#include +#include +#include +#include + +#include "ColorMode.h" #include "DamageAccumulator.h" #include "FrameInfoVisualizer.h" +#include "HardwareBufferRenderParams.h" #include "LayerUpdateQueue.h" #include "Lighting.h" #include "SwapBehavior.h" #include "hwui/Bitmap.h" -#include "ColorMode.h" - -#include -#include -#include class GrDirectContext; @@ -64,10 +66,14 @@ public: const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, const std::vector>& renderNodes, - FrameInfoVisualizer* profiler) = 0; + FrameInfoVisualizer* profiler, + const HardwareBufferRenderParams& bufferParams) = 0; virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) = 0; virtual DeferredLayerUpdater* createTextureLayer() = 0; + [[nodiscard]] virtual android::base::unique_fd flush() = 0; + virtual void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) = 0; + virtual bool hasHardwareBuffer() = 0; virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior) = 0; virtual void onStop() = 0; virtual bool isSurfaceReady() = 0; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 07f5a78fc1ec..ed01e322ffd9 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -85,6 +85,18 @@ void RenderProxy::setName(const char* name) { mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); }); } +void RenderProxy::setHardwareBuffer(AHardwareBuffer* buffer) { + if (buffer) { + AHardwareBuffer_acquire(buffer); + } + mRenderThread.queue().post([this, hardwareBuffer = buffer]() mutable { + mContext->setHardwareBuffer(hardwareBuffer); + if (hardwareBuffer) { + AHardwareBuffer_release(hardwareBuffer); + } + }); +} + void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) { if (window) { ANativeWindow_acquire(window); } mRenderThread.queue().post([this, win = window, enableTimeout]() mutable { @@ -324,6 +336,10 @@ void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) mDrawFrameTask.setContentDrawBounds(left, top, right, bottom); } +void RenderProxy::setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) { + mDrawFrameTask.setHardwareBufferRenderParams(params); +} + void RenderProxy::setPictureCapturedCallback( const std::function&&)>& callback) { mRenderThread.queue().post( diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index a21faa86ed0f..17cf6650f87d 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -18,6 +18,7 @@ #define RENDERPROXY_H_ #include +#include #include #include #include @@ -76,7 +77,7 @@ public: void setSwapBehavior(SwapBehavior swapBehavior); bool loadSystemProperties(); void setName(const char* name); - + void setHardwareBuffer(AHardwareBuffer* buffer); void setSurface(ANativeWindow* window, bool enableTimeout = true); void setSurfaceControl(ASurfaceControl* surfaceControl); void allocateBuffers(); @@ -84,6 +85,7 @@ public: void setStopped(bool stopped); void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); void setLightGeometry(const Vector3& lightCenter, float lightRadius); + void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params); void setOpaque(bool opaque); void setColorMode(ColorMode mode); int64_t* frameInfo(); diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp index 88420a5d5c23..9e376e32f8ea 100644 --- a/libs/hwui/tests/unit/CanvasContextTests.cpp +++ b/libs/hwui/tests/unit/CanvasContextTests.cpp @@ -38,7 +38,7 @@ RENDERTHREAD_TEST(CanvasContext, create) { std::unique_ptr canvasContext( CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0)); - ASSERT_FALSE(canvasContext->hasSurface()); + ASSERT_FALSE(canvasContext->hasOutputTarget()); canvasContext->destroy(); } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 7383d6ab4994..616f21cbe1a4 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -1136,6 +1136,15 @@ + + + + + + + { + result.getFence().await(Duration.ofMillis(3000)); + handler.post(() -> { + Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, colorSpace); + Bitmap copy = bitmap.copy(Config.ARGB_8888, false); + mImageView.setImageBitmap(copy); + }); + }); + } +} -- cgit v1.2.3-59-g8ed1b From 2e58b5c4b9f7d96ce84ccc0dabe2fcd3667052fe Mon Sep 17 00:00:00 2001 From: Jorge Betancourt Date: Thu, 1 Sep 2022 09:57:09 -0400 Subject: add support for rendering lottie animations through a LottieDrawable This is an initial push that only supports basic playback Test: frameworks/base/tests/VectorDrawableTest and run LottieDrawable activity Change-Id: Ic34366b0cd0984a512d8684d476227830903f778 Bug: 257304231 --- .../android/graphics/drawable/LottieDrawable.java | 151 +++++++++++++++++++++ libs/hwui/Android.bp | 4 + libs/hwui/SkiaCanvas.cpp | 4 + libs/hwui/SkiaCanvas.h | 1 + libs/hwui/apex/jni_runtime.cpp | 2 + libs/hwui/hwui/Canvas.h | 2 + libs/hwui/hwui/LottieDrawable.cpp | 83 +++++++++++ libs/hwui/hwui/LottieDrawable.h | 67 +++++++++ libs/hwui/jni/LottieDrawable.cpp | 91 +++++++++++++ libs/hwui/pipeline/skia/SkiaDisplayList.cpp | 10 ++ libs/hwui/pipeline/skia/SkiaDisplayList.h | 3 + libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp | 5 + libs/hwui/pipeline/skia/SkiaRecordingCanvas.h | 1 + tests/VectorDrawableTest/Android.bp | 2 + tests/VectorDrawableTest/AndroidManifest.xml | 9 ++ tests/VectorDrawableTest/res/raw/lottie.json | 123 +++++++++++++++++ .../android/test/dynamic/LottieDrawableTest.java | 76 +++++++++++ 17 files changed, 634 insertions(+) create mode 100644 graphics/java/android/graphics/drawable/LottieDrawable.java create mode 100644 libs/hwui/hwui/LottieDrawable.cpp create mode 100644 libs/hwui/hwui/LottieDrawable.h create mode 100644 libs/hwui/jni/LottieDrawable.cpp create mode 100644 tests/VectorDrawableTest/res/raw/lottie.json create mode 100644 tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/drawable/LottieDrawable.java b/graphics/java/android/graphics/drawable/LottieDrawable.java new file mode 100644 index 000000000000..c1f1b50cf339 --- /dev/null +++ b/graphics/java/android/graphics/drawable/LottieDrawable.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.drawable; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; + +import dalvik.annotation.optimization.FastNative; + +import libcore.util.NativeAllocationRegistry; + +import java.io.IOException; + +/** + * {@link Drawable} for drawing Lottie files. + * + *

    The framework handles decoding subsequent frames in another thread and + * updating when necessary. The drawable will only animate while it is being + * displayed.

    + * + * @hide + */ +@SuppressLint("NotCloseable") +public class LottieDrawable extends Drawable implements Animatable { + private long mNativePtr; + + /** + * Create an animation from the provided JSON string + * @hide + */ + private LottieDrawable(@NonNull String animationJson) throws IOException { + mNativePtr = nCreate(animationJson); + if (mNativePtr == 0) { + throw new IOException("could not make LottieDrawable from json"); + } + + final long nativeSize = nNativeByteSize(mNativePtr); + NativeAllocationRegistry registry = new NativeAllocationRegistry( + LottieDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize); + registry.registerNativeAllocation(this, mNativePtr); + } + + /** + * Factory for LottieDrawable, throws IOException if native Skottie Builder fails to parse + */ + public static LottieDrawable makeLottieDrawable(@NonNull String animationJson) + throws IOException { + return new LottieDrawable(animationJson); + } + + + + /** + * Draw the current frame to the Canvas. + * @hide + */ + @Override + public void draw(@NonNull Canvas canvas) { + if (mNativePtr == 0) { + throw new IllegalStateException("called draw on empty LottieDrawable"); + } + + nDraw(mNativePtr, canvas.getNativeCanvasWrapper()); + } + + /** + * Start the animation. Needs to be called before draw calls. + * @hide + */ + @Override + public void start() { + if (mNativePtr == 0) { + throw new IllegalStateException("called start on empty LottieDrawable"); + } + + if (nStart(mNativePtr)) { + invalidateSelf(); + } + } + + /** + * Stops the animation playback. Does not release anything. + * @hide + */ + @Override + public void stop() { + if (mNativePtr == 0) { + throw new IllegalStateException("called stop on empty LottieDrawable"); + } + nStop(mNativePtr); + } + + /** + * Return whether the animation is currently running. + */ + @Override + public boolean isRunning() { + if (mNativePtr == 0) { + throw new IllegalStateException("called isRunning on empty LottieDrawable"); + } + return nIsRunning(mNativePtr); + } + + @Override + public int getOpacity() { + // We assume translucency to avoid checking each pixel. + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha(int alpha) { + //TODO + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + //TODO + } + + private static native long nCreate(String json); + private static native void nDraw(long nativeInstance, long nativeCanvas); + @FastNative + private static native long nGetNativeFinalizer(); + @FastNative + private static native long nNativeByteSize(long nativeInstance); + @FastNative + private static native boolean nIsRunning(long nativeInstance); + @FastNative + private static native boolean nStart(long nativeInstance); + @FastNative + private static native boolean nStop(long nativeInstance); + +} diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 3e3d77b89a9a..9c4ea0ecbe30 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -78,6 +78,7 @@ cc_defaults { "external/skia/src/utils", "external/skia/src/gpu", "external/skia/src/shaders", + "external/skia/modules/skottie", ], }, host: { @@ -374,6 +375,7 @@ cc_defaults { "external/skia/src/effects", "external/skia/src/image", "external/skia/src/images", + "external/skia/modules/skottie", ], shared_libs: [ @@ -400,6 +402,7 @@ cc_defaults { "jni/BitmapRegionDecoder.cpp", "jni/GIFMovie.cpp", "jni/GraphicsStatsService.cpp", + "jni/LottieDrawable.cpp", "jni/Movie.cpp", "jni/MovieImpl.cpp", "jni/pdf/PdfDocument.cpp", @@ -507,6 +510,7 @@ cc_defaults { "hwui/BlurDrawLooper.cpp", "hwui/Canvas.cpp", "hwui/ImageDecoder.cpp", + "hwui/LottieDrawable.cpp", "hwui/MinikinSkia.cpp", "hwui/MinikinUtils.cpp", "hwui/PaintImpl.cpp", diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index d83d78f650aa..a1c4b49b6742 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -736,6 +736,10 @@ double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) { return imgDrawable->drawStaging(mCanvas); } +void SkiaCanvas::drawLottie(LottieDrawable* lottieDrawable) { + lottieDrawable->drawStaging(mCanvas); +} + void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) { vectorDrawable->drawStaging(this); } diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index 31e3b4c3c7e2..fd8b6cdb7ca0 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -145,6 +145,7 @@ public: float dstTop, float dstRight, float dstBottom, const Paint* paint) override; virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) override; + virtual void drawLottie(LottieDrawable* lottieDrawable) override; virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override; diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp index e6cfa7bcaf70..5a96174ebac0 100644 --- a/libs/hwui/apex/jni_runtime.cpp +++ b/libs/hwui/apex/jni_runtime.cpp @@ -37,6 +37,7 @@ extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env); extern int register_android_graphics_Graphics(JNIEnv* env); extern int register_android_graphics_ImageDecoder(JNIEnv*); extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv*); +extern int register_android_graphics_drawable_LottieDrawable(JNIEnv*); extern int register_android_graphics_Interpolator(JNIEnv* env); extern int register_android_graphics_MaskFilter(JNIEnv* env); extern int register_android_graphics_Movie(JNIEnv* env); @@ -116,6 +117,7 @@ extern int register_android_view_ThreadedRenderer(JNIEnv* env); REG_JNI(register_android_graphics_HardwareRendererObserver), REG_JNI(register_android_graphics_ImageDecoder), REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable), + REG_JNI(register_android_graphics_drawable_LottieDrawable), REG_JNI(register_android_graphics_Interpolator), REG_JNI(register_android_graphics_MaskFilter), REG_JNI(register_android_graphics_Matrix), diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 2a2019199bda..07e2fe24c939 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -60,6 +60,7 @@ typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; typedef std::function ReadGlyphFunc; class AnimatedImageDrawable; +class LottieDrawable; class Bitmap; class Paint; struct Typeface; @@ -242,6 +243,7 @@ public: const Paint* paint) = 0; virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) = 0; + virtual void drawLottie(LottieDrawable* lottieDrawable) = 0; virtual void drawPicture(const SkPicture& picture) = 0; /** diff --git a/libs/hwui/hwui/LottieDrawable.cpp b/libs/hwui/hwui/LottieDrawable.cpp new file mode 100644 index 000000000000..92dc51e01a85 --- /dev/null +++ b/libs/hwui/hwui/LottieDrawable.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LottieDrawable.h" + +#include +#include +#include + +namespace android { + +sk_sp LottieDrawable::Make(sk_sp animation, size_t bytesUsed) { + if (animation) { + return sk_sp(new LottieDrawable(std::move(animation), bytesUsed)); + } + return nullptr; +} +LottieDrawable::LottieDrawable(sk_sp animation, size_t bytesUsed) + : mAnimation(std::move(animation)), mBytesUsed(bytesUsed) {} + +bool LottieDrawable::start() { + if (mRunning) { + return false; + } + + mRunning = true; + return true; +} + +bool LottieDrawable::stop() { + bool wasRunning = mRunning; + mRunning = false; + return wasRunning; +} + +bool LottieDrawable::isRunning() { + return mRunning; +} + +// TODO: Check to see if drawable is actually dirty +bool LottieDrawable::isDirty() { + return true; +} + +void LottieDrawable::onDraw(SkCanvas* canvas) { + if (mRunning) { + const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + + nsecs_t t = 0; + if (mStartTime == 0) { + mStartTime = currentTime; + } else { + t = currentTime - mStartTime; + } + double seekTime = std::fmod((double)t * 1e-9, mAnimation->duration()); + mAnimation->seekFrameTime(seekTime); + mAnimation->render(canvas); + } +} + +void LottieDrawable::drawStaging(SkCanvas* canvas) { + onDraw(canvas); +} + +SkRect LottieDrawable::onGetBounds() { + // We do not actually know the bounds, so give a conservative answer. + return SkRectMakeLargest(); +} + +} // namespace android diff --git a/libs/hwui/hwui/LottieDrawable.h b/libs/hwui/hwui/LottieDrawable.h new file mode 100644 index 000000000000..9cc34bf12f4f --- /dev/null +++ b/libs/hwui/hwui/LottieDrawable.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +class SkCanvas; + +namespace android { + +/** + * Native component of android.graphics.drawable.LottieDrawable.java. + * This class can be drawn into Canvas.h and maintains the state needed to drive + * the animation from the RenderThread. + */ +class LottieDrawable : public SkDrawable { +public: + static sk_sp Make(sk_sp animation, size_t bytes); + + // Draw to software canvas + void drawStaging(SkCanvas* canvas); + + // Returns true if the animation was started; false otherwise (e.g. it was + // already running) + bool start(); + // Returns true if the animation was stopped; false otherwise (e.g. it was + // already stopped) + bool stop(); + bool isRunning(); + + // TODO: Is dirty should take in a time til next frame to determine if it is dirty + bool isDirty(); + + SkRect onGetBounds() override; + + size_t byteSize() const { return sizeof(*this) + mBytesUsed; } + +protected: + void onDraw(SkCanvas* canvas) override; + +private: + LottieDrawable(sk_sp animation, size_t bytes_used); + + sk_sp mAnimation; + bool mRunning = false; + // The start time for the drawable itself. + nsecs_t mStartTime = 0; + const size_t mBytesUsed = 0; +}; + +} // namespace android diff --git a/libs/hwui/jni/LottieDrawable.cpp b/libs/hwui/jni/LottieDrawable.cpp new file mode 100644 index 000000000000..fb6eede213a8 --- /dev/null +++ b/libs/hwui/jni/LottieDrawable.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "GraphicsJNI.h" +#include "Utils.h" + +using namespace android; + +static jclass gLottieDrawableClass; + +static jlong LottieDrawable_nCreate(JNIEnv* env, jobject, jstring jjson) { + const ScopedUtfChars cstr(env, jjson); + // TODO(b/259267150) provide more accurate byteSize + size_t bytes = strlen(cstr.c_str()); + auto animation = skottie::Animation::Builder().make(cstr.c_str(), bytes); + sk_sp drawable(LottieDrawable::Make(std::move(animation), bytes)); + if (!drawable) { + return 0; + } + return reinterpret_cast(drawable.release()); +} + +static void LottieDrawable_destruct(LottieDrawable* drawable) { + SkSafeUnref(drawable); +} + +static jlong LottieDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) { + return static_cast(reinterpret_cast(&LottieDrawable_destruct)); +} + +static void LottieDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jlong canvasPtr) { + auto* drawable = reinterpret_cast(nativePtr); + auto* canvas = reinterpret_cast(canvasPtr); + canvas->drawLottie(drawable); +} + +static jboolean LottieDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + return drawable->isRunning(); +} + +static jboolean LottieDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + return drawable->start(); +} + +static jboolean LottieDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + return drawable->stop(); +} + +static jlong LottieDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + return drawable->byteSize(); +} + +static const JNINativeMethod gLottieDrawableMethods[] = { + {"nCreate", "(Ljava/lang/String;)J", (void*)LottieDrawable_nCreate}, + {"nNativeByteSize", "(J)J", (void*)LottieDrawable_nNativeByteSize}, + {"nGetNativeFinalizer", "()J", (void*)LottieDrawable_nGetNativeFinalizer}, + {"nDraw", "(JJ)V", (void*)LottieDrawable_nDraw}, + {"nIsRunning", "(J)Z", (void*)LottieDrawable_nIsRunning}, + {"nStart", "(J)Z", (void*)LottieDrawable_nStart}, + {"nStop", "(J)Z", (void*)LottieDrawable_nStop}, +}; + +int register_android_graphics_drawable_LottieDrawable(JNIEnv* env) { + gLottieDrawableClass = reinterpret_cast( + env->NewGlobalRef(FindClassOrDie(env, "android/graphics/drawable/LottieDrawable"))); + + return android::RegisterMethodsOrDie(env, "android/graphics/drawable/LottieDrawable", + gLottieDrawableMethods, NELEM(gLottieDrawableMethods)); +} diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index fcfc4f82abed..f0dc5eb4dd0e 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -146,6 +146,16 @@ bool SkiaDisplayList::prepareListAndChildren( } } + for (auto& lottie : mLotties) { + // If any animated image in the display list needs updated, then damage the node. + if (lottie->isDirty()) { + isDirty = true; + } + if (lottie->isRunning()) { + info.out.hasAnimations = true; + } + } + for (auto& [vectorDrawable, cachedMatrix] : mVectorDrawables) { // If any vector drawable in the display list needs update, damage the node. if (vectorDrawable->isDirty()) { diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index 2a677344b7b2..39217fcf1a56 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.h +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h @@ -22,6 +22,7 @@ #include "RenderNodeDrawable.h" #include "TreeInfo.h" #include "hwui/AnimatedImageDrawable.h" +#include "hwui/LottieDrawable.h" #include "utils/LinearAllocator.h" #include "utils/Pair.h" @@ -186,6 +187,8 @@ public: return mHasHolePunches; } + // TODO(b/257304231): create common base class for Lotties and AnimatedImages + std::vector mLotties; std::vector mAnimatedImages; DisplayListData mDisplayList; diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index 1f87865f2672..db449d608c1f 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -188,6 +188,11 @@ void SkiaRecordingCanvas::drawWebViewFunctor(int functor) { #endif } +void SkiaRecordingCanvas::drawLottie(LottieDrawable* lottie) { + drawDrawable(lottie); + mDisplayList->mLotties.push_back(lottie); +} + void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { mRecorder.drawVectorDrawable(tree); SkMatrix mat; diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index 7844e2cc2a73..c823d8d0a755 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -78,6 +78,7 @@ public: uirenderer::CanvasPropertyPaint* paint) override; virtual void drawRipple(const RippleDrawableParams& params) override; + virtual void drawLottie(LottieDrawable* lottieDrawable) override; virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override; virtual void enableZ(bool enableZ) override; diff --git a/tests/VectorDrawableTest/Android.bp b/tests/VectorDrawableTest/Android.bp index 9da7c5fdbb17..099d874375a1 100644 --- a/tests/VectorDrawableTest/Android.bp +++ b/tests/VectorDrawableTest/Android.bp @@ -26,5 +26,7 @@ package { android_test { name: "VectorDrawableTest", srcs: ["**/*.java"], + // certificate set as platform to allow testing of @hidden APIs + certificate: "platform", platform_apis: true, } diff --git a/tests/VectorDrawableTest/AndroidManifest.xml b/tests/VectorDrawableTest/AndroidManifest.xml index 5334dac57ca2..163e438e0677 100644 --- a/tests/VectorDrawableTest/AndroidManifest.xml +++ b/tests/VectorDrawableTest/AndroidManifest.xml @@ -158,6 +158,15 @@ + + + + + + + diff --git a/tests/VectorDrawableTest/res/raw/lottie.json b/tests/VectorDrawableTest/res/raw/lottie.json new file mode 100644 index 000000000000..fea571c6bedb --- /dev/null +++ b/tests/VectorDrawableTest/res/raw/lottie.json @@ -0,0 +1,123 @@ +{ + "v":"4.6.9", + "fr":60, + "ip":0, + "op":200, + "w":800, + "h":600, + "nm":"Loader 1 JSON", + "ddd":0, + + + "layers":[ + { + "ddd":0, + "ind":1, + "ty":4, + "nm":"Custom Path 1", + "ao": 0, + "ip": 0, + "op": 300, + "st": 0, + "sr": 1, + "bm": 0, + "ks": { + "o": { "a":0, "k":100 }, + "r": { "a":1, "k": [ + { "s": [ 0 ], "e": [ 360], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 }, + { "t": 200 } + ] }, + "p": { "a":0, "k":[ 300, 300, 0 ] }, + "a": { "a":0, "k":[ 100, 100, 0 ] }, + "s": { "a":1, "k":[ + { "s": [ 100, 100 ], "e": [ 200, 200 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 }, + { "s": [ 200, 200 ], "e": [ 100, 100 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 100 }, + { "t": 200 } + ] } + }, + + "shapes":[ + { + "ty":"gr", + "it":[ + { + "ty" : "sh", + "nm" : "Path 1", + "ks" : { + "a" : 1, + "k" : [ + { + "s": [ { + "i": [ [ 0, 50 ], [ -50, 0 ], [ 0, -50 ], [ 50, 0 ] ], + "o": [ [ 0, -50 ], [ 50, 0 ], [ 0, 50 ], [ -50, 0 ] ], + "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ], + "c": true + } ], + "e": [ { + "i": [ [ 50, 50 ], [ -50, 0 ], [ -50, -50 ], [ 50, 50 ] ], + "o": [ [ 50, -50 ], [ 50, 0 ], [ -50, 50 ], [ -50, 50 ] ], + "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ], + "c": true + } ], + "i": { "x":0.5, "y":0.5 }, + "o": { "x":0.5, "y":0.5 }, + "t": 0 + }, + { + "s": [ { + "i": [ [ 50, 50 ], [ -50, 0 ], [ -50, -50 ], [ 50, 50 ] ], + "o": [ [ 50, -50 ], [ 50, 0 ], [ -50, 50 ], [ -50, 50 ] ], + "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ], + "c": true + } ], + "e": [ { + "i": [ [ 0, 50 ], [ -50, 0 ], [ 0, -50 ], [ 50, 0 ] ], + "o": [ [ 0, -50 ], [ 50, 0 ], [ 0, 50 ], [ -50, 0 ] ], + "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ], + "c": true + } ], + "i": { "x":0.5, "y":0.5 }, + "o": { "x":0.5, "y":0.5 }, + "t": 100 + }, + { + "t": 200 + } + ] + } + }, + + { + "ty": "st", + "nm": "Stroke 1", + "lc": 1, + "lj": 1, + "ml": 4, + "w" : { "a": 1, "k": [ + { "s": [ 30 ], "e": [ 50 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 }, + { "s": [ 50 ], "e": [ 30 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 100 }, + { "t": 200 } + ] }, + "o" : { "a": 0, "k": 100 }, + "c" : { "a": 1, "k": [ + { "s": [ 0, 1, 0 ], "e": [ 1, 0, 0 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 }, + { "s": [ 1, 0, 0 ], "e": [ 0, 1, 0 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 100 }, + { "t": 200 } + ] } + }, + + { + "ty":"tr", + "p" : { "a":0, "k":[ 0, 0 ] }, + "a" : { "a":0, "k":[ 0, 0 ] }, + "s" : { "a":0, "k":[ 100, 100 ] }, + "r" : { "a":0, "k": 0 }, + "o" : { "a":0, "k":100 }, + "nm": "Transform" + } + ] + } + ] + } + ] + } diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java new file mode 100644 index 000000000000..05eae7b0e642 --- /dev/null +++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.dynamic; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.LottieDrawable; +import android.os.Bundle; +import android.view.View; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Scanner; + +@SuppressWarnings({"UnusedDeclaration"}) +public class LottieDrawableTest extends Activity { + private static final String TAG = "LottieDrawableTest"; + static final int BACKGROUND = 0xFFF44336; + + class LottieDrawableView extends View { + private Rect mLottieBounds; + + private LottieDrawable mLottie; + + LottieDrawableView(Context context, InputStream is) { + super(context); + Scanner s = new Scanner(is).useDelimiter("\\A"); + String json = s.hasNext() ? s.next() : ""; + try { + mLottie = LottieDrawable.makeLottieDrawable(json); + } catch (IOException e) { + throw new RuntimeException(TAG + ": error parsing test Lottie"); + } + mLottie.start(); + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawColor(BACKGROUND); + + mLottie.setBounds(mLottieBounds); + mLottie.draw(canvas); + } + + public void setLottieSize(Rect bounds) { + mLottieBounds = bounds; + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + InputStream is = getResources().openRawResource(R.raw.lottie); + + LottieDrawableView view = new LottieDrawableView(this, is); + view.setLottieSize(new Rect(0, 0, 900, 900)); + setContentView(view); + } +} -- cgit v1.2.3-59-g8ed1b From 3217d3dcdc4f3b93e2a9228187d5c3bc1dca89ec Mon Sep 17 00:00:00 2001 From: Haoyu Zhang Date: Tue, 10 Jan 2023 12:59:15 -0800 Subject: Address API feedback for getRunCharacterAdvance Bug: 241167328 Test: N/A Change-Id: I0d66c55d31802e555d8b171bd36984bb3496f189 --- graphics/java/android/graphics/Paint.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index f0e496f3a178..d35dcab11f49 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -3151,10 +3151,10 @@ public class Paint { * @see #getRunAdvance(char[], int, int, int, int, boolean, int) for more details. * * @param text the text to measure. Cannot be null. - * @param start the index of the start of the range to measure - * @param end the index + 1 of the end of the range to measure - * @param contextStart the index of the start of the shaping context - * @param contextEnd the index + 1 of the end of the shaping context + * @param start the start index of the range to measure, inclusive + * @param end the end index of the range to measure, exclusive + * @param contextStart the start index of the shaping context, inclusive + * @param contextEnd the end index of the shaping context, exclusive * @param isRtl whether the run is in RTL direction * @param offset index of caret position * @param advances the array that receives the computed character advances -- cgit v1.2.3-59-g8ed1b From 3b2c0ce8fe8119b5784a7ced1ecc2f8d58635e0b Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Wed, 14 Dec 2022 19:58:55 +0000 Subject: Encode JPEG/R from YuvImage Test: YuvImageTest Bug: b/252835416 Change-Id: I010b4498487bf58a0eb1dec3f619fec60b0191aa --- core/api/current.txt | 3 + graphics/java/android/graphics/YuvImage.java | 208 +++++++++++++++++++++++++-- libs/hwui/Android.bp | 11 +- libs/hwui/jni/YuvToJpegEncoder.cpp | 121 +++++++++++++++- libs/hwui/jni/YuvToJpegEncoder.h | 47 +++++- 5 files changed, 372 insertions(+), 18 deletions(-) (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index 801da28af206..90f9dba3a3c5 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -16338,7 +16338,10 @@ package android.graphics { public class YuvImage { ctor public YuvImage(byte[], int, int, int, int[]); + ctor public YuvImage(@NonNull byte[], int, int, int, @Nullable int[], @NonNull android.graphics.ColorSpace); method public boolean compressToJpeg(android.graphics.Rect, int, java.io.OutputStream); + method public boolean compressToJpegR(@NonNull android.graphics.YuvImage, int, @NonNull java.io.OutputStream); + method @NonNull public android.graphics.ColorSpace getColorSpace(); method public int getHeight(); method public int[] getStrides(); method public int getWidth(); diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java index af3f27661c84..6b5238b20cdc 100644 --- a/graphics/java/android/graphics/YuvImage.java +++ b/graphics/java/android/graphics/YuvImage.java @@ -16,6 +16,8 @@ package android.graphics; +import android.annotation.NonNull; +import android.annotation.Nullable; import java.io.OutputStream; /** @@ -63,7 +65,70 @@ public class YuvImage { private int mHeight; /** - * Construct an YuvImage. + * The color space of the image, defaults to SRGB + */ + @NonNull private ColorSpace mColorSpace; + + /** + * Array listing all supported ImageFormat that are supported by this class + */ + private final static String[] sSupportedFormats = + {"NV21", "YUY2", "YCBCR_P010", "YUV_420_888"}; + + private static String printSupportedFormats() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < sSupportedFormats.length; ++i) { + sb.append(sSupportedFormats[i]); + if (i != sSupportedFormats.length - 1) { + sb.append(", "); + } + } + return sb.toString(); + } + + /** + * Array listing all supported HDR ColorSpaces that are supported by JPEG/R encoding + */ + private final static ColorSpace.Named[] sSupportedJpegRHdrColorSpaces = { + ColorSpace.Named.BT2020_HLG, + ColorSpace.Named.BT2020_PQ + }; + + /** + * Array listing all supported SDR ColorSpaces that are supported by JPEG/R encoding + */ + private final static ColorSpace.Named[] sSupportedJpegRSdrColorSpaces = { + ColorSpace.Named.SRGB, + ColorSpace.Named.DISPLAY_P3 + }; + + private static String printSupportedJpegRColorSpaces(boolean isHdr) { + ColorSpace.Named[] colorSpaces = isHdr ? sSupportedJpegRHdrColorSpaces : + sSupportedJpegRSdrColorSpaces; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < colorSpaces.length; ++i) { + sb.append(ColorSpace.get(colorSpaces[i]).getName()); + if (i != colorSpaces.length - 1) { + sb.append(", "); + } + } + return sb.toString(); + } + + private static boolean isSupportedJpegRColorSpace(boolean isHdr, int colorSpace) { + ColorSpace.Named[] colorSpaces = isHdr ? sSupportedJpegRHdrColorSpaces : + sSupportedJpegRSdrColorSpaces; + for (ColorSpace.Named cs : colorSpaces) { + if (cs.ordinal() == colorSpace) { + return true; + } + } + return false; + } + + + /** + * Construct an YuvImage. Use SRGB for as default {@link ColorSpace}. * * @param yuv The YUV data. In the case of more than one image plane, all the planes must be * concatenated into a single byte array. @@ -77,11 +142,33 @@ public class YuvImage { * null. */ public YuvImage(byte[] yuv, int format, int width, int height, int[] strides) { + this(yuv, format, width, height, strides, ColorSpace.get(ColorSpace.Named.SRGB)); + } + + /** + * Construct an YuvImage. + * + * @param yuv The YUV data. In the case of more than one image plane, all the planes + * must be concatenated into a single byte array. + * @param format The YUV data format as defined in {@link ImageFormat}. + * @param width The width of the YuvImage. + * @param height The height of the YuvImage. + * @param strides (Optional) Row bytes of each image plane. If yuv contains padding, the + * stride of each image must be provided. If strides is null, the method + * assumes no padding and derives the row bytes by format and width itself. + * @param colorSpace The YUV image color space as defined in {@link ColorSpace}. + * If the parameter is null, SRGB will be set as the default value. + * @throws IllegalArgumentException if format is not support; width or height <= 0; or yuv is + * null. + */ + public YuvImage(@NonNull byte[] yuv, int format, int width, int height, + @Nullable int[] strides, @NonNull ColorSpace colorSpace) { if (format != ImageFormat.NV21 && - format != ImageFormat.YUY2) { + format != ImageFormat.YUY2 && + format != ImageFormat.YCBCR_P010 && + format != ImageFormat.YUV_420_888) { throw new IllegalArgumentException( - "only support ImageFormat.NV21 " + - "and ImageFormat.YUY2 for now"); + "only supports the following ImageFormat:" + printSupportedFormats()); } if (width <= 0 || height <= 0) { @@ -93,6 +180,10 @@ public class YuvImage { throw new IllegalArgumentException("yuv cannot be null"); } + if (colorSpace == null) { + throw new IllegalArgumentException("ColorSpace cannot be null"); + } + if (strides == null) { mStrides = calculateStrides(width, format); } else { @@ -103,12 +194,13 @@ public class YuvImage { mFormat = format; mWidth = width; mHeight = height; + mColorSpace = colorSpace; } /** * Compress a rectangle region in the YuvImage to a jpeg. - * Only ImageFormat.NV21 and ImageFormat.YUY2 - * are supported for now. + * For image format, only ImageFormat.NV21 and ImageFormat.YUY2 are supported. + * For color space, only SRGB is supported. * * @param rectangle The rectangle region to be compressed. The medthod checks if rectangle is * inside the image. Also, the method modifies rectangle if the chroma pixels @@ -117,10 +209,18 @@ public class YuvImage { * small size, 100 meaning compress for max quality. * @param stream OutputStream to write the compressed data. * @return True if the compression is successful. - * @throws IllegalArgumentException if rectangle is invalid; quality is not within [0, - * 100]; or stream is null. + * @throws IllegalArgumentException if rectangle is invalid; color space or image format + * is not supported; quality is not within [0, 100]; or stream is null. */ public boolean compressToJpeg(Rect rectangle, int quality, OutputStream stream) { + if (mFormat != ImageFormat.NV21 && mFormat != ImageFormat.YUY2) { + throw new IllegalArgumentException( + "Only ImageFormat.NV21 and ImageFormat.YUY2 are supported."); + } + if (mColorSpace.getId() != ColorSpace.Named.SRGB.ordinal()) { + throw new IllegalArgumentException("Only SRGB color space is supported."); + } + Rect wholeImage = new Rect(0, 0, mWidth, mHeight); if (!wholeImage.contains(rectangle)) { throw new IllegalArgumentException( @@ -143,6 +243,70 @@ public class YuvImage { new byte[WORKING_COMPRESS_STORAGE]); } + /** + * Compress the HDR image into JPEG/R format. + * + * Sample usage: + * hdr_image.compressToJpegR(sdr_image, 90, stream); + * + * For the SDR image, only YUV_420_888 image format is supported, and the following + * color spaces are supported: + * ColorSpace.Named.SRGB, + * ColorSpace.Named.DISPLAY_P3 + * + * For the HDR image, only YCBCR_P010 image format is supported, and the following + * color spaces are supported: + * ColorSpace.Named.BT2020_HLG, + * ColorSpace.Named.BT2020_PQ + * + * @param sdr The SDR image, only ImageFormat.YUV_420_888 is supported. + * @param quality Hint to the compressor, 0-100. 0 meaning compress for + * small size, 100 meaning compress for max quality. + * @param stream OutputStream to write the compressed data. + * @return True if the compression is successful. + * @throws IllegalArgumentException if input images are invalid; quality is not within [0, + * 100]; or stream is null. + */ + public boolean compressToJpegR(@NonNull YuvImage sdr, int quality, + @NonNull OutputStream stream) { + if (sdr == null) { + throw new IllegalArgumentException("SDR input cannot be null"); + } + + if (mData.length == 0 || sdr.getYuvData().length == 0) { + throw new IllegalArgumentException("Input images cannot be empty"); + } + + if (mFormat != ImageFormat.YCBCR_P010 || sdr.getYuvFormat() != ImageFormat.YUV_420_888) { + throw new IllegalArgumentException( + "only support ImageFormat.YCBCR_P010 and ImageFormat.YUV_420_888"); + } + + if (sdr.getWidth() != mWidth || sdr.getHeight() != mHeight) { + throw new IllegalArgumentException("HDR and SDR resolution mismatch"); + } + + if (quality < 0 || quality > 100) { + throw new IllegalArgumentException("quality must be 0..100"); + } + + if (stream == null) { + throw new IllegalArgumentException("stream cannot be null"); + } + + if (!isSupportedJpegRColorSpace(true, mColorSpace.getId()) || + !isSupportedJpegRColorSpace(false, sdr.getColorSpace().getId())) { + throw new IllegalArgumentException("Not supported color space. " + + "SDR only supports: " + printSupportedJpegRColorSpaces(false) + + "HDR only supports: " + printSupportedJpegRColorSpaces(true)); + } + + return nativeCompressToJpegR(mData, mColorSpace.getDataSpace(), + sdr.getYuvData(), sdr.getColorSpace().getDataSpace(), + mWidth, mHeight, quality, stream, + new byte[WORKING_COMPRESS_STORAGE]); + } + /** * @return the YUV data. @@ -179,6 +343,12 @@ public class YuvImage { return mHeight; } + + /** + * @return the color space of the image. + */ + public @NonNull ColorSpace getColorSpace() { return mColorSpace; } + int[] calculateOffsets(int left, int top) { int[] offsets = null; if (mFormat == ImageFormat.NV21) { @@ -198,17 +368,23 @@ public class YuvImage { private int[] calculateStrides(int width, int format) { int[] strides = null; - if (format == ImageFormat.NV21) { + switch (format) { + case ImageFormat.NV21: strides = new int[] {width, width}; return strides; - } - - if (format == ImageFormat.YUY2) { + case ImageFormat.YCBCR_P010: + strides = new int[] {width * 2, width * 2}; + return strides; + case ImageFormat.YUV_420_888: + strides = new int[] {width, (width + 1) / 2, (width + 1) / 2}; + return strides; + case ImageFormat.YUY2: strides = new int[] {width * 2}; return strides; + default: + throw new IllegalArgumentException( + "only supports the following ImageFormat:" + printSupportedFormats()); } - - return strides; } private void adjustRectangle(Rect rect) { @@ -237,4 +413,8 @@ public class YuvImage { private static native boolean nativeCompressToJpeg(byte[] oriYuv, int format, int width, int height, int[] offsets, int[] strides, int quality, OutputStream stream, byte[] tempStorage); + + private static native boolean nativeCompressToJpegR(byte[] hdr, int hdrColorSpaceId, + byte[] sdr, int sdrColorSpaceId, int width, int height, int quality, + OutputStream stream, byte[] tempStorage); } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index aeead5efc48a..9112b1b40c44 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -376,7 +376,10 @@ cc_defaults { "jni/text/TextShaper.cpp", ], - header_libs: ["android_graphics_jni_headers"], + header_libs: [ + "android_graphics_jni_headers", + "libnativewindow_headers", + ], include_dirs: [ "external/skia/include/private", @@ -392,10 +395,14 @@ cc_defaults { "libbase", "libcutils", "libharfbuzz_ng", + "libimage_io", + "libjpeg", + "libjpegdecoder", + "libjpegencoder", + "libjpegrecoverymap", "liblog", "libminikin", "libz", - "libjpeg", ], static_libs: [ diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp index 1c5f126d8672..80bca1f3a72f 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.cpp +++ b/libs/hwui/jni/YuvToJpegEncoder.cpp @@ -1,3 +1,6 @@ +#undef LOG_TAG +#define LOG_TAG "YuvToJpegEncoder" + #include "CreateJavaOutputStreamAdaptor.h" #include "SkJPEGWriteUtility.h" #include "SkStream.h" @@ -235,6 +238,99 @@ void Yuv422IToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) { } /////////////////////////////////////////////////////////////////////////////// +using namespace android::recoverymap; + +jpegr_color_gamut P010Yuv420ToJpegREncoder::findColorGamut(JNIEnv* env, int aDataSpace) { + switch (aDataSpace & ADataSpace::STANDARD_MASK) { + case ADataSpace::STANDARD_BT709: + return jpegr_color_gamut::JPEGR_COLORGAMUT_BT709; + case ADataSpace::STANDARD_DCI_P3: + return jpegr_color_gamut::JPEGR_COLORGAMUT_P3; + case ADataSpace::STANDARD_BT2020: + return jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100; + default: + jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException"); + env->ThrowNew(IllegalArgumentException, + "The requested color gamut is not supported by JPEG/R."); + } + + return jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED; +} + +jpegr_transfer_function P010Yuv420ToJpegREncoder::findHdrTransferFunction(JNIEnv* env, + int aDataSpace) { + switch (aDataSpace & ADataSpace::TRANSFER_MASK) { + case ADataSpace::TRANSFER_ST2084: + return jpegr_transfer_function::JPEGR_TF_PQ; + case ADataSpace::TRANSFER_HLG: + return jpegr_transfer_function::JPEGR_TF_HLG; + default: + jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException"); + env->ThrowNew(IllegalArgumentException, + "The requested HDR transfer function is not supported by JPEG/R."); + } + + return jpegr_transfer_function::JPEGR_TF_UNSPECIFIED; +} + +bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env, + SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace, + int width, int height, int jpegQuality) { + // Check SDR color space. Now we only support SRGB transfer function + if ((sdrColorSpace & ADataSpace::TRANSFER_MASK) != ADataSpace::TRANSFER_SRGB) { + jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException"); + env->ThrowNew(IllegalArgumentException, + "The requested SDR color space is not supported. Transfer function must be SRGB"); + return false; + } + + jpegr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace); + jpegr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace); + jpegr_transfer_function hdrTransferFunction = findHdrTransferFunction(env, hdrColorSpace); + + if (hdrColorGamut == jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED + || sdrColorGamut == jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED + || hdrTransferFunction == jpegr_transfer_function::JPEGR_TF_UNSPECIFIED) { + return false; + } + + RecoveryMap recoveryMap; + + jpegr_uncompressed_struct p010; + p010.data = hdr; + p010.width = width; + p010.height = height; + p010.colorGamut = hdrColorGamut; + + jpegr_uncompressed_struct yuv420; + yuv420.data = sdr; + yuv420.width = width; + yuv420.height = height; + yuv420.colorGamut = sdrColorGamut; + + jpegr_compressed_struct jpegR; + jpegR.maxLength = width * height * sizeof(uint8_t); + + std::unique_ptr jpegr_data = std::make_unique(jpegR.maxLength); + jpegR.data = jpegr_data.get(); + + if (int success = recoveryMap.encodeJPEGR(&p010, &yuv420, + hdrTransferFunction, + &jpegR, jpegQuality, nullptr); success != android::OK) { + ALOGW("Encode JPEG/R failed, error code: %d.", success); + return false; + } + + if (!stream->write(jpegR.data, jpegR.length)) { + ALOGW("Writing JPEG/R to stream failed."); + return false; + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv, jint format, jint width, jint height, jintArray offsets, jintArray strides, jint jpegQuality, jobject jstream, @@ -258,11 +354,34 @@ static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv, delete strm; return result; } + +static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr, + jint hdrColorSpace, jbyteArray inSdr, jint sdrColorSpace, + jint width, jint height, jint quality, jobject jstream, + jbyteArray jstorage) { + jbyte* hdr = env->GetByteArrayElements(inHdr, NULL); + jbyte* sdr = env->GetByteArrayElements(inSdr, NULL); + SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage); + P010Yuv420ToJpegREncoder encoder; + + jboolean result = JNI_FALSE; + if (encoder.encode(env, strm, hdr, hdrColorSpace, sdr, sdrColorSpace, + width, height, quality)) { + result = JNI_TRUE; + } + + env->ReleaseByteArrayElements(inHdr, hdr, 0); + env->ReleaseByteArrayElements(inSdr, sdr, 0); + delete strm; + return result; +} /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gYuvImageMethods[] = { { "nativeCompressToJpeg", "([BIII[I[IILjava/io/OutputStream;[B)Z", - (void*)YuvImage_compressToJpeg } + (void*)YuvImage_compressToJpeg }, + { "nativeCompressToJpegR", "([BI[BIIIILjava/io/OutputStream;[B)Z", + (void*)YuvImage_compressToJpegR } }; int register_android_graphics_YuvImage(JNIEnv* env) diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h index a69726b17e9d..3d6d1f3fd3dd 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.h +++ b/libs/hwui/jni/YuvToJpegEncoder.h @@ -1,6 +1,9 @@ #ifndef _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_ #define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_ +#include +#include + extern "C" { #include "jpeglib.h" #include "jerror.h" @@ -24,7 +27,7 @@ public: * * @param stream The jpeg output stream. * @param inYuv The input yuv data. - * @param width Width of the the Yuv data in terms of pixels. + * @param width Width of the Yuv data in terms of pixels. * @param height Height of the Yuv data in terms of pixels. * @param offsets The offsets in each image plane with respect to inYuv. * @param jpegQuality Picture quality in [0, 100]. @@ -71,4 +74,46 @@ private: uint8_t* vRows, int rowIndex, int width, int height); }; +class P010Yuv420ToJpegREncoder { +public: + /** Encode YUV data to jpeg/r, which is output to a stream. + * This method will call RecoveryMap::EncodeJPEGR() method. If encoding failed, + * Corresponding error code (defined in jpegrerrorcode.h) will be printed and this + * method will be terminated and return false. + * + * @param env JNI environment. + * @param stream The jpeg output stream. + * @param hdr The input yuv data (p010 format). + * @param hdrColorSpaceId color space id for the input hdr. + * @param sdr The input yuv data (yuv420p format). + * @param sdrColorSpaceId color space id for the input sdr. + * @param width Width of the Yuv data in terms of pixels. + * @param height Height of the Yuv data in terms of pixels. + * @param jpegQuality Picture quality in [0, 100]. + * @return true if successfully compressed the stream. + */ + bool encode(JNIEnv* env, + SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace, + int width, int height, int jpegQuality); + + /** Map data space (defined in DataSpace.java and data_space.h) to the color gamut + * used in JPEG/R + * + * @param env JNI environment. + * @param aDataSpace data space defined in data_space.h. + * @return color gamut for JPEG/R. + */ + static android::recoverymap::jpegr_color_gamut findColorGamut(JNIEnv* env, int aDataSpace); + + /** Map data space (defined in DataSpace.java and data_space.h) to the transfer function + * used in JPEG/R + * + * @param env JNI environment. + * @param aDataSpace data space defined in data_space.h. + * @return color gamut for JPEG/R. + */ + static android::recoverymap::jpegr_transfer_function findHdrTransferFunction( + JNIEnv* env, int aDataSpace); +}; + #endif // _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_ -- cgit v1.2.3-59-g8ed1b From 659fd2021168aa0ccebd9c357c08639021826210 Mon Sep 17 00:00:00 2001 From: Jorge Betancourt Date: Thu, 19 Jan 2023 19:21:26 +0000 Subject: Revert "add support for rendering lottie animations through a LottieDrawable" This reverts commit 2e58b5c4b9f7d96ce84ccc0dabe2fcd3667052fe. Reason for revert: prep for mainline Bug: 257304231 Change-Id: I51515a6eed577ad098020588f12cafc8fff0541e --- .../android/graphics/drawable/LottieDrawable.java | 151 --------------------- libs/hwui/Android.bp | 4 - libs/hwui/SkiaCanvas.cpp | 4 - libs/hwui/SkiaCanvas.h | 1 - libs/hwui/apex/jni_runtime.cpp | 2 - libs/hwui/hwui/Canvas.h | 2 - libs/hwui/hwui/LottieDrawable.cpp | 83 ----------- libs/hwui/hwui/LottieDrawable.h | 67 --------- libs/hwui/jni/LottieDrawable.cpp | 91 ------------- libs/hwui/pipeline/skia/SkiaDisplayList.cpp | 10 -- libs/hwui/pipeline/skia/SkiaDisplayList.h | 3 - libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp | 5 - libs/hwui/pipeline/skia/SkiaRecordingCanvas.h | 1 - tests/VectorDrawableTest/Android.bp | 2 - tests/VectorDrawableTest/AndroidManifest.xml | 9 -- tests/VectorDrawableTest/res/raw/lottie.json | 123 ----------------- .../android/test/dynamic/LottieDrawableTest.java | 76 ----------- 17 files changed, 634 deletions(-) delete mode 100644 graphics/java/android/graphics/drawable/LottieDrawable.java delete mode 100644 libs/hwui/hwui/LottieDrawable.cpp delete mode 100644 libs/hwui/hwui/LottieDrawable.h delete mode 100644 libs/hwui/jni/LottieDrawable.cpp delete mode 100644 tests/VectorDrawableTest/res/raw/lottie.json delete mode 100644 tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/drawable/LottieDrawable.java b/graphics/java/android/graphics/drawable/LottieDrawable.java deleted file mode 100644 index c1f1b50cf339..000000000000 --- a/graphics/java/android/graphics/drawable/LottieDrawable.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.graphics.drawable; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.PixelFormat; - -import dalvik.annotation.optimization.FastNative; - -import libcore.util.NativeAllocationRegistry; - -import java.io.IOException; - -/** - * {@link Drawable} for drawing Lottie files. - * - *

    The framework handles decoding subsequent frames in another thread and - * updating when necessary. The drawable will only animate while it is being - * displayed.

    - * - * @hide - */ -@SuppressLint("NotCloseable") -public class LottieDrawable extends Drawable implements Animatable { - private long mNativePtr; - - /** - * Create an animation from the provided JSON string - * @hide - */ - private LottieDrawable(@NonNull String animationJson) throws IOException { - mNativePtr = nCreate(animationJson); - if (mNativePtr == 0) { - throw new IOException("could not make LottieDrawable from json"); - } - - final long nativeSize = nNativeByteSize(mNativePtr); - NativeAllocationRegistry registry = new NativeAllocationRegistry( - LottieDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize); - registry.registerNativeAllocation(this, mNativePtr); - } - - /** - * Factory for LottieDrawable, throws IOException if native Skottie Builder fails to parse - */ - public static LottieDrawable makeLottieDrawable(@NonNull String animationJson) - throws IOException { - return new LottieDrawable(animationJson); - } - - - - /** - * Draw the current frame to the Canvas. - * @hide - */ - @Override - public void draw(@NonNull Canvas canvas) { - if (mNativePtr == 0) { - throw new IllegalStateException("called draw on empty LottieDrawable"); - } - - nDraw(mNativePtr, canvas.getNativeCanvasWrapper()); - } - - /** - * Start the animation. Needs to be called before draw calls. - * @hide - */ - @Override - public void start() { - if (mNativePtr == 0) { - throw new IllegalStateException("called start on empty LottieDrawable"); - } - - if (nStart(mNativePtr)) { - invalidateSelf(); - } - } - - /** - * Stops the animation playback. Does not release anything. - * @hide - */ - @Override - public void stop() { - if (mNativePtr == 0) { - throw new IllegalStateException("called stop on empty LottieDrawable"); - } - nStop(mNativePtr); - } - - /** - * Return whether the animation is currently running. - */ - @Override - public boolean isRunning() { - if (mNativePtr == 0) { - throw new IllegalStateException("called isRunning on empty LottieDrawable"); - } - return nIsRunning(mNativePtr); - } - - @Override - public int getOpacity() { - // We assume translucency to avoid checking each pixel. - return PixelFormat.TRANSLUCENT; - } - - @Override - public void setAlpha(int alpha) { - //TODO - } - - @Override - public void setColorFilter(@Nullable ColorFilter colorFilter) { - //TODO - } - - private static native long nCreate(String json); - private static native void nDraw(long nativeInstance, long nativeCanvas); - @FastNative - private static native long nGetNativeFinalizer(); - @FastNative - private static native long nNativeByteSize(long nativeInstance); - @FastNative - private static native boolean nIsRunning(long nativeInstance); - @FastNative - private static native boolean nStart(long nativeInstance); - @FastNative - private static native boolean nStop(long nativeInstance); - -} diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 9c4ea0ecbe30..3e3d77b89a9a 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -78,7 +78,6 @@ cc_defaults { "external/skia/src/utils", "external/skia/src/gpu", "external/skia/src/shaders", - "external/skia/modules/skottie", ], }, host: { @@ -375,7 +374,6 @@ cc_defaults { "external/skia/src/effects", "external/skia/src/image", "external/skia/src/images", - "external/skia/modules/skottie", ], shared_libs: [ @@ -402,7 +400,6 @@ cc_defaults { "jni/BitmapRegionDecoder.cpp", "jni/GIFMovie.cpp", "jni/GraphicsStatsService.cpp", - "jni/LottieDrawable.cpp", "jni/Movie.cpp", "jni/MovieImpl.cpp", "jni/pdf/PdfDocument.cpp", @@ -510,7 +507,6 @@ cc_defaults { "hwui/BlurDrawLooper.cpp", "hwui/Canvas.cpp", "hwui/ImageDecoder.cpp", - "hwui/LottieDrawable.cpp", "hwui/MinikinSkia.cpp", "hwui/MinikinUtils.cpp", "hwui/PaintImpl.cpp", diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index a1c4b49b6742..d83d78f650aa 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -736,10 +736,6 @@ double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) { return imgDrawable->drawStaging(mCanvas); } -void SkiaCanvas::drawLottie(LottieDrawable* lottieDrawable) { - lottieDrawable->drawStaging(mCanvas); -} - void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) { vectorDrawable->drawStaging(this); } diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index fd8b6cdb7ca0..31e3b4c3c7e2 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -145,7 +145,6 @@ public: float dstTop, float dstRight, float dstBottom, const Paint* paint) override; virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) override; - virtual void drawLottie(LottieDrawable* lottieDrawable) override; virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override; diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp index 5a96174ebac0..e6cfa7bcaf70 100644 --- a/libs/hwui/apex/jni_runtime.cpp +++ b/libs/hwui/apex/jni_runtime.cpp @@ -37,7 +37,6 @@ extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env); extern int register_android_graphics_Graphics(JNIEnv* env); extern int register_android_graphics_ImageDecoder(JNIEnv*); extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv*); -extern int register_android_graphics_drawable_LottieDrawable(JNIEnv*); extern int register_android_graphics_Interpolator(JNIEnv* env); extern int register_android_graphics_MaskFilter(JNIEnv* env); extern int register_android_graphics_Movie(JNIEnv* env); @@ -117,7 +116,6 @@ extern int register_android_view_ThreadedRenderer(JNIEnv* env); REG_JNI(register_android_graphics_HardwareRendererObserver), REG_JNI(register_android_graphics_ImageDecoder), REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable), - REG_JNI(register_android_graphics_drawable_LottieDrawable), REG_JNI(register_android_graphics_Interpolator), REG_JNI(register_android_graphics_MaskFilter), REG_JNI(register_android_graphics_Matrix), diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 07e2fe24c939..2a2019199bda 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -60,7 +60,6 @@ typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; typedef std::function ReadGlyphFunc; class AnimatedImageDrawable; -class LottieDrawable; class Bitmap; class Paint; struct Typeface; @@ -243,7 +242,6 @@ public: const Paint* paint) = 0; virtual double drawAnimatedImage(AnimatedImageDrawable* imgDrawable) = 0; - virtual void drawLottie(LottieDrawable* lottieDrawable) = 0; virtual void drawPicture(const SkPicture& picture) = 0; /** diff --git a/libs/hwui/hwui/LottieDrawable.cpp b/libs/hwui/hwui/LottieDrawable.cpp deleted file mode 100644 index 92dc51e01a85..000000000000 --- a/libs/hwui/hwui/LottieDrawable.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "LottieDrawable.h" - -#include -#include -#include - -namespace android { - -sk_sp LottieDrawable::Make(sk_sp animation, size_t bytesUsed) { - if (animation) { - return sk_sp(new LottieDrawable(std::move(animation), bytesUsed)); - } - return nullptr; -} -LottieDrawable::LottieDrawable(sk_sp animation, size_t bytesUsed) - : mAnimation(std::move(animation)), mBytesUsed(bytesUsed) {} - -bool LottieDrawable::start() { - if (mRunning) { - return false; - } - - mRunning = true; - return true; -} - -bool LottieDrawable::stop() { - bool wasRunning = mRunning; - mRunning = false; - return wasRunning; -} - -bool LottieDrawable::isRunning() { - return mRunning; -} - -// TODO: Check to see if drawable is actually dirty -bool LottieDrawable::isDirty() { - return true; -} - -void LottieDrawable::onDraw(SkCanvas* canvas) { - if (mRunning) { - const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); - - nsecs_t t = 0; - if (mStartTime == 0) { - mStartTime = currentTime; - } else { - t = currentTime - mStartTime; - } - double seekTime = std::fmod((double)t * 1e-9, mAnimation->duration()); - mAnimation->seekFrameTime(seekTime); - mAnimation->render(canvas); - } -} - -void LottieDrawable::drawStaging(SkCanvas* canvas) { - onDraw(canvas); -} - -SkRect LottieDrawable::onGetBounds() { - // We do not actually know the bounds, so give a conservative answer. - return SkRectMakeLargest(); -} - -} // namespace android diff --git a/libs/hwui/hwui/LottieDrawable.h b/libs/hwui/hwui/LottieDrawable.h deleted file mode 100644 index 9cc34bf12f4f..000000000000 --- a/libs/hwui/hwui/LottieDrawable.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -class SkCanvas; - -namespace android { - -/** - * Native component of android.graphics.drawable.LottieDrawable.java. - * This class can be drawn into Canvas.h and maintains the state needed to drive - * the animation from the RenderThread. - */ -class LottieDrawable : public SkDrawable { -public: - static sk_sp Make(sk_sp animation, size_t bytes); - - // Draw to software canvas - void drawStaging(SkCanvas* canvas); - - // Returns true if the animation was started; false otherwise (e.g. it was - // already running) - bool start(); - // Returns true if the animation was stopped; false otherwise (e.g. it was - // already stopped) - bool stop(); - bool isRunning(); - - // TODO: Is dirty should take in a time til next frame to determine if it is dirty - bool isDirty(); - - SkRect onGetBounds() override; - - size_t byteSize() const { return sizeof(*this) + mBytesUsed; } - -protected: - void onDraw(SkCanvas* canvas) override; - -private: - LottieDrawable(sk_sp animation, size_t bytes_used); - - sk_sp mAnimation; - bool mRunning = false; - // The start time for the drawable itself. - nsecs_t mStartTime = 0; - const size_t mBytesUsed = 0; -}; - -} // namespace android diff --git a/libs/hwui/jni/LottieDrawable.cpp b/libs/hwui/jni/LottieDrawable.cpp deleted file mode 100644 index fb6eede213a8..000000000000 --- a/libs/hwui/jni/LottieDrawable.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include - -#include "GraphicsJNI.h" -#include "Utils.h" - -using namespace android; - -static jclass gLottieDrawableClass; - -static jlong LottieDrawable_nCreate(JNIEnv* env, jobject, jstring jjson) { - const ScopedUtfChars cstr(env, jjson); - // TODO(b/259267150) provide more accurate byteSize - size_t bytes = strlen(cstr.c_str()); - auto animation = skottie::Animation::Builder().make(cstr.c_str(), bytes); - sk_sp drawable(LottieDrawable::Make(std::move(animation), bytes)); - if (!drawable) { - return 0; - } - return reinterpret_cast(drawable.release()); -} - -static void LottieDrawable_destruct(LottieDrawable* drawable) { - SkSafeUnref(drawable); -} - -static jlong LottieDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) { - return static_cast(reinterpret_cast(&LottieDrawable_destruct)); -} - -static void LottieDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jlong canvasPtr) { - auto* drawable = reinterpret_cast(nativePtr); - auto* canvas = reinterpret_cast(canvasPtr); - canvas->drawLottie(drawable); -} - -static jboolean LottieDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { - auto* drawable = reinterpret_cast(nativePtr); - return drawable->isRunning(); -} - -static jboolean LottieDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { - auto* drawable = reinterpret_cast(nativePtr); - return drawable->start(); -} - -static jboolean LottieDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { - auto* drawable = reinterpret_cast(nativePtr); - return drawable->stop(); -} - -static jlong LottieDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { - auto* drawable = reinterpret_cast(nativePtr); - return drawable->byteSize(); -} - -static const JNINativeMethod gLottieDrawableMethods[] = { - {"nCreate", "(Ljava/lang/String;)J", (void*)LottieDrawable_nCreate}, - {"nNativeByteSize", "(J)J", (void*)LottieDrawable_nNativeByteSize}, - {"nGetNativeFinalizer", "()J", (void*)LottieDrawable_nGetNativeFinalizer}, - {"nDraw", "(JJ)V", (void*)LottieDrawable_nDraw}, - {"nIsRunning", "(J)Z", (void*)LottieDrawable_nIsRunning}, - {"nStart", "(J)Z", (void*)LottieDrawable_nStart}, - {"nStop", "(J)Z", (void*)LottieDrawable_nStop}, -}; - -int register_android_graphics_drawable_LottieDrawable(JNIEnv* env) { - gLottieDrawableClass = reinterpret_cast( - env->NewGlobalRef(FindClassOrDie(env, "android/graphics/drawable/LottieDrawable"))); - - return android::RegisterMethodsOrDie(env, "android/graphics/drawable/LottieDrawable", - gLottieDrawableMethods, NELEM(gLottieDrawableMethods)); -} diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index f0dc5eb4dd0e..fcfc4f82abed 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -146,16 +146,6 @@ bool SkiaDisplayList::prepareListAndChildren( } } - for (auto& lottie : mLotties) { - // If any animated image in the display list needs updated, then damage the node. - if (lottie->isDirty()) { - isDirty = true; - } - if (lottie->isRunning()) { - info.out.hasAnimations = true; - } - } - for (auto& [vectorDrawable, cachedMatrix] : mVectorDrawables) { // If any vector drawable in the display list needs update, damage the node. if (vectorDrawable->isDirty()) { diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index 39217fcf1a56..2a677344b7b2 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.h +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h @@ -22,7 +22,6 @@ #include "RenderNodeDrawable.h" #include "TreeInfo.h" #include "hwui/AnimatedImageDrawable.h" -#include "hwui/LottieDrawable.h" #include "utils/LinearAllocator.h" #include "utils/Pair.h" @@ -187,8 +186,6 @@ public: return mHasHolePunches; } - // TODO(b/257304231): create common base class for Lotties and AnimatedImages - std::vector mLotties; std::vector mAnimatedImages; DisplayListData mDisplayList; diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index db449d608c1f..1f87865f2672 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -188,11 +188,6 @@ void SkiaRecordingCanvas::drawWebViewFunctor(int functor) { #endif } -void SkiaRecordingCanvas::drawLottie(LottieDrawable* lottie) { - drawDrawable(lottie); - mDisplayList->mLotties.push_back(lottie); -} - void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { mRecorder.drawVectorDrawable(tree); SkMatrix mat; diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index c823d8d0a755..7844e2cc2a73 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -78,7 +78,6 @@ public: uirenderer::CanvasPropertyPaint* paint) override; virtual void drawRipple(const RippleDrawableParams& params) override; - virtual void drawLottie(LottieDrawable* lottieDrawable) override; virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override; virtual void enableZ(bool enableZ) override; diff --git a/tests/VectorDrawableTest/Android.bp b/tests/VectorDrawableTest/Android.bp index 099d874375a1..9da7c5fdbb17 100644 --- a/tests/VectorDrawableTest/Android.bp +++ b/tests/VectorDrawableTest/Android.bp @@ -26,7 +26,5 @@ package { android_test { name: "VectorDrawableTest", srcs: ["**/*.java"], - // certificate set as platform to allow testing of @hidden APIs - certificate: "platform", platform_apis: true, } diff --git a/tests/VectorDrawableTest/AndroidManifest.xml b/tests/VectorDrawableTest/AndroidManifest.xml index 163e438e0677..5334dac57ca2 100644 --- a/tests/VectorDrawableTest/AndroidManifest.xml +++ b/tests/VectorDrawableTest/AndroidManifest.xml @@ -158,15 +158,6 @@ - - - - - - - diff --git a/tests/VectorDrawableTest/res/raw/lottie.json b/tests/VectorDrawableTest/res/raw/lottie.json deleted file mode 100644 index fea571c6bedb..000000000000 --- a/tests/VectorDrawableTest/res/raw/lottie.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "v":"4.6.9", - "fr":60, - "ip":0, - "op":200, - "w":800, - "h":600, - "nm":"Loader 1 JSON", - "ddd":0, - - - "layers":[ - { - "ddd":0, - "ind":1, - "ty":4, - "nm":"Custom Path 1", - "ao": 0, - "ip": 0, - "op": 300, - "st": 0, - "sr": 1, - "bm": 0, - "ks": { - "o": { "a":0, "k":100 }, - "r": { "a":1, "k": [ - { "s": [ 0 ], "e": [ 360], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 }, - { "t": 200 } - ] }, - "p": { "a":0, "k":[ 300, 300, 0 ] }, - "a": { "a":0, "k":[ 100, 100, 0 ] }, - "s": { "a":1, "k":[ - { "s": [ 100, 100 ], "e": [ 200, 200 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 }, - { "s": [ 200, 200 ], "e": [ 100, 100 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 100 }, - { "t": 200 } - ] } - }, - - "shapes":[ - { - "ty":"gr", - "it":[ - { - "ty" : "sh", - "nm" : "Path 1", - "ks" : { - "a" : 1, - "k" : [ - { - "s": [ { - "i": [ [ 0, 50 ], [ -50, 0 ], [ 0, -50 ], [ 50, 0 ] ], - "o": [ [ 0, -50 ], [ 50, 0 ], [ 0, 50 ], [ -50, 0 ] ], - "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ], - "c": true - } ], - "e": [ { - "i": [ [ 50, 50 ], [ -50, 0 ], [ -50, -50 ], [ 50, 50 ] ], - "o": [ [ 50, -50 ], [ 50, 0 ], [ -50, 50 ], [ -50, 50 ] ], - "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ], - "c": true - } ], - "i": { "x":0.5, "y":0.5 }, - "o": { "x":0.5, "y":0.5 }, - "t": 0 - }, - { - "s": [ { - "i": [ [ 50, 50 ], [ -50, 0 ], [ -50, -50 ], [ 50, 50 ] ], - "o": [ [ 50, -50 ], [ 50, 0 ], [ -50, 50 ], [ -50, 50 ] ], - "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ], - "c": true - } ], - "e": [ { - "i": [ [ 0, 50 ], [ -50, 0 ], [ 0, -50 ], [ 50, 0 ] ], - "o": [ [ 0, -50 ], [ 50, 0 ], [ 0, 50 ], [ -50, 0 ] ], - "v": [ [ 0, 100 ], [ 100, 0 ], [ 200, 100 ], [ 100, 200 ] ], - "c": true - } ], - "i": { "x":0.5, "y":0.5 }, - "o": { "x":0.5, "y":0.5 }, - "t": 100 - }, - { - "t": 200 - } - ] - } - }, - - { - "ty": "st", - "nm": "Stroke 1", - "lc": 1, - "lj": 1, - "ml": 4, - "w" : { "a": 1, "k": [ - { "s": [ 30 ], "e": [ 50 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 }, - { "s": [ 50 ], "e": [ 30 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 100 }, - { "t": 200 } - ] }, - "o" : { "a": 0, "k": 100 }, - "c" : { "a": 1, "k": [ - { "s": [ 0, 1, 0 ], "e": [ 1, 0, 0 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 0 }, - { "s": [ 1, 0, 0 ], "e": [ 0, 1, 0 ], "i": { "x":0.5, "y":0.5 }, "o": { "x":0.5, "y":0.5 }, "t": 100 }, - { "t": 200 } - ] } - }, - - { - "ty":"tr", - "p" : { "a":0, "k":[ 0, 0 ] }, - "a" : { "a":0, "k":[ 0, 0 ] }, - "s" : { "a":0, "k":[ 100, 100 ] }, - "r" : { "a":0, "k": 0 }, - "o" : { "a":0, "k":100 }, - "nm": "Transform" - } - ] - } - ] - } - ] - } diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java deleted file mode 100644 index 05eae7b0e642..000000000000 --- a/tests/VectorDrawableTest/src/com/android/test/dynamic/LottieDrawableTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.test.dynamic; - -import android.app.Activity; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.LottieDrawable; -import android.os.Bundle; -import android.view.View; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Scanner; - -@SuppressWarnings({"UnusedDeclaration"}) -public class LottieDrawableTest extends Activity { - private static final String TAG = "LottieDrawableTest"; - static final int BACKGROUND = 0xFFF44336; - - class LottieDrawableView extends View { - private Rect mLottieBounds; - - private LottieDrawable mLottie; - - LottieDrawableView(Context context, InputStream is) { - super(context); - Scanner s = new Scanner(is).useDelimiter("\\A"); - String json = s.hasNext() ? s.next() : ""; - try { - mLottie = LottieDrawable.makeLottieDrawable(json); - } catch (IOException e) { - throw new RuntimeException(TAG + ": error parsing test Lottie"); - } - mLottie.start(); - } - - @Override - protected void onDraw(Canvas canvas) { - canvas.drawColor(BACKGROUND); - - mLottie.setBounds(mLottieBounds); - mLottie.draw(canvas); - } - - public void setLottieSize(Rect bounds) { - mLottieBounds = bounds; - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - InputStream is = getResources().openRawResource(R.raw.lottie); - - LottieDrawableView view = new LottieDrawableView(this, is); - view.setLottieSize(new Rect(0, 0, 900, 900)); - setContentView(view); - } -} -- cgit v1.2.3-59-g8ed1b From 3a269b0ecf87c35ced38db6b9a156d3cbb55cc6d Mon Sep 17 00:00:00 2001 From: Derek Sollenberger Date: Mon, 12 Dec 2022 15:22:07 -0500 Subject: Provide access to the bitmap's SharedMemory. The shared memory object can then be passed through HIDL to avoid unecessary copying of pixel data. Test: WearOS DisplayOffload Bug: 260872900 Change-Id: I7f78d2940a295190bd1f1076a01419481dd0d15c --- graphics/java/android/graphics/Bitmap.java | 24 ++++++++++++++++++++++++ libs/hwui/jni/Bitmap.cpp | 6 ++++++ 2 files changed, 30 insertions(+) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 3c654d6dec48..e60506ff88ef 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -26,7 +26,9 @@ import android.compat.annotation.UnsupportedAppUsage; import android.hardware.HardwareBuffer; import android.os.Build; import android.os.Parcel; +import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import android.os.SharedMemory; import android.os.StrictMode; import android.os.Trace; import android.util.DisplayMetrics; @@ -38,6 +40,7 @@ import dalvik.annotation.optimization.CriticalNative; import libcore.util.NativeAllocationRegistry; +import java.io.IOException; import java.io.OutputStream; import java.lang.ref.WeakReference; import java.nio.Buffer; @@ -737,6 +740,26 @@ public final class Bitmap implements Parcelable { return shared; } + /** + * Returns the shared memory handle to the pixel storage if the bitmap is already using + * shared memory and null if it is not. The SharedMemory object is then useful to then pass + * through HIDL APIs (e.g. WearOS's DisplayOffload service). + * + * @hide + */ + public SharedMemory getSharedMemory() { + checkRecycled("Cannot access shared memory of a recycled bitmap"); + if (nativeIsBackedByAshmem(mNativePtr)) { + try { + int fd = nativeGetAshmemFD(mNativePtr); + return SharedMemory.fromFileDescriptor(ParcelFileDescriptor.fromFd(fd)); + } catch (IOException e) { + Log.e(TAG, "Unable to create dup'd file descriptor for shared bitmap memory"); + } + } + return null; + } + /** * Create a hardware bitmap backed by a {@link HardwareBuffer}. * @@ -2294,6 +2317,7 @@ public final class Bitmap implements Parcelable { boolean isMutable); private static native Bitmap nativeCopyAshmem(long nativeSrcBitmap); private static native Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig); + private static native int nativeGetAshmemFD(long nativeBitmap); private static native long nativeGetNativeFinalizer(); private static native void nativeRecycle(long nativeBitmap); @UnsupportedAppUsage diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index 540abec38ebc..c68a6b9962c2 100755 --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -422,6 +422,11 @@ static jobject Bitmap_copyAshmemConfig(JNIEnv* env, jobject, jlong srcHandle, ji return ret; } +static jint Bitmap_getAshmemFd(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmap(bitmapHandle); + return (bitmap.valid()) ? bitmap->bitmap().getAshmemFd() : -1; +} + static void Bitmap_destruct(BitmapWrapper* bitmap) { delete bitmap; } @@ -1257,6 +1262,7 @@ static const JNINativeMethod gBitmapMethods[] = { (void*)Bitmap_copyAshmem }, { "nativeCopyAshmemConfig", "(JI)Landroid/graphics/Bitmap;", (void*)Bitmap_copyAshmemConfig }, + { "nativeGetAshmemFD", "(J)I", (void*)Bitmap_getAshmemFd }, { "nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer }, { "nativeRecycle", "(J)V", (void*)Bitmap_recycle }, { "nativeReconfigure", "(JIIIZ)V", (void*)Bitmap_reconfigure }, -- cgit v1.2.3-59-g8ed1b From 5bd537ea140e393ba64421cd8da168736c3b269e Mon Sep 17 00:00:00 2001 From: John Reck Date: Tue, 24 Jan 2023 20:13:45 -0500 Subject: Add Gainmap bitmap & imagedecoder Bug: 266628247 Test: builds & boots Change-Id: I0da44e0c48cf8a6b6f95e3b62f6d5f74bd6c1eab --- graphics/java/android/graphics/Bitmap.java | 27 ++++++ graphics/java/android/graphics/Gainmap.java | 127 +++++++++++++++++++++++++++ libs/hwui/Android.bp | 1 + libs/hwui/Gainmap.h | 32 +++++++ libs/hwui/apex/jni_runtime.cpp | 2 + libs/hwui/hwui/Bitmap.cpp | 12 +++ libs/hwui/hwui/Bitmap.h | 12 +++ libs/hwui/hwui/ImageDecoder.cpp | 88 +++++++++++++++++-- libs/hwui/hwui/ImageDecoder.h | 7 ++ libs/hwui/jni/Bitmap.cpp | 130 +++++++++++++++------------- libs/hwui/jni/Gainmap.cpp | 125 ++++++++++++++++++++++++++ libs/hwui/jni/ImageDecoder.cpp | 45 +++++++--- 12 files changed, 529 insertions(+), 79 deletions(-) create mode 100644 graphics/java/android/graphics/Gainmap.java create mode 100644 libs/hwui/Gainmap.h mode change 100755 => 100644 libs/hwui/jni/Bitmap.cpp create mode 100644 libs/hwui/jni/Gainmap.cpp (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index e60506ff88ef..046373d671af 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -93,6 +93,7 @@ public final class Bitmap implements Parcelable { private boolean mRecycled; private ColorSpace mColorSpace; + private Gainmap mGainmap; /*package*/ int mDensity = getDefaultDensity(); @@ -1896,6 +1897,27 @@ public final class Bitmap implements Parcelable { } } + /** + * Returns whether or not this Bitmap contains a Gainmap. + * @hide + */ + public boolean hasGainmap() { + checkRecycled("Bitmap is recycled"); + return nativeHasGainmap(mNativePtr); + } + + /** + * Returns the gainmap or null if the bitmap doesn't contain a gainmap + * @hide + */ + public @Nullable Gainmap getGainmap() { + checkRecycled("Bitmap is recycled"); + if (mGainmap == null) { + mGainmap = nativeExtractGainmap(mNativePtr); + } + return mGainmap; + } + /** * Fills the bitmap's pixels with the specified {@link Color}. * @@ -2380,6 +2402,8 @@ public final class Bitmap implements Parcelable { private static native void nativeSetImmutable(long nativePtr); + private static native Gainmap nativeExtractGainmap(long nativePtr); + // ---------------- @CriticalNative ------------------- @CriticalNative @@ -2387,4 +2411,7 @@ public final class Bitmap implements Parcelable { @CriticalNative private static native boolean nativeIsBackedByAshmem(long nativePtr); + + @CriticalNative + private static native boolean nativeHasGainmap(long nativePtr); } diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java new file mode 100644 index 000000000000..a25a60568510 --- /dev/null +++ b/graphics/java/android/graphics/Gainmap.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import android.annotation.NonNull; + +import libcore.util.NativeAllocationRegistry; + +/** + * Gainmap represents a mechanism for augmenting an SDR image to produce an HDR one with variable + * display adjustment capability. + * + * It is a combination of a set of metadata describing the gainmap, as well as either a 1 or 3 + * channel Bitmap that represents the gainmap data itself. + * + * @hide + */ +public class Gainmap { + private final long mNativePtr; + private final Bitmap mGainmapImage; + + // called from JNI and Bitmap_Delegate. + private Gainmap(Bitmap gainmapImage, long nativeGainmap, int allocationByteCount, + boolean fromMalloc) { + if (nativeGainmap == 0) { + throw new RuntimeException("internal error: native gainmap is 0"); + } + + mGainmapImage = gainmapImage; + mNativePtr = nativeGainmap; + + final NativeAllocationRegistry registry; + if (fromMalloc) { + registry = NativeAllocationRegistry.createMalloced( + Bitmap.class.getClassLoader(), nGetFinalizer(), allocationByteCount); + } else { + registry = NativeAllocationRegistry.createNonmalloced( + Bitmap.class.getClassLoader(), nGetFinalizer(), allocationByteCount); + } + registry.registerNativeAllocation(this, nativeGainmap); + } + + /** + * Returns the image data of the gainmap represented as a Bitmap + * @return + */ + @NonNull + public Bitmap getGainmapImage() { + return mGainmapImage; + } + + /** + * Sets the gainmap max metadata. For single-plane gainmaps, r, g, and b should be the same. + */ + @NonNull + public void setGainmapMax(float r, float g, float b) { + nSetGainmapMax(mNativePtr, r, g, b); + } + + /** + * Gets the gainmap max metadata. For single-plane gainmaps, all 3 components should be the + * same. The components are in r, g, b order. + */ + @NonNull + public float[] getGainmapMax() { + float[] ret = new float[3]; + nGetGainmapMax(mNativePtr, ret); + return ret; + } + + /** + * Sets the maximum HDR ratio for the gainmap + */ + @NonNull + public void setHdrRatioMax(float max) { + nSetHdrRatioMax(mNativePtr, max); + } + + /** + * Gets the maximum HDR ratio for the gainmap + */ + @NonNull + public float getHdrRatioMax() { + return nGetHdrRatioMax(mNativePtr); + } + + /** + * Sets the maximum HDR ratio for the gainmap + */ + @NonNull + public void setHdrRatioMin(float min) { + nSetHdrRatioMin(mNativePtr, min); + } + + /** + * Gets the maximum HDR ratio for the gainmap + */ + @NonNull + public float getHdrRatioMin() { + return nGetHdrRatioMin(mNativePtr); + } + + private static native long nGetFinalizer(); + + private static native void nSetGainmapMax(long ptr, float r, float g, float b); + private static native void nGetGainmapMax(long ptr, float[] components); + + private static native void nSetHdrRatioMax(long ptr, float max); + private static native float nGetHdrRatioMax(long ptr); + + private static native void nSetHdrRatioMin(long ptr, float min); + private static native float nGetHdrRatioMin(long ptr); +} diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 8d4bda26c841..dd6c2bc326fd 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -347,6 +347,7 @@ cc_defaults { "jni/CreateJavaOutputStreamAdaptor.cpp", "jni/FontFamily.cpp", "jni/FontUtils.cpp", + "jni/Gainmap.cpp", "jni/Graphics.cpp", "jni/ImageDecoder.cpp", "jni/Interpolator.cpp", diff --git a/libs/hwui/Gainmap.h b/libs/hwui/Gainmap.h new file mode 100644 index 000000000000..765f98a257b8 --- /dev/null +++ b/libs/hwui/Gainmap.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +namespace android::uirenderer { + +class Gainmap : public LightRefBase { +public: + SkGainmapInfo info; + sk_sp bitmap; +}; + +} // namespace android::uirenderer diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp index f57d80c496ad..c509ed4dfd21 100644 --- a/libs/hwui/apex/jni_runtime.cpp +++ b/libs/hwui/apex/jni_runtime.cpp @@ -55,6 +55,7 @@ extern int register_android_graphics_ColorFilter(JNIEnv* env); extern int register_android_graphics_ColorSpace(JNIEnv* env); extern int register_android_graphics_DrawFilter(JNIEnv* env); extern int register_android_graphics_FontFamily(JNIEnv* env); +extern int register_android_graphics_Gainmap(JNIEnv* env); extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env); extern int register_android_graphics_Matrix(JNIEnv* env); extern int register_android_graphics_Paint(JNIEnv* env); @@ -114,6 +115,7 @@ extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); REG_JNI(register_android_graphics_ColorFilter), REG_JNI(register_android_graphics_DrawFilter), REG_JNI(register_android_graphics_FontFamily), + REG_JNI(register_android_graphics_Gainmap), REG_JNI(register_android_graphics_HardwareRendererObserver), REG_JNI(register_android_graphics_ImageDecoder), REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable), diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index feafc2372442..0a755f0d67fd 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -34,6 +34,7 @@ #include #endif +#include #include #include #include @@ -44,6 +45,7 @@ #include #include #include + #include namespace android { @@ -494,4 +496,14 @@ bool Bitmap::compress(const SkBitmap& bitmap, JavaCompressFormat format, return SkEncodeImage(stream, bitmap, fm, quality); } + +sp Bitmap::gainmap() const { + LOG_ALWAYS_FATAL_IF(!hasGainmap(), "Bitmap doesn't have a gainmap"); + return mGainmap; +} + +void Bitmap::setGainmap(sp&& gainmap) { + mGainmap = std::move(gainmap); +} + } // namespace android diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index 133f1fe0a1e7..912d311c3678 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -23,6 +23,10 @@ #include #include #include +#include + +#include + #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration #include #endif @@ -47,6 +51,7 @@ enum class BitmapPalette { }; namespace uirenderer { +class Gainmap; namespace renderthread { class RenderThread; } @@ -119,6 +124,11 @@ public: void getBounds(SkRect* bounds) const; bool isHardware() const { return mPixelStorageType == PixelStorageType::Hardware; } + bool hasGainmap() const { return mGainmap.get() != nullptr; } + + sp gainmap() const; + + void setGainmap(sp&& gainmap); PixelStorageType pixelStorageType() const { return mPixelStorageType; } @@ -193,6 +203,8 @@ private: bool mHasHardwareMipMap = false; + sp mGainmap; + union { struct { SkPixelRef* pixelRef; diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index dd68f825b61d..b1abe8529afe 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -16,15 +16,20 @@ #include "ImageDecoder.h" -#include -#include - +#include #include #include #include #include #include +#include #include +#include +#include +#include +#include + +#include #undef LOG_TAG #define LOG_TAG "ImageDecoder" @@ -195,7 +200,7 @@ SkImageInfo ImageDecoder::getOutputInfo() const { } bool ImageDecoder::swapWidthHeight() const { - return SkEncodedOriginSwapsWidthHeight(mCodec->codec()->getOrigin()); + return SkEncodedOriginSwapsWidthHeight(getOrigin()); } int ImageDecoder::width() const { @@ -316,7 +321,7 @@ SkCodec::FrameInfo ImageDecoder::getCurrentFrameInfo() { info.fFrameRect = SkIRect::MakeSize(dims); } - if (auto origin = mCodec->codec()->getOrigin(); origin != kDefault_SkEncodedOrigin) { + if (auto origin = getOrigin(); origin != kDefault_SkEncodedOrigin) { if (SkEncodedOriginSwapsWidthHeight(origin)) { dims = swapped(dims); } @@ -400,7 +405,7 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380. SkBitmap tmp; const bool scale = mDecodeSize != mTargetSize; - const auto origin = mCodec->codec()->getOrigin(); + const auto origin = getOrigin(); const bool handleOrigin = origin != kDefault_SkEncodedOrigin; SkMatrix outputMatrix; if (scale || handleOrigin || mCropRect) { @@ -455,12 +460,15 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { mOptions.fZeroInitialized = SkCodec::kYes_ZeroInitialized; } + ATRACE_BEGIN("getAndroidPixels"); auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &mOptions); + ATRACE_END(); // The next call to decode() may not provide zero initialized memory. mOptions.fZeroInitialized = SkCodec::kNo_ZeroInitialized; if (scale || handleOrigin || mCropRect) { + ATRACE_NAME("Handling scale/origin/crop"); SkBitmap scaledBm; if (!scaledBm.installPixels(outputInfo, pixels, rowBytes)) { return SkCodec::kInternalError; @@ -478,3 +486,71 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { return result; } +SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) { + ATRACE_CALL(); + SkGainmapInfo gainmapInfo; + std::unique_ptr gainmapStream; + { + ATRACE_NAME("getAndroidGainmap"); + if (!mCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream)) { + return SkCodec::kSuccess; + } + } + auto gainmapCodec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream)); + if (!gainmapCodec) { + ALOGW("Failed to create codec for gainmap stream"); + return SkCodec::kInvalidInput; + } + ImageDecoder decoder{std::move(gainmapCodec)}; + // Gainmap inherits the origin of the containing image + decoder.mOverrideOrigin.emplace(getOrigin()); + + const bool isScaled = width() != mTargetSize.width() || height() != mTargetSize.height(); + + if (isScaled) { + float scaleX = (float)mTargetSize.width() / width(); + float scaleY = (float)mTargetSize.height() / height(); + decoder.setTargetSize(decoder.width() * scaleX, decoder.height() * scaleY); + } + + if (mCropRect) { + float sX = decoder.mTargetSize.width() / (float)mTargetSize.width(); + float sY = decoder.mTargetSize.height() / (float)mTargetSize.height(); + SkIRect crop = *mCropRect; + // TODO: Tweak rounding? + crop.fLeft *= sX; + crop.fRight *= sX; + crop.fTop *= sY; + crop.fBottom *= sY; + decoder.setCropRect(&crop); + } + + SkImageInfo bitmapInfo = decoder.getOutputInfo(); + + SkBitmap bm; + if (!bm.setInfo(bitmapInfo)) { + ALOGE("Failed to setInfo properly"); + return SkCodec::kInternalError; + } + + // TODO: We don't currently parcel the gainmap, but if we should then also support + // the shared allocator + sk_sp nativeBitmap = Bitmap::allocateHeapBitmap(&bm); + if (!nativeBitmap) { + ALOGE("OOM allocating Bitmap with dimensions %i x %i", bitmapInfo.width(), + bitmapInfo.height()); + return SkCodec::kInternalError; + } + + SkCodec::Result result = decoder.decode(bm.getPixels(), bm.rowBytes()); + bm.setImmutable(); + + if (result == SkCodec::kSuccess) { + auto gainmap = sp::make(); + gainmap->info = gainmapInfo; + gainmap->bitmap = std::move(nativeBitmap); + destination->setGainmap(std::move(gainmap)); + } + + return result; +} diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h index b6d73b39d8d0..97573e1e8207 100644 --- a/libs/hwui/hwui/ImageDecoder.h +++ b/libs/hwui/hwui/ImageDecoder.h @@ -79,6 +79,8 @@ public: // Set whether the ImageDecoder should handle RestorePrevious frames. void setHandleRestorePrevious(bool handle); + SkCodec::Result extractGainmap(Bitmap* destination); + private: // State machine for keeping track of how to handle RestorePrevious (RP) // frames in decode(). @@ -115,6 +117,7 @@ private: RestoreState mRestoreState; sk_sp mRestoreFrame; std::optional mCropRect; + std::optional mOverrideOrigin; ImageDecoder(const ImageDecoder&) = delete; ImageDecoder& operator=(const ImageDecoder&) = delete; @@ -124,6 +127,10 @@ private: bool swapWidthHeight() const; // Store/restore a frame if necessary. Returns false on error. bool handleRestorePrevious(const SkImageInfo&, void* pixels, size_t rowBytes); + + SkEncodedOrigin getOrigin() const { + return mOverrideOrigin.has_value() ? *mOverrideOrigin : mCodec->codec()->getOrigin(); + } }; } // namespace android diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp old mode 100755 new mode 100644 index c68a6b9962c2..10c287d1e07d --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -6,6 +6,7 @@ #include #include "CreateJavaOutputStreamAdaptor.h" +#include "Gainmap.h" #include "GraphicsJNI.h" #include "HardwareBufferHelpers.h" #include "SkBitmap.h" @@ -47,6 +48,8 @@ static jmethodID gBitmap_reinitMethodID; namespace android { +jobject Gainmap_extractFromBitmap(JNIEnv* env, const Bitmap& bitmap); + class BitmapWrapper { public: explicit BitmapWrapper(Bitmap* bitmap) @@ -1251,68 +1254,77 @@ static void Bitmap_setImmutable(JNIEnv* env, jobject, jlong bitmapHandle) { return bitmapHolder->bitmap().setImmutable(); } +static jboolean Bitmap_hasGainmap(CRITICAL_JNI_PARAMS_COMMA jlong bitmapHandle) { + LocalScopedBitmap bitmapHolder(bitmapHandle); + if (!bitmapHolder.valid()) return false; + + return bitmapHolder->bitmap().hasGainmap(); +} + +static jobject Bitmap_extractGainmap(JNIEnv* env, jobject, jlong bitmapHandle) { + LocalScopedBitmap bitmapHolder(bitmapHandle); + if (!bitmapHolder.valid()) return nullptr; + if (!bitmapHolder->bitmap().hasGainmap()) return nullptr; + + return Gainmap_extractFromBitmap(env, bitmapHolder->bitmap()); +} + /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gBitmapMethods[] = { - { "nativeCreate", "([IIIIIIZJ)Landroid/graphics/Bitmap;", - (void*)Bitmap_creator }, - { "nativeCopy", "(JIZ)Landroid/graphics/Bitmap;", - (void*)Bitmap_copy }, - { "nativeCopyAshmem", "(J)Landroid/graphics/Bitmap;", - (void*)Bitmap_copyAshmem }, - { "nativeCopyAshmemConfig", "(JI)Landroid/graphics/Bitmap;", - (void*)Bitmap_copyAshmemConfig }, - { "nativeGetAshmemFD", "(J)I", (void*)Bitmap_getAshmemFd }, - { "nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer }, - { "nativeRecycle", "(J)V", (void*)Bitmap_recycle }, - { "nativeReconfigure", "(JIIIZ)V", (void*)Bitmap_reconfigure }, - { "nativeCompress", "(JIILjava/io/OutputStream;[B)Z", - (void*)Bitmap_compress }, - { "nativeErase", "(JI)V", (void*)Bitmap_erase }, - { "nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong }, - { "nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes }, - { "nativeConfig", "(J)I", (void*)Bitmap_config }, - { "nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha }, - { "nativeIsPremultiplied", "(J)Z", (void*)Bitmap_isPremultiplied}, - { "nativeSetHasAlpha", "(JZZ)V", (void*)Bitmap_setHasAlpha}, - { "nativeSetPremultiplied", "(JZ)V", (void*)Bitmap_setPremultiplied}, - { "nativeHasMipMap", "(J)Z", (void*)Bitmap_hasMipMap }, - { "nativeSetHasMipMap", "(JZ)V", (void*)Bitmap_setHasMipMap }, - { "nativeCreateFromParcel", - "(Landroid/os/Parcel;)Landroid/graphics/Bitmap;", - (void*)Bitmap_createFromParcel }, - { "nativeWriteToParcel", "(JILandroid/os/Parcel;)Z", - (void*)Bitmap_writeToParcel }, - { "nativeExtractAlpha", "(JJ[I)Landroid/graphics/Bitmap;", - (void*)Bitmap_extractAlpha }, - { "nativeGenerationId", "(J)I", (void*)Bitmap_getGenerationId }, - { "nativeGetPixel", "(JII)I", (void*)Bitmap_getPixel }, - { "nativeGetColor", "(JII)J", (void*)Bitmap_getColor }, - { "nativeGetPixels", "(J[IIIIIII)V", (void*)Bitmap_getPixels }, - { "nativeSetPixel", "(JIII)V", (void*)Bitmap_setPixel }, - { "nativeSetPixels", "(J[IIIIIII)V", (void*)Bitmap_setPixels }, - { "nativeCopyPixelsToBuffer", "(JLjava/nio/Buffer;)V", - (void*)Bitmap_copyPixelsToBuffer }, - { "nativeCopyPixelsFromBuffer", "(JLjava/nio/Buffer;)V", - (void*)Bitmap_copyPixelsFromBuffer }, - { "nativeSameAs", "(JJ)Z", (void*)Bitmap_sameAs }, - { "nativePrepareToDraw", "(J)V", (void*)Bitmap_prepareToDraw }, - { "nativeGetAllocationByteCount", "(J)I", (void*)Bitmap_getAllocationByteCount }, - { "nativeCopyPreserveInternalConfig", "(J)Landroid/graphics/Bitmap;", - (void*)Bitmap_copyPreserveInternalConfig }, - { "nativeWrapHardwareBufferBitmap", "(Landroid/hardware/HardwareBuffer;J)Landroid/graphics/Bitmap;", - (void*) Bitmap_wrapHardwareBufferBitmap }, - { "nativeGetHardwareBuffer", "(J)Landroid/hardware/HardwareBuffer;", - (void*) Bitmap_getHardwareBuffer }, - { "nativeComputeColorSpace", "(J)Landroid/graphics/ColorSpace;", (void*)Bitmap_computeColorSpace }, - { "nativeSetColorSpace", "(JJ)V", (void*)Bitmap_setColorSpace }, - { "nativeIsSRGB", "(J)Z", (void*)Bitmap_isSRGB }, - { "nativeIsSRGBLinear", "(J)Z", (void*)Bitmap_isSRGBLinear}, - { "nativeSetImmutable", "(J)V", (void*)Bitmap_setImmutable}, - - // ------------ @CriticalNative ---------------- - { "nativeIsImmutable", "(J)Z", (void*)Bitmap_isImmutable}, - { "nativeIsBackedByAshmem", "(J)Z", (void*)Bitmap_isBackedByAshmem} + {"nativeCreate", "([IIIIIIZJ)Landroid/graphics/Bitmap;", (void*)Bitmap_creator}, + {"nativeCopy", "(JIZ)Landroid/graphics/Bitmap;", (void*)Bitmap_copy}, + {"nativeCopyAshmem", "(J)Landroid/graphics/Bitmap;", (void*)Bitmap_copyAshmem}, + {"nativeCopyAshmemConfig", "(JI)Landroid/graphics/Bitmap;", (void*)Bitmap_copyAshmemConfig}, + {"nativeGetAshmemFD", "(J)I", (void*)Bitmap_getAshmemFd}, + {"nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer}, + {"nativeRecycle", "(J)V", (void*)Bitmap_recycle}, + {"nativeReconfigure", "(JIIIZ)V", (void*)Bitmap_reconfigure}, + {"nativeCompress", "(JIILjava/io/OutputStream;[B)Z", (void*)Bitmap_compress}, + {"nativeErase", "(JI)V", (void*)Bitmap_erase}, + {"nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong}, + {"nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes}, + {"nativeConfig", "(J)I", (void*)Bitmap_config}, + {"nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha}, + {"nativeIsPremultiplied", "(J)Z", (void*)Bitmap_isPremultiplied}, + {"nativeSetHasAlpha", "(JZZ)V", (void*)Bitmap_setHasAlpha}, + {"nativeSetPremultiplied", "(JZ)V", (void*)Bitmap_setPremultiplied}, + {"nativeHasMipMap", "(J)Z", (void*)Bitmap_hasMipMap}, + {"nativeSetHasMipMap", "(JZ)V", (void*)Bitmap_setHasMipMap}, + {"nativeCreateFromParcel", "(Landroid/os/Parcel;)Landroid/graphics/Bitmap;", + (void*)Bitmap_createFromParcel}, + {"nativeWriteToParcel", "(JILandroid/os/Parcel;)Z", (void*)Bitmap_writeToParcel}, + {"nativeExtractAlpha", "(JJ[I)Landroid/graphics/Bitmap;", (void*)Bitmap_extractAlpha}, + {"nativeGenerationId", "(J)I", (void*)Bitmap_getGenerationId}, + {"nativeGetPixel", "(JII)I", (void*)Bitmap_getPixel}, + {"nativeGetColor", "(JII)J", (void*)Bitmap_getColor}, + {"nativeGetPixels", "(J[IIIIIII)V", (void*)Bitmap_getPixels}, + {"nativeSetPixel", "(JIII)V", (void*)Bitmap_setPixel}, + {"nativeSetPixels", "(J[IIIIIII)V", (void*)Bitmap_setPixels}, + {"nativeCopyPixelsToBuffer", "(JLjava/nio/Buffer;)V", (void*)Bitmap_copyPixelsToBuffer}, + {"nativeCopyPixelsFromBuffer", "(JLjava/nio/Buffer;)V", (void*)Bitmap_copyPixelsFromBuffer}, + {"nativeSameAs", "(JJ)Z", (void*)Bitmap_sameAs}, + {"nativePrepareToDraw", "(J)V", (void*)Bitmap_prepareToDraw}, + {"nativeGetAllocationByteCount", "(J)I", (void*)Bitmap_getAllocationByteCount}, + {"nativeCopyPreserveInternalConfig", "(J)Landroid/graphics/Bitmap;", + (void*)Bitmap_copyPreserveInternalConfig}, + {"nativeWrapHardwareBufferBitmap", + "(Landroid/hardware/HardwareBuffer;J)Landroid/graphics/Bitmap;", + (void*)Bitmap_wrapHardwareBufferBitmap}, + {"nativeGetHardwareBuffer", "(J)Landroid/hardware/HardwareBuffer;", + (void*)Bitmap_getHardwareBuffer}, + {"nativeComputeColorSpace", "(J)Landroid/graphics/ColorSpace;", + (void*)Bitmap_computeColorSpace}, + {"nativeSetColorSpace", "(JJ)V", (void*)Bitmap_setColorSpace}, + {"nativeIsSRGB", "(J)Z", (void*)Bitmap_isSRGB}, + {"nativeIsSRGBLinear", "(J)Z", (void*)Bitmap_isSRGBLinear}, + {"nativeSetImmutable", "(J)V", (void*)Bitmap_setImmutable}, + {"nativeExtractGainmap", "(J)Landroid/graphics/Gainmap;", (void*)Bitmap_extractGainmap}, + + // ------------ @CriticalNative ---------------- + {"nativeIsImmutable", "(J)Z", (void*)Bitmap_isImmutable}, + {"nativeIsBackedByAshmem", "(J)Z", (void*)Bitmap_isBackedByAshmem}, + {"nativeHasGainmap", "(J)Z", (void*)Bitmap_hasGainmap}, }; diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp new file mode 100644 index 000000000000..f2efbc7b833e --- /dev/null +++ b/libs/hwui/jni/Gainmap.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "Bitmap.h" +#include "GraphicsJNI.h" +#include "graphics_jni_helpers.h" + +namespace android { + +static jclass gGainmap_class; +static jmethodID gGainmap_constructorMethodID; + +using namespace uirenderer; + +static Gainmap* fromJava(jlong gainmap) { + return reinterpret_cast(gainmap); +} + +static int getCreateFlags(const sk_sp& bitmap) { + int flags = 0; + if (bitmap->info().alphaType() == kPremul_SkAlphaType) { + flags |= android::bitmap::kBitmapCreateFlag_Premultiplied; + } + if (!bitmap->isImmutable()) { + flags |= android::bitmap::kBitmapCreateFlag_Mutable; + } + return flags; +} + +jobject Gainmap_extractFromBitmap(JNIEnv* env, const Bitmap& bitmap) { + auto gainmap = bitmap.gainmap(); + jobject jGainmapImage; + size_t allocationSize; + + { + // Scope to guard the release of nativeBitmap + auto nativeBitmap = gainmap->bitmap; + const int createFlags = getCreateFlags(nativeBitmap); + allocationSize = nativeBitmap->getAllocationByteCount(); + jGainmapImage = bitmap::createBitmap(env, nativeBitmap.release(), createFlags); + } + + // Grab a ref for the jobject + gainmap->incStrong(0); + jobject obj = env->NewObject(gGainmap_class, gGainmap_constructorMethodID, jGainmapImage, + gainmap.get(), allocationSize + sizeof(Gainmap), true); + + if (env->ExceptionCheck() != 0) { + // sadtrombone + gainmap->decStrong(0); + ALOGE("*** Uncaught exception returned from Java call!\n"); + env->ExceptionDescribe(); + } + return obj; +} + +static void Gainmap_destructor(Gainmap* gainmap) { + gainmap->decStrong(0); +} + +static jlong Gainmap_getNativeFinalizer(JNIEnv*, jobject) { + return static_cast(reinterpret_cast(&Gainmap_destructor)); +} + +static void Gainmap_setGainmapMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, + jfloat b) { + fromJava(gainmapPtr)->info.fLogRatioMax = {r, g, b, 1.f}; +} + +static void Gainmap_getGainmapMax(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) { + const auto ratioMax = fromJava(gainmapPtr)->info.fLogRatioMax; + jfloat buf[3]{ratioMax.fR, ratioMax.fG, ratioMax.fB}; + env->SetFloatArrayRegion(components, 0, 3, buf); +} + +static void Gainmap_setHdrRatioMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat max) { + fromJava(gainmapPtr)->info.fHdrRatioMax = max; +} + +static jfloat Gainmap_getHdrRatioMax(JNIEnv*, jobject, jlong gainmapPtr) { + return fromJava(gainmapPtr)->info.fHdrRatioMax; +} + +static void Gainmap_setHdrRatioMin(JNIEnv*, jobject, jlong gainmapPtr, jfloat min) { + fromJava(gainmapPtr)->info.fHdrRatioMin = min; +} + +static jfloat Gainmap_getHdrRatioMin(JNIEnv*, jobject, jlong gainmapPtr) { + return fromJava(gainmapPtr)->info.fHdrRatioMin; +} + +static const JNINativeMethod gGainmapMethods[] = { + {"nGetFinalizer", "()J", (void*)Gainmap_getNativeFinalizer}, + {"nSetGainmapMax", "(JFFF)V", (void*)Gainmap_setGainmapMax}, + {"nGetGainmapMax", "(J[F)V", (void*)Gainmap_getGainmapMax}, + {"nSetHdrRatioMax", "(JF)V", (void*)Gainmap_setHdrRatioMax}, + {"nGetHdrRatioMax", "(J)F", (void*)Gainmap_getHdrRatioMax}, + {"nSetHdrRatioMin", "(JF)V", (void*)Gainmap_setHdrRatioMin}, + {"nGetHdrRatioMin", "(J)F", (void*)Gainmap_getHdrRatioMin}, +}; + +int register_android_graphics_Gainmap(JNIEnv* env) { + gGainmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Gainmap")); + gGainmap_constructorMethodID = + GetMethodIDOrDie(env, gGainmap_class, "", "(Landroid/graphics/Bitmap;JIZ)V"); + return android::RegisterMethodsOrDie(env, "android/graphics/Gainmap", gGainmapMethods, + NELEM(gGainmapMethods)); +} + +} // namespace android diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp index bad710dec274..add62b1f0d6d 100644 --- a/libs/hwui/jni/ImageDecoder.cpp +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -14,20 +14,10 @@ * limitations under the License. */ -#include "Bitmap.h" -#include "BitmapFactory.h" -#include "ByteBufferStreamAdaptor.h" -#include "CreateJavaOutputStreamAdaptor.h" -#include "GraphicsJNI.h" #include "ImageDecoder.h" -#include "NinePatchPeeker.h" -#include "Utils.h" - -#include -#include -#include #include +#include #include #include #include @@ -35,11 +25,22 @@ #include #include #include - #include #include +#include +#include +#include #include +#include "Bitmap.h" +#include "BitmapFactory.h" +#include "ByteBufferStreamAdaptor.h" +#include "CreateJavaOutputStreamAdaptor.h" +#include "Gainmap.h" +#include "GraphicsJNI.h" +#include "NinePatchPeeker.h" +#include "Utils.h" + using namespace android; static jclass gImageDecoder_class; @@ -246,6 +247,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong jboolean requireUnpremul, jboolean preferRamOverQuality, jboolean asAlphaMask, jlong colorSpaceHandle, jboolean extended) { + ATRACE_CALL(); auto* decoder = reinterpret_cast(nativePtr); if (!decoder->setTargetSize(targetWidth, targetHeight)) { doThrowISE(env, "Could not scale to target size!"); @@ -336,10 +338,21 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong return nullptr; } + ATRACE_FORMAT("Decoding %dx%d bitmap", bitmapInfo.width(), bitmapInfo.height()); SkCodec::Result result = decoder->decode(bm.getPixels(), bm.rowBytes()); jthrowable jexception = get_and_clear_exception(env); - int onPartialImageError = jexception ? kSourceException - : 0; // No error. + int onPartialImageError = jexception ? kSourceException : 0; // No error. + + // Only attempt to extract the gainmap if we're not post-processing, as we can't automatically + // mimic that to the gainmap and expect it to be meaningful. And also don't extract the gainmap + // if we're prioritizing RAM over quality, since the gainmap improves quality at the + // cost of RAM + if (result == SkCodec::kSuccess && !jpostProcess && !preferRamOverQuality) { + // The gainmap costs RAM to improve quality, so skip this if we're prioritizing RAM instead + result = decoder->extractGainmap(nativeBitmap.get()); + jexception = get_and_clear_exception(env); + } + switch (result) { case SkCodec::kSuccess: // Ignore the exception, since the decode was successful anyway. @@ -450,6 +463,10 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong sk_sp hwBitmap = Bitmap::allocateHardwareBitmap(bm); if (hwBitmap) { hwBitmap->setImmutable(); + if (nativeBitmap->hasGainmap()) { + // TODO: Also convert to a HW gainmap image + hwBitmap->setGainmap(nativeBitmap->gainmap()); + } return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets); } -- cgit v1.2.3-59-g8ed1b From 55887762f3ecf61771cb57c6a3395a473158f42f Mon Sep 17 00:00:00 2001 From: John Reck Date: Wed, 25 Jan 2023 16:51:18 -0500 Subject: Add extended brightness plumbing to VRI Test: manual, builds & boots Bug: 266628247 Change-Id: I6310883f3d10bb3eefa3cc189938b6c2c1a14544 --- core/java/android/view/ThreadedRenderer.java | 13 ++---- core/java/android/view/ViewRootImpl.java | 48 +++++++++++++++------- .../java/android/graphics/HardwareRenderer.java | 19 +++++++-- libs/hwui/ColorMode.h | 4 +- .../hwui/jni/android_graphics_HardwareRenderer.cpp | 16 ++++++-- libs/hwui/pipeline/skia/SkiaPipeline.cpp | 19 ++++++--- libs/hwui/pipeline/skia/SkiaPipeline.h | 3 ++ libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp | 7 ++++ libs/hwui/pipeline/skia/SkiaVulkanPipeline.h | 2 + libs/hwui/renderthread/CanvasContext.cpp | 37 +++++++++++++++-- libs/hwui/renderthread/CanvasContext.h | 7 +++- libs/hwui/renderthread/DrawFrameTask.cpp | 1 + libs/hwui/renderthread/DrawFrameTask.h | 3 ++ libs/hwui/renderthread/EglManager.cpp | 13 +++--- libs/hwui/renderthread/IRenderPipeline.h | 3 ++ libs/hwui/renderthread/RenderProxy.cpp | 16 +++++++- libs/hwui/renderthread/RenderProxy.h | 3 +- libs/hwui/renderthread/VulkanSurface.cpp | 36 +++++++++++++++- libs/hwui/renderthread/VulkanSurface.h | 3 ++ libs/hwui/utils/Color.cpp | 41 ++++++++++++++++++ libs/hwui/utils/Color.h | 2 + 21 files changed, 239 insertions(+), 57 deletions(-) (limited to 'graphics/java/android') diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 9c6e823b549e..2db2132ed354 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -572,17 +572,10 @@ public final class ThreadedRenderer extends HardwareRenderer { } @Override - public void setSurfaceControl(@Nullable SurfaceControl surfaceControl) { - super.setSurfaceControl(surfaceControl); + public void setSurfaceControl(@Nullable SurfaceControl surfaceControl, + @Nullable BLASTBufferQueue blastBufferQueue) { + super.setSurfaceControl(surfaceControl, blastBufferQueue); mWebViewOverlayProvider.setSurfaceControl(surfaceControl); - updateWebViewOverlayCallbacks(); - } - - /** - * Sets the BLASTBufferQueue being used for rendering. This is required to be specified - * for WebView overlay support - */ - public void setBlastBufferQueue(@Nullable BLASTBufferQueue blastBufferQueue) { mWebViewOverlayProvider.setBLASTBufferQueue(blastBufferQueue); updateWebViewOverlayCallbacks(); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 84ed845d62f8..81b20a49dd1e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -683,6 +683,10 @@ public final class ViewRootImpl implements ViewParent, private BLASTBufferQueue mBlastBufferQueue; + private boolean mUpdateSdrHdrRatioInfo = false; + private float mDesiredSdrHdrRatio = 1f; + private float mRenderSdrHdrRatio = 1f; + /** * Child container layer of {@code mSurface} with the same bounds as its parent, and cropped to * the surface insets. This surface is created only if a client requests it via {@link @@ -1627,18 +1631,16 @@ public final class ViewRootImpl implements ViewParent, final boolean hasSurfaceInsets = insets.left != 0 || insets.right != 0 || insets.top != 0 || insets.bottom != 0; final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets; - mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent, + final ThreadedRenderer renderer = ThreadedRenderer.create(mContext, translucent, attrs.getTitle().toString()); + mAttachInfo.mThreadedRenderer = renderer; + renderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue); updateColorModeIfNeeded(attrs.getColorMode()); updateForceDarkMode(); - if (mAttachInfo.mThreadedRenderer != null) { - mAttachInfo.mHardwareAccelerated = - mAttachInfo.mHardwareAccelerationRequested = true; - if (mHardwareRendererObserver != null) { - mAttachInfo.mThreadedRenderer.addObserver(mHardwareRendererObserver); - } - mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl); - mAttachInfo.mThreadedRenderer.setBlastBufferQueue(mBlastBufferQueue); + mAttachInfo.mHardwareAccelerated = true; + mAttachInfo.mHardwareAccelerationRequested = true; + if (mHardwareRendererObserver != null) { + renderer.addObserver(mHardwareRendererObserver); } } } @@ -2264,8 +2266,7 @@ public final class ViewRootImpl implements ViewParent, } if (mAttachInfo.mThreadedRenderer != null) { - mAttachInfo.mThreadedRenderer.setSurfaceControl(null); - mAttachInfo.mThreadedRenderer.setBlastBufferQueue(null); + mAttachInfo.mThreadedRenderer.setSurfaceControl(null, null); } } @@ -4828,6 +4829,13 @@ public final class ViewRootImpl implements ViewParent, useAsyncReport = true; + if (mUpdateSdrHdrRatioInfo) { + mUpdateSdrHdrRatioInfo = false; + applyTransactionOnDraw(mTransaction.setExtendedRangeBrightness( + getSurfaceControl(), mRenderSdrHdrRatio, mDesiredSdrHdrRatio)); + mAttachInfo.mThreadedRenderer.setTargetSdrHdrRatio(mRenderSdrHdrRatio); + } + if (forceDraw) { mAttachInfo.mThreadedRenderer.forceDrawNextFrame(); } @@ -5361,7 +5369,20 @@ public final class ViewRootImpl implements ViewParent, && !getConfiguration().isScreenWideColorGamut()) { colorMode = ActivityInfo.COLOR_MODE_DEFAULT; } - mAttachInfo.mThreadedRenderer.setColorMode(colorMode); + float desiredRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode); + if (desiredRatio != mDesiredSdrHdrRatio) { + mDesiredSdrHdrRatio = desiredRatio; + mUpdateSdrHdrRatioInfo = true; + } + } + + /** happylint */ + public void setTargetSdrHdrRatio(float ratio) { + if (mRenderSdrHdrRatio != ratio) { + mRenderSdrHdrRatio = ratio; + mUpdateSdrHdrRatioInfo = true; + invalidate(); + } } @Override @@ -8435,8 +8456,7 @@ public final class ViewRootImpl implements ViewParent, updateBlastSurfaceIfNeeded(); } if (mAttachInfo.mThreadedRenderer != null) { - mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl); - mAttachInfo.mThreadedRenderer.setBlastBufferQueue(mBlastBufferQueue); + mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue); } if (mPreviousTransformHint != transformHint) { mPreviousTransformHint = transformHint; diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index f815a5e91e76..c3eb7aa454ae 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -158,6 +158,7 @@ public class HardwareRenderer { private boolean mOpaque = true; private boolean mForceDark = false; private @ActivityInfo.ColorMode int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT; + private float mDesiredSdrHdrRatio = 1f; /** * Creates a new instance of a HardwareRenderer. The HardwareRenderer will default @@ -319,7 +320,8 @@ public class HardwareRenderer { * @param surfaceControl The surface control to pass to render thread in hwui. * If null, any previous references held in render thread will be discarded. */ - public void setSurfaceControl(@Nullable SurfaceControl surfaceControl) { + public void setSurfaceControl(@Nullable SurfaceControl surfaceControl, + @Nullable BLASTBufferQueue blastBufferQueue) { nSetSurfaceControl(mNativeProxy, surfaceControl != null ? surfaceControl.mNativeObject : 0); } @@ -643,11 +645,12 @@ public class HardwareRenderer { * @param colorMode The @{@link ActivityInfo.ColorMode} to request * @hide */ - public void setColorMode(@ActivityInfo.ColorMode int colorMode) { + public float setColorMode(@ActivityInfo.ColorMode int colorMode) { if (mColorMode != colorMode) { mColorMode = colorMode; - nSetColorMode(mNativeProxy, colorMode); + mDesiredSdrHdrRatio = nSetColorMode(mNativeProxy, colorMode); } + return mDesiredSdrHdrRatio; } /** @@ -663,6 +666,12 @@ public class HardwareRenderer { nSetColorMode(mNativeProxy, colorMode); } + /** @hide */ + public void setTargetSdrHdrRatio(float ratio) { + if (ratio < 1.f || Float.isNaN(ratio) || Float.isInfinite(ratio)) ratio = 1.f; + nSetTargetSdrHdrRatio(mNativeProxy, ratio); + } + /** * Blocks until all previously queued work has completed. * @@ -1451,7 +1460,9 @@ public class HardwareRenderer { private static native void nSetOpaque(long nativeProxy, boolean opaque); - private static native void nSetColorMode(long nativeProxy, int colorMode); + private static native float nSetColorMode(long nativeProxy, int colorMode); + + private static native void nSetTargetSdrHdrRatio(long nativeProxy, float ratio); private static native void nSetSdrWhitePoint(long nativeProxy, float whitePoint); diff --git a/libs/hwui/ColorMode.h b/libs/hwui/ColorMode.h index 3df5c3c9caed..e45db01c0e34 100644 --- a/libs/hwui/ColorMode.h +++ b/libs/hwui/ColorMode.h @@ -25,10 +25,8 @@ enum class ColorMode { // WideColorGamut selects the most optimal colorspace & format for the device's display // Most commonly DisplayP3 + RGBA_8888 currently. WideColorGamut = 1, - // HDR Rec2020 + F16 + // Extended range Display P3 Hdr = 2, - // HDR Rec2020 + 1010102 - Hdr10 = 3, // Alpha 8 A8 = 4, }; diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index 58923089deb0..d6aad7d3eede 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -232,10 +232,16 @@ static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz, proxy->setOpaque(opaque); } -static void android_view_ThreadedRenderer_setColorMode(JNIEnv* env, jobject clazz, - jlong proxyPtr, jint colorMode) { +static jfloat android_view_ThreadedRenderer_setColorMode(JNIEnv* env, jobject clazz, jlong proxyPtr, + jint colorMode) { RenderProxy* proxy = reinterpret_cast(proxyPtr); - proxy->setColorMode(static_cast(colorMode)); + return proxy->setColorMode(static_cast(colorMode)); +} + +static void android_view_ThreadedRenderer_setTargetSdrHdrRatio(JNIEnv* env, jobject clazz, + jlong proxyPtr, jfloat ratio) { + RenderProxy* proxy = reinterpret_cast(proxyPtr); + return proxy->setRenderSdrHdrRatio(ratio); } static void android_view_ThreadedRenderer_setSdrWhitePoint(JNIEnv* env, jobject clazz, @@ -924,7 +930,9 @@ static const JNINativeMethod gMethods[] = { {"nSetLightAlpha", "(JFF)V", (void*)android_view_ThreadedRenderer_setLightAlpha}, {"nSetLightGeometry", "(JFFFF)V", (void*)android_view_ThreadedRenderer_setLightGeometry}, {"nSetOpaque", "(JZ)V", (void*)android_view_ThreadedRenderer_setOpaque}, - {"nSetColorMode", "(JI)V", (void*)android_view_ThreadedRenderer_setColorMode}, + {"nSetColorMode", "(JI)F", (void*)android_view_ThreadedRenderer_setColorMode}, + {"nSetTargetSdrHdrRatio", "(JF)V", + (void*)android_view_ThreadedRenderer_setTargetSdrHdrRatio}, {"nSetSdrWhitePoint", "(JF)V", (void*)android_view_ThreadedRenderer_setSdrWhitePoint}, {"nSetIsHighEndGfx", "(Z)V", (void*)android_view_ThreadedRenderer_setIsHighEndGfx}, {"nSetIsLowRam", "(Z)V", (void*)android_view_ThreadedRenderer_setIsLowRam}, diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 3692f0940b28..2017eb6eb7da 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -642,12 +642,9 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace(); break; case ColorMode::Hdr: - mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType; - mSurfaceColorSpace = SkColorSpace::MakeRGB(GetPQSkTransferFunction(), SkNamedGamut::kRec2020); - break; - case ColorMode::Hdr10: - mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType; - mSurfaceColorSpace = SkColorSpace::MakeRGB(GetPQSkTransferFunction(), SkNamedGamut::kRec2020); + mSurfaceColorType = SkColorType::kN32_SkColorType; + mSurfaceColorSpace = SkColorSpace::MakeRGB( + GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3); break; case ColorMode::A8: mSurfaceColorType = SkColorType::kAlpha_8_SkColorType; @@ -656,6 +653,16 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { } } +void SkiaPipeline::setTargetSdrHdrRatio(float ratio) { + if (mColorMode == ColorMode::Hdr) { + mTargetSdrHdrRatio = ratio; + mSurfaceColorSpace = SkColorSpace::MakeRGB(GetExtendedTransferFunction(mTargetSdrHdrRatio), + SkNamedGamut::kDisplayP3); + } else { + mTargetSdrHdrRatio = 1.f; + } +} + // Overdraw debugging // These colors should be kept in sync with Caches::getOverdrawColor() with a few differences. diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index 4f9334654c9b..befee8989383 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -78,6 +78,8 @@ public: virtual void setHardwareBuffer(AHardwareBuffer* buffer) override; bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; } + void setTargetSdrHdrRatio(float ratio) override; + protected: sk_sp getBufferSkSurface( const renderthread::HardwareBufferRenderParams& bufferParams); @@ -92,6 +94,7 @@ protected: ColorMode mColorMode = ColorMode::Default; SkColorType mSurfaceColorType; sk_sp mSurfaceColorSpace; + float mTargetSdrHdrRatio = 1.f; bool isCapturingSkp() const { return mCaptureMode != CaptureMode::None; } diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index b94b6cf0546a..99298bc0fe9b 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -185,6 +185,13 @@ bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior /*swapB return mVkSurface != nullptr; } +void SkiaVulkanPipeline::setTargetSdrHdrRatio(float ratio) { + SkiaPipeline::setTargetSdrHdrRatio(ratio); + if (mVkSurface) { + mVkSurface->setColorSpace(mSurfaceColorSpace); + } +} + bool SkiaVulkanPipeline::isSurfaceReady() { return CC_UNLIKELY(mVkSurface != nullptr); } diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 2c7b268e8174..d921ddb0d0fb 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -53,6 +53,8 @@ public: void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; + bool supportsExtendedRangeHdr() const override { return true; } + void setTargetSdrHdrRatio(float ratio) override; static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor); static sk_sp allocateHardwareBitmap(renderthread::RenderThread& thread, diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 8dea6845c9f8..0cc68cc00710 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -299,9 +299,40 @@ void CanvasContext::setOpaque(bool opaque) { mOpaque = opaque; } -void CanvasContext::setColorMode(ColorMode mode) { - mRenderPipeline->setSurfaceColorProperties(mode); - setupPipelineSurface(); +float CanvasContext::setColorMode(ColorMode mode) { + if (mode != mColorMode) { + if (mode == ColorMode::Hdr && !mRenderPipeline->supportsExtendedRangeHdr()) { + mode = ColorMode::WideColorGamut; + } + mColorMode = mode; + mRenderPipeline->setSurfaceColorProperties(mode); + setupPipelineSurface(); + } + switch (mColorMode) { + case ColorMode::Hdr: + return 3.f; // TODO: Refine this number + default: + return 1.f; + } +} + +float CanvasContext::targetSdrHdrRatio() const { + if (mColorMode == ColorMode::Hdr) { + return mTargetSdrHdrRatio; + } else { + return 1.f; + } +} + +void CanvasContext::setTargetSdrHdrRatio(float ratio) { + if (mTargetSdrHdrRatio == ratio) return; + + mTargetSdrHdrRatio = ratio; + mRenderPipeline->setTargetSdrHdrRatio(ratio); + // We don't actually but we need to behave as if we do. Specifically we need to ensure + // all buffers in the swapchain are fully re-rendered as any partial updates to them will + // result in mixed target white points which looks really bad & flickery + mHaveNewSurface = true; } bool CanvasContext::makeCurrent() { diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index a274d2f2377f..a811670e8176 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -137,7 +137,9 @@ public: void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); void setLightGeometry(const Vector3& lightCenter, float lightRadius); void setOpaque(bool opaque); - void setColorMode(ColorMode mode); + float setColorMode(ColorMode mode); + float targetSdrHdrRatio() const; + void setTargetSdrHdrRatio(float ratio); bool makeCurrent(); void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued, RenderNode* target); // Returns the DequeueBufferDuration. @@ -352,6 +354,9 @@ private: nsecs_t mLastDequeueBufferDuration = 0; nsecs_t mSyncDelayDuration = 0; nsecs_t mIdleDuration = 0; + + ColorMode mColorMode = ColorMode::Default; + float mTargetSdrHdrRatio = 1.f; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index b06c5dd9ad36..fab2f46e91c3 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -91,6 +91,7 @@ void DrawFrameTask::run() { ATRACE_FORMAT("DrawFrames %" PRId64, vsyncId); mContext->setSyncDelayDuration(systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued); + mContext->setTargetSdrHdrRatio(mRenderSdrHdrRatio); auto hardwareBufferParams = mHardwareBufferParams; mContext->setHardwareBufferRenderParams(hardwareBufferParams); diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index c5c5fe280743..4130d4abe09e 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -100,6 +100,8 @@ public: mHardwareBufferParams = params; } + void setRenderSdrHdrRatio(float ratio) { mRenderSdrHdrRatio = ratio; } + private: void postAndWait(); bool syncFrameState(TreeInfo& info); @@ -112,6 +114,7 @@ private: CanvasContext* mContext; RenderNode* mTargetNode = nullptr; Rect mContentDrawBounds; + float mRenderSdrHdrRatio = 1.f; /********************************************* * Single frame data diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 02257db9df6a..5b7cf7538bd7 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -450,6 +450,11 @@ Result EglManager::createSurface(EGLNativeWindowType window, case ColorMode::Default: attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR; break; + // Extended Range HDR requires being able to manipulate the dataspace in ways + // we cannot easily do while going through EGLSurface. Given this requires + // composer3 support, just treat HDR as equivalent to wide color gamut if + // the GLES path is still being hit + case ColorMode::Hdr: case ColorMode::WideColorGamut: { skcms_Matrix3x3 colorGamut; LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut), @@ -466,14 +471,6 @@ Result EglManager::createSurface(EGLNativeWindowType window, } break; } - case ColorMode::Hdr: - config = mEglConfigF16; - attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT; - break; - case ColorMode::Hdr10: - config = mEglConfig1010102; - attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT; - break; case ColorMode::A8: LOG_ALWAYS_FATAL("Unreachable: A8 doesn't use a color space"); break; diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index 715c17dfc895..c68fcdfc76f2 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -95,6 +95,9 @@ public: virtual void setPictureCapturedCallback( const std::function&&)>& callback) = 0; + virtual bool supportsExtendedRangeHdr() const { return false; } + virtual void setTargetSdrHdrRatio(float ratio) = 0; + virtual ~IRenderPipeline() {} }; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 5edb0b1dd818..1e011c231343 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -143,8 +143,20 @@ void RenderProxy::setOpaque(bool opaque) { mRenderThread.queue().post([=]() { mContext->setOpaque(opaque); }); } -void RenderProxy::setColorMode(ColorMode mode) { - mRenderThread.queue().post([=]() { mContext->setColorMode(mode); }); +float RenderProxy::setColorMode(ColorMode mode) { + // We only need to figure out what the renderer supports for HDR, otherwise this can stay + // an async call since we already know the return value + if (mode == ColorMode::Hdr) { + return mRenderThread.queue().runSync( + [=]() -> float { return mContext->setColorMode(mode); }); + } else { + mRenderThread.queue().post([=]() { mContext->setColorMode(mode); }); + return 1.f; + } +} + +void RenderProxy::setRenderSdrHdrRatio(float ratio) { + mDrawFrameTask.setRenderSdrHdrRatio(ratio); } int64_t* RenderProxy::frameInfo() { diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 2aafe76f94f9..82072a6e2499 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -87,7 +87,8 @@ public: void setLightGeometry(const Vector3& lightCenter, float lightRadius); void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params); void setOpaque(bool opaque); - void setColorMode(ColorMode mode); + float setColorMode(ColorMode mode); + void setRenderSdrHdrRatio(float ratio); int64_t* frameInfo(); void forceDrawNextFrame(); int syncAndDrawFrame(); diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp index 7dd3561cb220..2efa5d691ca5 100644 --- a/libs/hwui/renderthread/VulkanSurface.cpp +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -199,7 +199,14 @@ bool VulkanSurface::InitializeWindowInfoStruct(ANativeWindow* window, ColorMode outWindowInfo->bufferFormat = ColorTypeToBufferFormat(colorType); outWindowInfo->colorspace = colorSpace; - outWindowInfo->dataspace = ColorSpaceToADataSpace(colorSpace.get(), colorType); + outWindowInfo->colorMode = colorMode; + + if (colorMode == ColorMode::Hdr) { + outWindowInfo->dataspace = + static_cast(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED); + } else { + outWindowInfo->dataspace = ColorSpaceToADataSpace(colorSpace.get(), colorType); + } LOG_ALWAYS_FATAL_IF( outWindowInfo->dataspace == HAL_DATASPACE_UNKNOWN && colorType != kAlpha_8_SkColorType, "Unsupported colorspace"); @@ -496,6 +503,33 @@ int VulkanSurface::getCurrentBuffersAge() { return currentBuffer.hasValidContents ? (mPresentCount - currentBuffer.lastPresentedCount) : 0; } +void VulkanSurface::setColorSpace(sk_sp colorSpace) { + mWindowInfo.colorspace = std::move(colorSpace); + for (int i = 0; i < kNumBufferSlots; i++) { + mNativeBuffers[i].skSurface.reset(); + } + + if (mWindowInfo.colorMode == ColorMode::Hdr) { + mWindowInfo.dataspace = + static_cast(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED); + } else { + mWindowInfo.dataspace = ColorSpaceToADataSpace( + mWindowInfo.colorspace.get(), BufferFormatToColorType(mWindowInfo.bufferFormat)); + } + LOG_ALWAYS_FATAL_IF(mWindowInfo.dataspace == HAL_DATASPACE_UNKNOWN && + mWindowInfo.bufferFormat != AHARDWAREBUFFER_FORMAT_R8_UNORM, + "Unsupported colorspace"); + + if (mNativeWindow) { + int err = native_window_set_buffers_data_space(mNativeWindow.get(), mWindowInfo.dataspace); + if (err != 0) { + ALOGE("VulkanSurface::setColorSpace() native_window_set_buffers_data_space(%d) " + "failed: %s (%d)", + mWindowInfo.dataspace, strerror(-err), err); + } + } +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h index 26486669e712..e2ddc6b07768 100644 --- a/libs/hwui/renderthread/VulkanSurface.h +++ b/libs/hwui/renderthread/VulkanSurface.h @@ -46,6 +46,8 @@ public: } const SkMatrix& getCurrentPreTransform() { return mWindowInfo.preTransform; } + void setColorSpace(sk_sp colorSpace); + private: /* * All structs/methods in this private section are specifically for use by the VulkanManager @@ -94,6 +96,7 @@ private: uint32_t bufferFormat; android_dataspace dataspace; sk_sp colorspace; + ColorMode colorMode; int transform; size_t bufferCount; uint64_t windowUsageFlags; diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index 3afb419f9b8b..230c7f92fbf7 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -101,6 +101,26 @@ uint32_t ColorTypeToBufferFormat(SkColorType colorType) { return AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; } } + +SkColorType BufferFormatToColorType(uint32_t format) { + switch (format) { + case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM: + return kN32_SkColorType; + case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM: + return kN32_SkColorType; + case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM: + return kRGB_565_SkColorType; + case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: + return kRGBA_1010102_SkColorType; + case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: + return kRGBA_F16_SkColorType; + case AHARDWAREBUFFER_FORMAT_R8_UNORM: + return kAlpha_8_SkColorType; + default: + ALOGV("Unsupported format: %d, return unknown by default", format); + return kUnknown_SkColorType; + } +} #endif namespace { @@ -396,6 +416,27 @@ skcms_TransferFunction GetPQSkTransferFunction(float sdr_white_level) { return fn; } +static skcms_TransferFunction trfn_apply_gain(const skcms_TransferFunction trfn, float gain) { + float pow_gain_ginv = sk_float_pow(gain, 1 / trfn.g); + skcms_TransferFunction result; + result.g = trfn.g; + result.a = trfn.a * pow_gain_ginv; + result.b = trfn.b * pow_gain_ginv; + result.c = trfn.c * gain; + result.d = trfn.d; + result.e = trfn.e * gain; + result.f = trfn.f * gain; + return result; +} + +skcms_TransferFunction GetExtendedTransferFunction(float sdrHdrRatio) { + if (sdrHdrRatio <= 1.f) { + return SkNamedTransferFn::kSRGB; + } + // Scale the transfer by the sdrHdrRatio + return trfn_apply_gain(SkNamedTransferFn::kSRGB, sdrHdrRatio); +} + // Skia skcms' default HLG maps encoded [0, 1] to linear [1, 12] in order to follow ARIB // but LinearEffect expects a decoded [0, 1] range instead to follow Rec 2100. std::optional GetHLGScaleTransferFunction() { diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index 00f910f45c38..0fd61c7b990b 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -100,6 +100,7 @@ SkImageInfo BufferDescriptionToImageInfo(const AHardwareBuffer_Desc& bufferDesc, sk_sp colorSpace); uint32_t ColorTypeToBufferFormat(SkColorType colorType); +SkColorType BufferFormatToColorType(uint32_t bufferFormat); #endif ANDROID_API sk_sp DataSpaceToColorSpace(android_dataspace dataspace); @@ -129,6 +130,7 @@ struct Lab { Lab sRGBToLab(SkColor color); SkColor LabToSRGB(const Lab& lab, SkAlpha alpha); skcms_TransferFunction GetPQSkTransferFunction(float sdr_white_level = 0.f); +skcms_TransferFunction GetExtendedTransferFunction(float sdrHdrRatio); std::optional GetHLGScaleTransferFunction(); } /* namespace uirenderer */ -- cgit v1.2.3-59-g8ed1b From 9776d3e35718efd9ea08400fe0dbea61dc6386ce Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Fri, 4 Nov 2022 01:32:24 +0000 Subject: Tonemap in RecordingCanvas Intecepts bitmap calls to tonemap whenever the source is HDR (PQ/HLG) and the destination is SDR. Also, fix the following bugs discovered as part of testing: 1. Don't implicitly cast to booleans when extracting transfer functions from a dataspace in hwui's tonemapper. 2. Fix some typos in defining the HLG/PQ transfer functions. Bug: 261088450 Test: New ColorBitmapActivity in HwAccelerationTest Change-Id: I9d9d68fc4f57b999b3c6d4156bef281b4409f37e --- graphics/java/android/graphics/ColorSpace.java | 4 +- libs/hwui/Android.bp | 2 +- libs/hwui/CanvasTransform.h | 2 +- libs/hwui/RecordingCanvas.cpp | 15 +- libs/hwui/Tonemapper.cpp | 17 +- tests/HwAccelerationTest/AndroidManifest.xml | 9 + .../com/android/test/hwui/ColorBitmapActivity.java | 196 +++++++++++++++++++++ 7 files changed, 234 insertions(+), 11 deletions(-) create mode 100644 tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 2427dec169d6..4c669b8c34f5 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -212,9 +212,9 @@ public abstract class ColorSpace { new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4); private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS = new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f, - 0.28466892f, 0.5599107f, 0.0f, -3.0f, true); + 0.28466892f, 0.5599107f, -11 / 12.0f, -3.0f, true); private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS = - new Rgb.TransferParameters(107 / 128.0f, 1.0f, 32 / 2523.0f, + new Rgb.TransferParameters(-107 / 128.0f, 1.0f, 32 / 2523.0f, 2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true); // See static initialization block next to #get(Named) diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 59e4b7acdba7..2245aae29ecc 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -536,6 +536,7 @@ cc_defaults { "RootRenderNode.cpp", "SkiaCanvas.cpp", "SkiaInterpolator.cpp", + "Tonemapper.cpp", "VectorDrawable.cpp", ], @@ -594,7 +595,6 @@ cc_defaults { "ProfileData.cpp", "ProfileDataContainer.cpp", "Readback.cpp", - "Tonemapper.cpp", "TreeInfo.cpp", "WebViewFunctorManager.cpp", "protos/graphicsstats.proto", diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h index c46a2d369974..291f4cf7193b 100644 --- a/libs/hwui/CanvasTransform.h +++ b/libs/hwui/CanvasTransform.h @@ -45,4 +45,4 @@ bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette pale SkColor transformColor(ColorTransform transform, SkColor color); SkColor transformColorInverse(ColorTransform transform, SkColor color); -} // namespace android::uirenderer; \ No newline at end of file +} // namespace android::uirenderer diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 3f21940d35a7..bbe79d922b3f 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -43,6 +43,7 @@ #include "SkRegion.h" #include "SkTextBlob.h" #include "SkVertices.h" +#include "Tonemapper.h" #include "VectorDrawable.h" #include "pipeline/skia/AnimatedDrawables.h" #include "pipeline/skia/FunctorDrawable.h" @@ -334,7 +335,9 @@ struct DrawImage final : Op { SkPaint paint; BitmapPalette palette; void draw(SkCanvas* c, const SkMatrix&) const { - c->drawImage(image.get(), x, y, sampling, &paint); + SkPaint newPaint = paint; + tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint); + c->drawImage(image.get(), x, y, sampling, &newPaint); } }; struct DrawImageRect final : Op { @@ -356,7 +359,9 @@ struct DrawImageRect final : Op { SkCanvas::SrcRectConstraint constraint; BitmapPalette palette; void draw(SkCanvas* c, const SkMatrix&) const { - c->drawImageRect(image.get(), src, dst, sampling, &paint, constraint); + SkPaint newPaint = paint; + tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint); + c->drawImageRect(image.get(), src, dst, sampling, &newPaint, constraint); } }; struct DrawImageLattice final : Op { @@ -389,8 +394,10 @@ struct DrawImageLattice final : Op { auto flags = (0 == fs) ? nullptr : pod( this, (xs + ys) * sizeof(int) + fs * sizeof(SkColor)); - c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, - filter, &paint); + SkPaint newPaint = paint; + tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint); + c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, filter, + &newPaint); } }; diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp index a7e76b631140..0d39f0e33298 100644 --- a/libs/hwui/Tonemapper.cpp +++ b/libs/hwui/Tonemapper.cpp @@ -18,7 +18,10 @@ #include #include +// libshaders only exists on Android devices +#ifdef __ANDROID__ #include +#endif #include "utils/Color.h" @@ -26,6 +29,8 @@ namespace android::uirenderer { namespace { +// custom tonemapping only exists on Android devices +#ifdef __ANDROID__ class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder { public: explicit ColorFilterRuntimeEffectBuilder(sk_sp effect) @@ -59,20 +64,21 @@ static sk_sp createLinearEffectColorFilter(const shaders::LinearE return effectBuilder.makeColorFilter(); } -static bool extractTransfer(ui::Dataspace dataspace) { - return dataspace & HAL_DATASPACE_TRANSFER_MASK; +static ui::Dataspace extractTransfer(ui::Dataspace dataspace) { + return static_cast(dataspace & HAL_DATASPACE_TRANSFER_MASK); } static bool isHdrDataspace(ui::Dataspace dataspace) { const auto transfer = extractTransfer(dataspace); - return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG; + return transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG; } static ui::Dataspace getDataspace(const SkImageInfo& image) { return static_cast( ColorSpaceToADataSpace(image.colorSpace(), image.colorType())); } +#endif } // namespace @@ -80,6 +86,8 @@ static ui::Dataspace getDataspace(const SkImageInfo& image) { // shader and tag it on the supplied paint. void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits, SkPaint& paint) { +// custom tonemapping only exists on Android devices +#ifdef __ANDROID__ const auto sourceDataspace = getDataspace(source); const auto destinationDataspace = getDataspace(destination); @@ -102,6 +110,9 @@ void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, flo paint.setColorFilter(colorFilter); } } +#else + return; +#endif } } // namespace android::uirenderer diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 616f21cbe1a4..5a3d28a4dfad 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -120,6 +120,15 @@ + + + + + + + diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java new file mode 100644 index 000000000000..017de605fe39 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.hwui; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorSpace; +import android.graphics.HardwareBufferRenderer; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.RenderNode; +import android.graphics.Shader; +import android.hardware.HardwareBuffer; +import android.media.Image; +import android.media.ImageWriter; +import android.os.Bundle; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.Spinner; + +import java.time.Duration; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; + +@SuppressWarnings({"UnusedDeclaration"}) +public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callback, + AdapterView.OnItemSelectedListener { + + private static final int WIDTH = 512; + private static final int HEIGHT = 512; + + private ImageView mImageView; + private SurfaceView mSurfaceView; + private HardwareBuffer mGradientBuffer; + private ImageWriter mImageWriter; + private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + private String[] mColorNames = {"sRGB", "BT2020_HLG", "BT2020_PQ"}; + private String mCurrentColorName = "sRGB"; + + private FutureTask authorGradientBuffer(HardwareBuffer buffer) { + HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer); + RenderNode node = new RenderNode("content"); + node.setPosition(0, 0, buffer.getWidth(), buffer.getHeight()); + + Canvas canvas = node.beginRecording(); + LinearGradient gradient = new LinearGradient( + 0, 0, buffer.getWidth(), buffer.getHeight(), 0xFF000000, + 0xFFFFFFFF, Shader.TileMode.CLAMP); + Paint paint = new Paint(); + paint.setShader(gradient); + canvas.drawRect(0f, 0f, buffer.getWidth(), buffer.getHeight(), paint); + node.endRecording(); + + renderer.setContentRoot(node); + + ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + FutureTask resolvedBuffer = new FutureTask<>(() -> buffer); + renderer.obtainRenderRequest() + .setColorSpace(colorSpace) + .draw(Executors.newSingleThreadExecutor(), result -> { + result.getFence().await(Duration.ofSeconds(3)); + resolvedBuffer.run(); + }); + return resolvedBuffer; + } + + private FutureTask getGradientBuffer() { + HardwareBuffer buffer = HardwareBuffer.create( + WIDTH, HEIGHT, PixelFormat.RGBA_8888, 1, + HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE + | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT); + return authorGradientBuffer(buffer); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + try { + + mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + + ArrayAdapter adapter = new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, mColorNames); + + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + Spinner spinner = new Spinner(this); + spinner.setAdapter(adapter); + spinner.setOnItemSelectedListener(this); + + mGradientBuffer = getGradientBuffer().get(); + + LinearLayout linearLayout = new LinearLayout(this); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + mImageView = new ImageView(this); + + mSurfaceView = new SurfaceView(this); + mSurfaceView.getHolder().addCallback(this); + + linearLayout.addView(spinner, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + linearLayout.addView(mImageView, new LinearLayout.LayoutParams(WIDTH, HEIGHT)); + linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT)); + + setContentView(linearLayout); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private ColorSpace getFromName(String name) { + if (name.equals("sRGB")) { + return ColorSpace.get(ColorSpace.Named.SRGB); + } else if (name.equals("BT2020_HLG")) { + return ColorSpace.get(ColorSpace.Named.BT2020_HLG); + } else if (name.equals("BT2020_PQ")) { + return ColorSpace.get(ColorSpace.Named.BT2020_PQ); + } + + throw new RuntimeException("Unrecognized Colorspace!"); + } + + private void populateBuffers() { + Bitmap bitmap = Bitmap.wrapHardwareBuffer( + mGradientBuffer, ColorSpace.get(ColorSpace.Named.SRGB)); + Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false); + copy.setColorSpace(mColorSpace); + mImageView.setImageBitmap(copy); + + try (Image image = mImageWriter.dequeueInputImage()) { + authorGradientBuffer(image.getHardwareBuffer()).get(); + image.setDataSpace(mColorSpace.getDataSpace()); + mImageWriter.queueInputImage(image); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + mImageWriter = new ImageWriter.Builder(holder.getSurface()) + .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE + | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT + | HardwareBuffer.USAGE_COMPOSER_OVERLAY) + .build(); + populateBuffers(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + mImageWriter.close(); + mImageWriter = null; + } + + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + mCurrentColorName = mColorNames[position]; + mColorSpace = getFromName(mCurrentColorName); + populateBuffers(); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } +} -- cgit v1.2.3-59-g8ed1b From cca989f2b52725468464534f337ee55d01644fb3 Mon Sep 17 00:00:00 2001 From: John Reck Date: Mon, 30 Jan 2023 20:46:20 +0000 Subject: Revert "Created HardwareBufferRenderer to support rendering into..." Revert submission 20579518-hardware_buffer_renderer Reason for revert: The submission timing tracks with a major regression in CtsUiRenderingTestCases stability: https://screenshot.googleplex.com/3TxXCSP4xCZq7Zy.png and also some crash bugs, eg: https://b.corp.google.com/issues/264889058 Reverting to re-stabilize the tree Reverted changes: /q/submissionid:20579518-hardware_buffer_renderer Change-Id: I29f47da097257bdeaa963fccb9ad0dbe39ead063 --- core/api/current.txt | 23 -- core/java/android/hardware/SyncFence.java | 22 +- core/jni/LayoutlibLoader.cpp | 1 - .../android/graphics/HardwareBufferRenderer.java | 390 --------------------- libs/hwui/Android.bp | 2 - libs/hwui/apex/jni_runtime.cpp | 3 - libs/hwui/jni/Bitmap.cpp | 46 ++- libs/hwui/jni/GraphicsJNI.h | 31 +- libs/hwui/jni/HardwareBufferHelpers.cpp | 68 ---- libs/hwui/jni/HardwareBufferHelpers.h | 38 -- libs/hwui/jni/JvmErrorReporter.h | 44 --- .../android_graphics_HardwareBufferRenderer.cpp | 177 ---------- .../hwui/jni/android_graphics_HardwareRenderer.cpp | 65 +++- libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp | 67 +--- libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h | 16 +- libs/hwui/pipeline/skia/SkiaPipeline.cpp | 25 -- libs/hwui/pipeline/skia/SkiaPipeline.h | 10 - libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp | 48 +-- libs/hwui/pipeline/skia/SkiaVulkanPipeline.h | 24 +- libs/hwui/renderthread/CanvasContext.cpp | 20 +- libs/hwui/renderthread/CanvasContext.h | 12 +- libs/hwui/renderthread/DrawFrameTask.cpp | 14 +- libs/hwui/renderthread/DrawFrameTask.h | 13 - .../hwui/renderthread/HardwareBufferRenderParams.h | 62 ---- libs/hwui/renderthread/IRenderPipeline.h | 18 +- libs/hwui/renderthread/RenderProxy.cpp | 16 - libs/hwui/renderthread/RenderProxy.h | 4 +- libs/hwui/tests/unit/CanvasContextTests.cpp | 2 +- tests/HwAccelerationTest/AndroidManifest.xml | 9 - .../test/hwui/HardwareBufferRendererActivity.java | 86 ----- 30 files changed, 157 insertions(+), 1199 deletions(-) delete mode 100644 graphics/java/android/graphics/HardwareBufferRenderer.java delete mode 100644 libs/hwui/jni/HardwareBufferHelpers.cpp delete mode 100644 libs/hwui/jni/HardwareBufferHelpers.h delete mode 100644 libs/hwui/jni/JvmErrorReporter.h delete mode 100644 libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp delete mode 100644 libs/hwui/renderthread/HardwareBufferRenderParams.h delete mode 100644 tests/HwAccelerationTest/src/com/android/test/hwui/HardwareBufferRendererActivity.java (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index bca9913ff94e..eb1c7f5b7f01 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -15310,29 +15310,6 @@ package android.graphics { ctor @Deprecated public EmbossMaskFilter(float[], float, float, float); } - public class HardwareBufferRenderer implements java.lang.AutoCloseable { - ctor public HardwareBufferRenderer(@NonNull android.hardware.HardwareBuffer); - method public void close(); - method public boolean isClosed(); - method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest obtainRenderRequest(); - method public void setContentRoot(@Nullable android.graphics.RenderNode); - method public void setLightSourceAlpha(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float); - method public void setLightSourceGeometry(float, float, @FloatRange(from=0.0f) float, @FloatRange(from=0.0f) float); - } - - public final class HardwareBufferRenderer.RenderRequest { - method public void draw(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); - method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest setBufferTransform(int); - method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest setColorSpace(@Nullable android.graphics.ColorSpace); - } - - public static final class HardwareBufferRenderer.RenderResult { - method @NonNull public android.hardware.SyncFence getFence(); - method public int getStatus(); - field public static final int ERROR_UNKNOWN = 1; // 0x1 - field public static final int SUCCESS = 0; // 0x0 - } - public class HardwareRenderer { ctor public HardwareRenderer(); method public void clearContent(); diff --git a/core/java/android/hardware/SyncFence.java b/core/java/android/hardware/SyncFence.java index d6052cd4c67f..166001347bd4 100644 --- a/core/java/android/hardware/SyncFence.java +++ b/core/java/android/hardware/SyncFence.java @@ -87,8 +87,8 @@ public final class SyncFence implements AutoCloseable, Parcelable { // is well worth making. private final Runnable mCloser; - private SyncFence(int fileDescriptor) { - mNativePtr = nCreate(fileDescriptor); + private SyncFence(@NonNull ParcelFileDescriptor wrapped) { + mNativePtr = nCreate(wrapped.detachFd()); mCloser = sRegistry.registerNativeAllocation(this, mNativePtr); } @@ -136,26 +136,14 @@ public final class SyncFence implements AutoCloseable, Parcelable { } /** - * Create a new SyncFence wrapped around another {@link ParcelFileDescriptor}. By default, all - * method calls are delegated to the wrapped descriptor. This takes ownership of the - * {@link ParcelFileDescriptor}. + * Create a new SyncFence wrapped around another descriptor. By default, all method calls are + * delegated to the wrapped descriptor. * * @param wrapped The descriptor to be wrapped. * @hide */ public static @NonNull SyncFence create(@NonNull ParcelFileDescriptor wrapped) { - return new SyncFence(wrapped.detachFd()); - } - - /** - * Create a new SyncFence wrapped around another descriptor. The returned {@link SyncFence} - * instance takes ownership of the file descriptor. - * - * @param fileDescriptor The descriptor to be wrapped. - * @hide - */ - public static @NonNull SyncFence adopt(int fileDescriptor) { - return new SyncFence(fileDescriptor); + return new SyncFence(wrapped); } /** diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index d7cbf74d421a..93ba23bfdf84 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -102,7 +102,6 @@ extern int register_android_view_KeyCharacterMap(JNIEnv* env); extern int register_android_view_KeyEvent(JNIEnv* env); extern int register_android_view_MotionEvent(JNIEnv* env); extern int register_android_view_ThreadedRenderer(JNIEnv* env); -extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); extern int register_android_view_VelocityTracker(JNIEnv* env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); diff --git a/graphics/java/android/graphics/HardwareBufferRenderer.java b/graphics/java/android/graphics/HardwareBufferRenderer.java deleted file mode 100644 index 361dc594f2c6..000000000000 --- a/graphics/java/android/graphics/HardwareBufferRenderer.java +++ /dev/null @@ -1,390 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.graphics; - -import android.annotation.FloatRange; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.ColorSpace.Named; -import android.hardware.HardwareBuffer; -import android.hardware.SyncFence; -import android.view.SurfaceControl; - -import libcore.util.NativeAllocationRegistry; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.concurrent.Executor; -import java.util.function.Consumer; - -/** - *

    Creates an instance of a hardware-accelerated renderer. This is used to render a scene built - * from {@link RenderNode}s to an output {@link HardwareBuffer}. There can be as many - * HardwareBufferRenderer instances as desired.

    - * - *

    Resources & lifecycle

    - * - *

    All HardwareBufferRenderer and {@link HardwareRenderer} instances share a common render - * thread. Therefore HardwareBufferRenderer will share common resources and GPU utilization with - * hardware accelerated rendering initiated by the UI thread of an application. - * The render thread contains the GPU context & resources necessary to do GPU-accelerated - * rendering. As such, the first HardwareBufferRenderer created comes with the cost of also creating - * the associated GPU contexts, however each incremental HardwareBufferRenderer thereafter is fairly - * cheap. The expected usage is to have a HardwareBufferRenderer instance for every active {@link - * HardwareBuffer}.

    - * - * This is useful in situations where a scene built with {@link RenderNode}s can be consumed - * directly by the system compositor through - * {@link SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}. - * - * HardwareBufferRenderer will never clear contents before each draw invocation so previous contents - * in the {@link HardwareBuffer} target will be preserved across renders. - */ -public class HardwareBufferRenderer implements AutoCloseable { - - private static final ColorSpace DEFAULT_COLORSPACE = ColorSpace.get(Named.SRGB); - - private static class HardwareBufferRendererHolder { - public static final NativeAllocationRegistry REGISTRY = - NativeAllocationRegistry.createMalloced( - HardwareBufferRenderer.class.getClassLoader(), nGetFinalizer()); - } - - private final HardwareBuffer mHardwareBuffer; - private final RenderRequest mRenderRequest; - private final RenderNode mRootNode; - private final Runnable mCleaner; - - private long mProxy; - - /** - * Creates a new instance of {@link HardwareBufferRenderer} with the provided {@link - * HardwareBuffer} as the output of the rendered scene. - */ - public HardwareBufferRenderer(@NonNull HardwareBuffer buffer) { - RenderNode rootNode = RenderNode.adopt(nCreateRootRenderNode()); - rootNode.setClipToBounds(false); - mProxy = nCreateHardwareBufferRenderer(buffer, rootNode.mNativeRenderNode); - mCleaner = HardwareBufferRendererHolder.REGISTRY.registerNativeAllocation(this, mProxy); - mRenderRequest = new RenderRequest(); - mRootNode = rootNode; - mHardwareBuffer = buffer; - } - - /** - * Sets the content root to render. It is not necessary to call this whenever the content - * recording changes. Any mutations to the RenderNode content, or any of the RenderNodes - * contained within the content node, will be applied whenever a new {@link RenderRequest} is - * issued via {@link #obtainRenderRequest()} and {@link RenderRequest#draw(Executor, - * Consumer)}. - * - * @param content The content to set as the root RenderNode. If null the content root is removed - * and the renderer will draw nothing. - */ - public void setContentRoot(@Nullable RenderNode content) { - RecordingCanvas canvas = mRootNode.beginRecording(); - if (content != null) { - canvas.drawRenderNode(content); - } - mRootNode.endRecording(); - } - - /** - * Returns a {@link RenderRequest} that can be used to render into the provided {@link - * HardwareBuffer}. This is used to synchronize the RenderNode content provided by {@link - * #setContentRoot(RenderNode)}. - * - * @return An instance of {@link RenderRequest}. The instance may be reused for every frame, so - * the caller should not hold onto it for longer than a single render request. - */ - @NonNull - public RenderRequest obtainRenderRequest() { - mRenderRequest.reset(); - return mRenderRequest; - } - - /** - * Returns if the {@link HardwareBufferRenderer} has already been closed. That is - * {@link HardwareBufferRenderer#close()} has been invoked. - * @return True if the {@link HardwareBufferRenderer} has been closed, false otherwise. - */ - public boolean isClosed() { - return mProxy == 0L; - } - - /** - * Releases the resources associated with this {@link HardwareBufferRenderer} instance. **Note** - * this does not call {@link HardwareBuffer#close()} on the provided {@link HardwareBuffer} - * instance - */ - @Override - public void close() { - // Note we explicitly call this only here to clean-up potential animator state - // This is not done as part of the NativeAllocationRegistry as it would invoke animator - // callbacks on the wrong thread - nDestroyRootRenderNode(mRootNode.mNativeRenderNode); - if (mProxy != 0L) { - mCleaner.run(); - mProxy = 0L; - } - } - - /** - * Sets the center of the light source. The light source point controls the directionality and - * shape of shadows rendered by RenderNode Z & elevation. - * - *

    The light source should be setup both as part of initial configuration, and whenever - * the window moves to ensure the light source stays anchored in display space instead of in - * window space. - * - *

    This must be set at least once along with {@link #setLightSourceAlpha(float, float)} - * before shadows will work. - * - * @param lightX The X position of the light source. If unsure, a reasonable default - * is 'displayWidth / 2f - windowLeft'. - * @param lightY The Y position of the light source. If unsure, a reasonable default - * is '0 - windowTop' - * @param lightZ The Z position of the light source. Must be >= 0. If unsure, a reasonable - * default is 600dp. - * @param lightRadius The radius of the light source. Smaller radius will have sharper edges, - * larger radius will have softer shadows. If unsure, a reasonable default is 800 dp. - */ - public void setLightSourceGeometry( - float lightX, - float lightY, - @FloatRange(from = 0f) float lightZ, - @FloatRange(from = 0f) float lightRadius - ) { - validateFinite(lightX, "lightX"); - validateFinite(lightY, "lightY"); - validatePositive(lightZ, "lightZ"); - validatePositive(lightRadius, "lightRadius"); - nSetLightGeometry(mProxy, lightX, lightY, lightZ, lightRadius); - } - - /** - * Configures the ambient & spot shadow alphas. This is the alpha used when the shadow has max - * alpha, and ramps down from the values provided to zero. - * - *

    These values are typically provided by the current theme, see - * {@link android.R.attr#spotShadowAlpha} and {@link android.R.attr#ambientShadowAlpha}. - * - *

    This must be set at least once along with - * {@link #setLightSourceGeometry(float, float, float, float)} before shadows will work. - * - * @param ambientShadowAlpha The alpha for the ambient shadow. If unsure, a reasonable default - * is 0.039f. - * @param spotShadowAlpha The alpha for the spot shadow. If unsure, a reasonable default is - * 0.19f. - */ - public void setLightSourceAlpha(@FloatRange(from = 0.0f, to = 1.0f) float ambientShadowAlpha, - @FloatRange(from = 0.0f, to = 1.0f) float spotShadowAlpha) { - validateAlpha(ambientShadowAlpha, "ambientShadowAlpha"); - validateAlpha(spotShadowAlpha, "spotShadowAlpha"); - nSetLightAlpha(mProxy, ambientShadowAlpha, spotShadowAlpha); - } - - /** - * Class that contains data regarding the result of the render request. - * Consumers are to wait on the provided {@link SyncFence} before consuming the HardwareBuffer - * provided to {@link HardwareBufferRenderer} as well as verify that the status returned by - * {@link RenderResult#getStatus()} returns {@link RenderResult#SUCCESS}. - */ - public static final class RenderResult { - - /** - * Render request was completed successfully - */ - public static final int SUCCESS = 0; - - /** - * Render request failed with an unknown error - */ - public static final int ERROR_UNKNOWN = 1; - - /** @hide **/ - @IntDef(value = {SUCCESS, ERROR_UNKNOWN}) - @Retention(RetentionPolicy.SOURCE) - public @interface RenderResultStatus{} - - private final SyncFence mFence; - private final int mResultStatus; - - private RenderResult(@NonNull SyncFence fence, @RenderResultStatus int resultStatus) { - mFence = fence; - mResultStatus = resultStatus; - } - - @NonNull - public SyncFence getFence() { - return mFence; - } - - @RenderResultStatus - public int getStatus() { - return mResultStatus; - } - } - - /** - * Sets the parameters that can be used to control a render request for a {@link - * HardwareBufferRenderer}. This is not thread-safe and must not be held on to for longer than a - * single request. - */ - public final class RenderRequest { - - private ColorSpace mColorSpace = DEFAULT_COLORSPACE; - private int mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY; - - private RenderRequest() { } - - /** - * Syncs the RenderNode tree to the render thread and requests content to be drawn. This - * {@link RenderRequest} instance should no longer be used after calling this method. The - * system internally may reuse instances of {@link RenderRequest} to reduce allocation - * churn. - * - * @param executor Executor used to deliver callbacks - * @param renderCallback Callback invoked when rendering is complete. This includes a - * {@link RenderResult} that provides a {@link SyncFence} that should be waited upon for - * completion before consuming the rendered output in the provided {@link HardwareBuffer} - * instance. - * - * @throws IllegalStateException if attempt to draw is made when - * {@link HardwareBufferRenderer#isClosed()} returns true - */ - public void draw( - @NonNull Executor executor, - @NonNull Consumer renderCallback - ) { - Consumer wrapped = consumable -> executor.execute( - () -> renderCallback.accept(consumable)); - if (!isClosed()) { - nRender( - mProxy, - mTransform, - mHardwareBuffer.getWidth(), - mHardwareBuffer.getHeight(), - mColorSpace.getNativeInstance(), - wrapped); - } else { - throw new IllegalStateException("Attempt to draw with a HardwareBufferRenderer " - + "instance that has already been closed"); - } - } - - private void reset() { - mColorSpace = DEFAULT_COLORSPACE; - mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY; - } - - /** - * Configures the color space which the content should be rendered in. This affects - * how the framework will interpret the color at each pixel. The color space provided here - * must be non-null, RGB based and leverage an ICC parametric curve. The min/max values - * of the components should not reduce the numerical range compared to the previously - * assigned color space. If left unspecified, the default color space of SRGB will be used. - * - * @param colorSpace The color space the content should be rendered in. If null is provided - * the default of SRGB will be used. - */ - @NonNull - public RenderRequest setColorSpace(@Nullable ColorSpace colorSpace) { - if (colorSpace == null) { - mColorSpace = DEFAULT_COLORSPACE; - } else { - mColorSpace = colorSpace; - } - return this; - } - - /** - * Specifies a transform to be applied before content is rendered. This is useful - * for pre-rotating content for the current display orientation to increase performance - * of displaying the associated buffer. This transformation will also adjust the light - * source position for the specified rotation. - * @see SurfaceControl.Transaction#setBufferTransform(SurfaceControl, int) - */ - @NonNull - public RenderRequest setBufferTransform( - @SurfaceControl.BufferTransform int bufferTransform) { - boolean validTransform = bufferTransform == SurfaceControl.BUFFER_TRANSFORM_IDENTITY - || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_90 - || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_180 - || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_270; - if (validTransform) { - mTransform = bufferTransform; - } else { - throw new IllegalArgumentException("Invalid transform provided, must be one of" - + "the SurfaceControl.BufferTransform values"); - } - return this; - } - } - - /** - * @hide - */ - /* package */ - static native int nRender(long renderer, int transform, int width, int height, long colorSpace, - Consumer callback); - - private static native long nCreateRootRenderNode(); - - private static native void nDestroyRootRenderNode(long rootRenderNode); - - private static native long nCreateHardwareBufferRenderer(HardwareBuffer buffer, - long rootRenderNode); - - private static native void nSetLightGeometry(long bufferRenderer, float lightX, float lightY, - float lightZ, float radius); - - private static native void nSetLightAlpha(long nativeProxy, float ambientShadowAlpha, - float spotShadowAlpha); - - private static native long nGetFinalizer(); - - // Called by native - private static void invokeRenderCallback( - @NonNull Consumer callback, - int fd, - int status - ) { - callback.accept(new RenderResult(SyncFence.adopt(fd), status)); - } - - private static void validateAlpha(float alpha, String argumentName) { - if (!(alpha >= 0.0f && alpha <= 1.0f)) { - throw new IllegalArgumentException(argumentName + " must be a valid alpha, " - + alpha + " is not in the range of 0.0f to 1.0f"); - } - } - - private static void validateFinite(float f, String argumentName) { - if (!Float.isFinite(f)) { - throw new IllegalArgumentException(argumentName + " must be finite, given=" + f); - } - } - - private static void validatePositive(float f, String argumentName) { - if (!(Float.isFinite(f) && f >= 0.0f)) { - throw new IllegalArgumentException(argumentName - + " must be a finite positive, given=" + f); - } - } -} diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index dd6c2bc326fd..d116ed828527 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -338,7 +338,6 @@ cc_defaults { "jni/android_util_PathParser.cpp", "jni/Bitmap.cpp", - "jni/HardwareBufferHelpers.cpp", "jni/BitmapFactory.cpp", "jni/ByteBufferStreamAdaptor.cpp", "jni/Camera.cpp", @@ -415,7 +414,6 @@ cc_defaults { "jni/AnimatedImageDrawable.cpp", "jni/android_graphics_TextureLayer.cpp", "jni/android_graphics_HardwareRenderer.cpp", - "jni/android_graphics_HardwareBufferRenderer.cpp", "jni/BitmapRegionDecoder.cpp", "jni/GIFMovie.cpp", "jni/GraphicsStatsService.cpp", diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp index c509ed4dfd21..d07fc5d36ca5 100644 --- a/libs/hwui/apex/jni_runtime.cpp +++ b/libs/hwui/apex/jni_runtime.cpp @@ -84,7 +84,6 @@ extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_view_DisplayListCanvas(JNIEnv* env); extern int register_android_view_RenderNode(JNIEnv* env); extern int register_android_view_ThreadedRenderer(JNIEnv* env); -extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); #ifdef NDEBUG #define REG_JNI(name) { name } @@ -154,8 +153,6 @@ extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); REG_JNI(register_android_util_PathParser), REG_JNI(register_android_view_RenderNode), REG_JNI(register_android_view_DisplayListCanvas), - REG_JNI(register_android_graphics_HardwareBufferRenderer), - REG_JNI(register_android_view_ThreadedRenderer), }; diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index 10c287d1e07d..1f5a1d3362c7 100644 --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -2,42 +2,49 @@ #define LOG_TAG "Bitmap" #include "Bitmap.h" -#include -#include - -#include "CreateJavaOutputStreamAdaptor.h" #include "Gainmap.h" #include "GraphicsJNI.h" -#include "HardwareBufferHelpers.h" #include "SkBitmap.h" #include "SkBlendMode.h" #include "SkCanvas.h" #include "SkColor.h" #include "SkColorSpace.h" #include "SkData.h" +#include "SkImageEncoder.h" #include "SkImageInfo.h" #include "SkPaint.h" +#include "SkPixelRef.h" #include "SkPixmap.h" #include "SkPoint.h" #include "SkRefCnt.h" #include "SkStream.h" #include "SkTypes.h" +#include "SkWebpEncoder.h" + + #include "android_nio_utils.h" +#include "CreateJavaOutputStreamAdaptor.h" +#include +#include +#include #ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread #include #include #include #include +#include +#include #include +#include #include #include #endif #include #include - #include +#include #define DEBUG_PARCEL 0 @@ -1198,11 +1205,18 @@ static jobject Bitmap_copyPreserveInternalConfig(JNIEnv* env, jobject, jlong bit return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(false)); } +#ifdef __ANDROID__ // Layoutlib does not support graphic buffer +typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject); +AHB_from_HB AHardwareBuffer_fromHardwareBuffer; + +typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*); +AHB_to_HB AHardwareBuffer_toHardwareBuffer; +#endif + static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer, jlong colorSpacePtr) { #ifdef __ANDROID__ // Layoutlib does not support graphic buffer - AHardwareBuffer* buffer = uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer( - env, hardwareBuffer); + AHardwareBuffer* buffer = AHardwareBuffer_fromHardwareBuffer(env, hardwareBuffer); sk_sp bitmap = Bitmap::createFrom(buffer, GraphicsJNI::getNativeColorSpace(colorSpacePtr)); if (!bitmap.get()) { @@ -1225,8 +1239,7 @@ static jobject Bitmap_getHardwareBuffer(JNIEnv* env, jobject, jlong bitmapPtr) { } Bitmap& bitmap = bitmapHandle->bitmap(); - return uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer( - env, bitmap.hardwareBuffer()); + return AHardwareBuffer_toHardwareBuffer(env, bitmap.hardwareBuffer()); #else return nullptr; #endif @@ -1334,7 +1347,18 @@ int register_android_graphics_Bitmap(JNIEnv* env) gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J"); gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V"); gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V"); - uirenderer::HardwareBufferHelpers::init(); + +#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel + void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); + AHardwareBuffer_fromHardwareBuffer = + (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer"); + LOG_ALWAYS_FATAL_IF(AHardwareBuffer_fromHardwareBuffer == nullptr, + "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!"); + + AHardwareBuffer_toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer"); + LOG_ALWAYS_FATAL_IF(AHardwareBuffer_toHardwareBuffer == nullptr, + " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!"); +#endif return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods, NELEM(gBitmapMethods)); } diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h index 6b983c10bdca..2c556af8a639 100644 --- a/libs/hwui/jni/GraphicsJNI.h +++ b/libs/hwui/jni/GraphicsJNI.h @@ -2,18 +2,19 @@ #define _ANDROID_GRAPHICS_GRAPHICS_JNI_H_ #include -#include -#include -#include "BRDAllocator.h" #include "Bitmap.h" +#include "BRDAllocator.h" #include "SkBitmap.h" #include "SkCodec.h" -#include "SkColorSpace.h" -#include "SkMallocPixelRef.h" #include "SkPixelRef.h" +#include "SkMallocPixelRef.h" #include "SkPoint.h" #include "SkRect.h" +#include "SkColorSpace.h" +#include +#include + #include "graphics_jni_helpers.h" class SkCanvas; @@ -332,26 +333,6 @@ private: int fLen; }; -class JGlobalRefHolder { -public: - JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {} - - virtual ~JGlobalRefHolder() { - GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject); - mObject = nullptr; - } - - jobject object() { return mObject; } - JavaVM* vm() { return mVm; } - -private: - JGlobalRefHolder(const JGlobalRefHolder&) = delete; - void operator=(const JGlobalRefHolder&) = delete; - - JavaVM* mVm; - jobject mObject; -}; - void doThrowNPE(JNIEnv* env); void doThrowAIOOBE(JNIEnv* env); // Array Index Out Of Bounds Exception void doThrowIAE(JNIEnv* env, const char* msg = NULL); // Illegal Argument diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp deleted file mode 100644 index 7e3f771b6b3d..000000000000 --- a/libs/hwui/jni/HardwareBufferHelpers.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "HardwareBufferHelpers.h" - -#include -#include - -#ifdef __ANDROID__ -typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject); -typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*); -static AHB_from_HB fromHardwareBuffer = nullptr; -static AHB_to_HB toHardwareBuffer = nullptr; -#endif - -void android::uirenderer::HardwareBufferHelpers::init() { -#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel - void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); - fromHardwareBuffer = (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer"); - LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr, - "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!"); - - toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer"); - LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr, - " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!"); -#endif -} - -AHardwareBuffer* android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer( - JNIEnv* env, jobject hardwarebuffer) { -#ifdef __ANDROID__ - LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr, - "Failed to find symbol AHardwareBuffer_fromHardwareBuffer, did you forget " - "to call HardwareBufferHelpers::init?"); - return fromHardwareBuffer(env, hardwarebuffer); -#else - ALOGE("ERROR attempting to invoke AHardwareBuffer_fromHardwareBuffer on non Android " - "configuration"); - return nullptr; -#endif -} - -jobject android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer( - JNIEnv* env, AHardwareBuffer* ahardwarebuffer) { -#ifdef __ANDROID__ - LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr, - "Failed to find symbol AHardwareBuffer_toHardwareBuffer, did you forget to " - "call HardwareBufferHelpers::init?"); - return toHardwareBuffer(env, ahardwarebuffer); -#else - ALOGE("ERROR attempting to invoke AHardwareBuffer_toHardwareBuffer on non Android " - "configuration"); - return nullptr; -#endif -} \ No newline at end of file diff --git a/libs/hwui/jni/HardwareBufferHelpers.h b/libs/hwui/jni/HardwareBufferHelpers.h deleted file mode 100644 index 326babfb0b34..000000000000 --- a/libs/hwui/jni/HardwareBufferHelpers.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef HARDWAREBUFFER_JNI_HELPERS_H -#define HARDWAREBUFFER_JNI_HELPERS_H - -#include -#include - -namespace android { -namespace uirenderer { - -class HardwareBufferHelpers { -public: - static void init(); - static AHardwareBuffer* AHardwareBuffer_fromHardwareBuffer(JNIEnv*, jobject); - static jobject AHardwareBuffer_toHardwareBuffer(JNIEnv*, AHardwareBuffer*); - -private: - HardwareBufferHelpers() = default; // not to be instantiated -}; - -} // namespace uirenderer -} // namespace android - -#endif // HARDWAREBUFFER_JNI_HELPERS_H \ No newline at end of file diff --git a/libs/hwui/jni/JvmErrorReporter.h b/libs/hwui/jni/JvmErrorReporter.h deleted file mode 100644 index 5e10b9d93275..000000000000 --- a/libs/hwui/jni/JvmErrorReporter.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef JVMERRORREPORTER_H -#define JVMERRORREPORTER_H - -#include -#include -#include - -#include "GraphicsJNI.h" - -namespace android { -namespace uirenderer { - -class JvmErrorReporter : public android::uirenderer::ErrorHandler { -public: - JvmErrorReporter(JNIEnv* env) { env->GetJavaVM(&mVm); } - - virtual void onError(const std::string& message) override { - JNIEnv* env = GraphicsJNI::getJNIEnv(); - jniThrowException(env, "java/lang/IllegalStateException", message.c_str()); - } - -private: - JavaVM* mVm; -}; - -} // namespace uirenderer -} // namespace android - -#endif // JVMERRORREPORTER_H diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp deleted file mode 100644 index 4886fdd7ac67..000000000000 --- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#undef LOG_TAG -#define LOG_TAG "HardwareBufferRenderer" -#define ATRACE_TAG ATRACE_TAG_VIEW - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "HardwareBufferHelpers.h" -#include "JvmErrorReporter.h" - -namespace android { - -using namespace android::uirenderer; -using namespace android::uirenderer::renderthread; - -struct { - jclass clazz; - jmethodID invokeRenderCallback; -} gHardwareBufferRendererClassInfo; - -static RenderCallback createRenderCallback(JNIEnv* env, jobject releaseCallback) { - if (releaseCallback == nullptr) return nullptr; - - JavaVM* vm = nullptr; - LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); - auto globalCallbackRef = - std::make_shared(vm, env->NewGlobalRef(releaseCallback)); - return [globalCallbackRef](android::base::unique_fd&& fd, int status) { - GraphicsJNI::getJNIEnv()->CallStaticVoidMethod( - gHardwareBufferRendererClassInfo.clazz, - gHardwareBufferRendererClassInfo.invokeRenderCallback, globalCallbackRef->object(), - reinterpret_cast(fd.release()), reinterpret_cast(status)); - }; -} - -static long android_graphics_HardwareBufferRenderer_createRootNode(JNIEnv* env, jobject) { - auto* node = new RootRenderNode(std::make_unique(env)); - node->incStrong(nullptr); - node->setName("RootRenderNode"); - return reinterpret_cast(node); -} - -static void android_graphics_hardwareBufferRenderer_destroyRootNode(JNIEnv*, jobject, - jlong renderNodePtr) { - auto* node = reinterpret_cast(renderNodePtr); - node->destroy(); -} - -static long android_graphics_HardwareBufferRenderer_create(JNIEnv* env, jobject, jobject buffer, - jlong renderNodePtr) { - auto* hardwareBuffer = HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(env, buffer); - auto* rootRenderNode = reinterpret_cast(renderNodePtr); - ContextFactoryImpl factory(rootRenderNode); - auto* proxy = new RenderProxy(true, rootRenderNode, &factory); - proxy->setHardwareBuffer(hardwareBuffer); - return (jlong)proxy; -} - -static void HardwareBufferRenderer_destroy(jobject renderProxy) { - auto* proxy = reinterpret_cast(renderProxy); - delete proxy; -} - -static SkMatrix createMatrixFromBufferTransform(SkScalar width, SkScalar height, int transform) { - auto matrix = SkMatrix(); - switch (transform) { - case ANATIVEWINDOW_TRANSFORM_ROTATE_90: - matrix.setRotate(90); - matrix.postTranslate(width, 0); - break; - case ANATIVEWINDOW_TRANSFORM_ROTATE_180: - matrix.setRotate(180); - matrix.postTranslate(width, height); - break; - case ANATIVEWINDOW_TRANSFORM_ROTATE_270: - matrix.setRotate(270); - matrix.postTranslate(0, width); - break; - default: - ALOGE("Invalid transform provided. Transform should be validated from" - "the java side. Leveraging identity transform as a fallback"); - [[fallthrough]]; - case ANATIVEWINDOW_TRANSFORM_IDENTITY: - break; - } - return matrix; -} - -static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, jobject renderProxy, - jint transform, jint width, jint height, - jlong colorspacePtr, jobject consumer) { - auto* proxy = reinterpret_cast(renderProxy); - auto skWidth = static_cast(width); - auto skHeight = static_cast(height); - auto matrix = createMatrixFromBufferTransform(skWidth, skHeight, transform); - auto colorSpace = GraphicsJNI::getNativeColorSpace(colorspacePtr); - proxy->setHardwareBufferRenderParams( - HardwareBufferRenderParams(matrix, colorSpace, createRenderCallback(env, consumer))); - return proxy->syncAndDrawFrame(); -} - -static void android_graphics_HardwareBufferRenderer_setLightGeometry(JNIEnv*, jobject, - jobject renderProxyPtr, - jfloat lightX, jfloat lightY, - jfloat lightZ, - jfloat lightRadius) { - auto* proxy = reinterpret_cast(renderProxyPtr); - proxy->setLightGeometry((Vector3){lightX, lightY, lightZ}, lightRadius); -} - -static void android_graphics_HardwareBufferRenderer_setLightAlpha(JNIEnv* env, jobject, - jobject renderProxyPtr, - jfloat ambientShadowAlpha, - jfloat spotShadowAlpha) { - auto* proxy = reinterpret_cast(renderProxyPtr); - proxy->setLightAlpha((uint8_t)(255 * ambientShadowAlpha), (uint8_t)(255 * spotShadowAlpha)); -} - -static jlong android_graphics_HardwareBufferRenderer_getFinalizer() { - return static_cast(reinterpret_cast(&HardwareBufferRenderer_destroy)); -} - -// ---------------------------------------------------------------------------- -// JNI Glue -// ---------------------------------------------------------------------------- - -const char* const kClassPathName = "android/graphics/HardwareBufferRenderer"; - -static const JNINativeMethod gMethods[] = { - {"nCreateHardwareBufferRenderer", "(Landroid/hardware/HardwareBuffer;J)J", - (void*)android_graphics_HardwareBufferRenderer_create}, - {"nRender", "(JIIIJLjava/util/function/Consumer;)I", - (void*)android_graphics_HardwareBufferRenderer_render}, - {"nCreateRootRenderNode", "()J", - (void*)android_graphics_HardwareBufferRenderer_createRootNode}, - {"nSetLightGeometry", "(JFFFF)V", - (void*)android_graphics_HardwareBufferRenderer_setLightGeometry}, - {"nSetLightAlpha", "(JFF)V", (void*)android_graphics_HardwareBufferRenderer_setLightAlpha}, - {"nGetFinalizer", "()J", (void*)android_graphics_HardwareBufferRenderer_getFinalizer}, - {"nDestroyRootRenderNode", "(J)V", - (void*)android_graphics_hardwareBufferRenderer_destroyRootNode}}; - -int register_android_graphics_HardwareBufferRenderer(JNIEnv* env) { - jclass hardwareBufferRendererClazz = - FindClassOrDie(env, "android/graphics/HardwareBufferRenderer"); - gHardwareBufferRendererClassInfo.clazz = hardwareBufferRendererClazz; - gHardwareBufferRendererClassInfo.invokeRenderCallback = - GetStaticMethodIDOrDie(env, hardwareBufferRendererClazz, "invokeRenderCallback", - "(Ljava/util/function/Consumer;II)V"); - HardwareBufferHelpers::init(); - return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); -} - -} // namespace android \ No newline at end of file diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index d6aad7d3eede..a30fc496b2fd 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -56,7 +56,6 @@ #include #include -#include "JvmErrorReporter.h" #include "android_graphics_HardwareRendererObserver.h" namespace android { @@ -94,12 +93,35 @@ struct { jmethodID getDestinationBitmap; } gCopyRequest; +static JNIEnv* getenv(JavaVM* vm) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm); + } + return env; +} + typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface); ANW_fromSurface fromSurface; +class JvmErrorReporter : public ErrorHandler { +public: + JvmErrorReporter(JNIEnv* env) { + env->GetJavaVM(&mVm); + } + + virtual void onError(const std::string& message) override { + JNIEnv* env = getenv(mVm); + jniThrowException(env, "java/lang/IllegalStateException", message.c_str()); + } +private: + JavaVM* mVm; +}; + class FrameCommitWrapper : public LightRefBase { public: explicit FrameCommitWrapper(JNIEnv* env, jobject jobject) { + env->GetJavaVM(&mVm); mObject = env->NewGlobalRef(jobject); LOG_ALWAYS_FATAL_IF(!mObject, "Failed to make global ref"); } @@ -109,18 +131,19 @@ public: void onFrameCommit(bool didProduceBuffer) { if (mObject) { ATRACE_FORMAT("frameCommit success=%d", didProduceBuffer); - GraphicsJNI::getJNIEnv()->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit, - didProduceBuffer); + getenv(mVm)->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit, + didProduceBuffer); releaseObject(); } } private: + JavaVM* mVm; jobject mObject; void releaseObject() { if (mObject) { - GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject); + getenv(mVm)->DeleteGlobalRef(mObject); mObject = nullptr; } } @@ -426,6 +449,26 @@ static void android_view_ThreadedRenderer_forceDrawNextFrame(JNIEnv* env, jobjec proxy->forceDrawNextFrame(); } +class JGlobalRefHolder { +public: + JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {} + + virtual ~JGlobalRefHolder() { + getenv(mVm)->DeleteGlobalRef(mObject); + mObject = nullptr; + } + + jobject object() { return mObject; } + JavaVM* vm() { return mVm; } + +private: + JGlobalRefHolder(const JGlobalRefHolder&) = delete; + void operator=(const JGlobalRefHolder&) = delete; + + JavaVM* mVm; + jobject mObject; +}; + using TextureMap = std::unordered_map>; struct PictureCaptureState { @@ -541,7 +584,7 @@ static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* auto pictureState = std::make_shared(); proxy->setPictureCapturedCallback([globalCallbackRef, pictureState](sk_sp&& picture) { - JNIEnv* env = GraphicsJNI::getJNIEnv(); + JNIEnv* env = getenv(globalCallbackRef->vm()); Picture* wrapper = new PictureWrapper{std::move(picture), pictureState}; env->CallStaticVoidMethod(gHardwareRenderer.clazz, gHardwareRenderer.invokePictureCapturedCallback, @@ -563,7 +606,7 @@ static void android_view_ThreadedRenderer_setASurfaceTransactionCallback( vm, env->NewGlobalRef(aSurfaceTransactionCallback)); proxy->setASurfaceTransactionCallback( [globalCallbackRef](int64_t transObj, int64_t scObj, int64_t frameNr) -> bool { - JNIEnv* env = GraphicsJNI::getJNIEnv(); + JNIEnv* env = getenv(globalCallbackRef->vm()); jboolean ret = env->CallBooleanMethod( globalCallbackRef->object(), gASurfaceTransactionCallback.onMergeTransaction, @@ -585,7 +628,7 @@ static void android_view_ThreadedRenderer_setPrepareSurfaceControlForWebviewCall auto globalCallbackRef = std::make_shared(vm, env->NewGlobalRef(callback)); proxy->setPrepareSurfaceControlForWebviewCallback([globalCallbackRef]() { - JNIEnv* env = GraphicsJNI::getJNIEnv(); + JNIEnv* env = getenv(globalCallbackRef->vm()); env->CallVoidMethod(globalCallbackRef->object(), gPrepareSurfaceControlForWebviewCallback.prepare); }); @@ -604,7 +647,7 @@ static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env, env->NewGlobalRef(frameCallback)); proxy->setFrameCallback([globalCallbackRef](int32_t syncResult, int64_t frameNr) -> std::function { - JNIEnv* env = GraphicsJNI::getJNIEnv(); + JNIEnv* env = getenv(globalCallbackRef->vm()); ScopedLocalRef frameCommitCallback( env, env->CallObjectMethod( globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw, @@ -643,7 +686,7 @@ static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env, auto globalCallbackRef = std::make_shared(vm, env->NewGlobalRef(callback)); proxy->setFrameCompleteCallback([globalCallbackRef]() { - JNIEnv* env = GraphicsJNI::getJNIEnv(); + JNIEnv* env = getenv(globalCallbackRef->vm()); env->CallVoidMethod(globalCallbackRef->object(), gFrameCompleteCallback.onFrameComplete); }); @@ -656,7 +699,7 @@ public: : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {} virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override { - JNIEnv* env = GraphicsJNI::getJNIEnv(); + JNIEnv* env = getenv(mRefHolder.vm()); jlong bitmapPtr = env->CallLongMethod( mRefHolder.object(), gCopyRequest.getDestinationBitmap, srcWidth, srcHeight); SkBitmap bitmap; @@ -665,7 +708,7 @@ public: } virtual void onCopyFinished(CopyResult result) override { - JNIEnv* env = GraphicsJNI::getJNIEnv(); + JNIEnv* env = getenv(mRefHolder.vm()); env->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished, static_cast(result)); } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 202a62cf320c..19cd7bdd6fcb 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -55,9 +55,7 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() { MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { // In case the surface was destroyed (e.g. a previous trimMemory call) we // need to recreate it here. - if (mHardwareBuffer) { - mRenderThread.requireGlContext(); - } else if (!isSurfaceReady() && mNativeWindow) { + if (!isSurfaceReady() && mNativeWindow) { setSurface(mNativeWindow.get(), mSwapBehavior); } @@ -69,24 +67,17 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { } Frame SkiaOpenGLPipeline::getFrame() { - if (mHardwareBuffer) { - AHardwareBuffer_Desc description; - AHardwareBuffer_describe(mHardwareBuffer, &description); - return Frame(description.width, description.height, 0); - } else { - LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, - "drawRenderNode called on a context with no surface!"); - return mEglManager.beginFrame(mEglSurface); - } + LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, + "drawRenderNode called on a context with no surface!"); + return mEglManager.beginFrame(mEglSurface); } IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector>& renderNodes, FrameInfoVisualizer* profiler, - const HardwareBufferRenderParams& bufferParams) { - if (!isCapturingSkp() && !mHardwareBuffer) { + const std::vector>& renderNodes, FrameInfoVisualizer* profiler) { + if (!isCapturingSkp()) { mEglManager.damageFrame(frame, dirty); } @@ -113,25 +104,13 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( SkSurfaceProps props(0, kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); - sk_sp surface; - SkMatrix preTransform; - if (mHardwareBuffer) { - surface = getBufferSkSurface(bufferParams); - preTransform = bufferParams.getTransform(); - } else { - surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT, - getSurfaceOrigin(), colorType, - mSurfaceColorSpace, &props); - preTransform = SkMatrix::I(); - } + sk_sp surface(SkSurface::MakeFromBackendRenderTarget( + mRenderThread.getGrContext(), backendRT, this->getSurfaceOrigin(), colorType, + mSurfaceColorSpace, &props)); - SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y); - LightGeometry localGeometry = lightGeometry; - localGeometry.center.x = lightCenter.fX; - localGeometry.center.y = lightCenter.fY; - LightingInfo::updateLighting(localGeometry, lightInfo); + LightingInfo::updateLighting(lightGeometry, lightInfo); renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, - preTransform); + SkMatrix::I()); // Draw visual debugging features if (CC_UNLIKELY(Properties::showDirtyRegions || @@ -163,10 +142,6 @@ bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect // metrics the frame was swapped at this point currentFrameInfo->markSwapBuffers(); - if (mHardwareBuffer) { - return false; - } - *requireSwap = drew || mEglManager.damageRequiresSwap(); if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) { @@ -222,26 +197,6 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh return false; } -[[nodiscard]] android::base::unique_fd SkiaOpenGLPipeline::flush() { - int fence = -1; - EGLSyncKHR sync = EGL_NO_SYNC_KHR; - mEglManager.createReleaseFence(true, &sync, &fence); - // If a sync object is returned here then the device does not support native - // fences, we block on the returned sync and return -1 as a file descriptor - if (sync != EGL_NO_SYNC_KHR) { - EGLDisplay display = mEglManager.eglDisplay(); - EGLint result = eglClientWaitSyncKHR(display, sync, 0, 1000000000); - if (result == EGL_FALSE) { - ALOGE("EglManager::createReleaseFence: error waiting for previous fence: %#x", - eglGetError()); - } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { - ALOGE("EglManager::createReleaseFence: timeout waiting for previous fence"); - } - eglDestroySyncKHR(display, sync); - } - return android::base::unique_fd(fence); -} - bool SkiaOpenGLPipeline::isSurfaceReady() { return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE); } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 940d6bfdb83c..a80c613697f2 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -21,7 +21,6 @@ #include "SkiaPipeline.h" #include "renderstate/RenderState.h" -#include "renderthread/HardwareBufferRenderParams.h" namespace android { @@ -37,18 +36,19 @@ public: renderthread::MakeCurrentResult makeCurrent() override; renderthread::Frame getFrame() override; - renderthread::IRenderPipeline::DrawResult draw( - const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector >& renderNodes, FrameInfoVisualizer* profiler, - const renderthread::HardwareBufferRenderParams& bufferParams) override; + renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame, + const SkRect& screenDirty, const SkRect& dirty, + const LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, + const LightInfo& lightInfo, + const std::vector >& renderNodes, + FrameInfoVisualizer* profiler) override; GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; } bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override; - [[nodiscard]] android::base::unique_fd flush() override; void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 2017eb6eb7da..09c3120a9f71 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -605,31 +605,6 @@ void SkiaPipeline::dumpResourceCacheUsage() const { ALOGD("%s", log.c_str()); } -void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) { - if (mHardwareBuffer) { - AHardwareBuffer_release(mHardwareBuffer); - mHardwareBuffer = nullptr; - } - - if (buffer) { - AHardwareBuffer_acquire(buffer); - mHardwareBuffer = buffer; - } -} - -sk_sp SkiaPipeline::getBufferSkSurface( - const renderthread::HardwareBufferRenderParams& bufferParams) { - auto bufferColorSpace = bufferParams.getColorSpace(); - if (mBufferSurface == nullptr || mBufferColorSpace == nullptr || - !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) { - mBufferSurface = SkSurface::MakeFromAHardwareBuffer( - mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin, - bufferColorSpace, nullptr, true); - mBufferColorSpace = bufferColorSpace; - } - return mBufferSurface; -} - void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { mColorMode = colorMode; switch (colorMode) { diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index befee8989383..b7d799f705b3 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -20,11 +20,9 @@ #include #include #include - #include "Lighting.h" #include "hwui/AnimatedImageDrawable.h" #include "renderthread/CanvasContext.h" -#include "renderthread/HardwareBufferRenderParams.h" #include "renderthread/IRenderPipeline.h" class SkFILEWStream; @@ -75,22 +73,14 @@ public: mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None; } - virtual void setHardwareBuffer(AHardwareBuffer* buffer) override; - bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; } void setTargetSdrHdrRatio(float ratio) override; protected: - sk_sp getBufferSkSurface( - const renderthread::HardwareBufferRenderParams& bufferParams); void dumpResourceCacheUsage() const; renderthread::RenderThread& mRenderThread; - AHardwareBuffer* mHardwareBuffer = nullptr; - sk_sp mBufferSurface = nullptr; - sk_sp mBufferColorSpace = nullptr; - ColorMode mColorMode = ColorMode::Default; SkColorType mSurfaceColorType; sk_sp mSurfaceColorSpace; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index 99298bc0fe9b..a8c752f0e0b7 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -57,55 +57,37 @@ VulkanManager& SkiaVulkanPipeline::vulkanManager() { MakeCurrentResult SkiaVulkanPipeline::makeCurrent() { // In case the surface was destroyed (e.g. a previous trimMemory call) we // need to recreate it here. - if (mHardwareBuffer) { - mRenderThread.requireVkContext(); - } else if (!isSurfaceReady() && mNativeWindow) { + if (!isSurfaceReady() && mNativeWindow) { setSurface(mNativeWindow.get(), SwapBehavior::kSwap_default); } return isContextReady() ? MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed; } Frame SkiaVulkanPipeline::getFrame() { - if (mHardwareBuffer) { - AHardwareBuffer_Desc description; - AHardwareBuffer_describe(mHardwareBuffer, &description); - return Frame(description.width, description.height, 0); - } else { - LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, - "getFrame() called on a context with no surface!"); - return vulkanManager().dequeueNextBuffer(mVkSurface); - } + LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, "getFrame() called on a context with no surface!"); + return vulkanManager().dequeueNextBuffer(mVkSurface); } IRenderPipeline::DrawResult SkiaVulkanPipeline::draw( const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector>& renderNodes, FrameInfoVisualizer* profiler, - const HardwareBufferRenderParams& bufferParams) { - sk_sp backBuffer; - SkMatrix preTransform; - if (mHardwareBuffer) { - backBuffer = getBufferSkSurface(bufferParams); - preTransform = bufferParams.getTransform(); - } else { - backBuffer = mVkSurface->getCurrentSkSurface(); - preTransform = mVkSurface->getCurrentPreTransform(); - } - + const std::vector>& renderNodes, FrameInfoVisualizer* profiler) { + sk_sp backBuffer = mVkSurface->getCurrentSkSurface(); if (backBuffer.get() == nullptr) { return {false, -1}; } // update the coordinates of the global light position based on surface rotation - SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y); + SkPoint lightCenter = mVkSurface->getCurrentPreTransform().mapXY(lightGeometry.center.x, + lightGeometry.center.y); LightGeometry localGeometry = lightGeometry; localGeometry.center.x = lightCenter.fX; localGeometry.center.y = lightCenter.fY; LightingInfo::updateLighting(localGeometry, lightInfo); renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer, - preTransform); + mVkSurface->getCurrentPreTransform()); // Draw visual debugging features if (CC_UNLIKELY(Properties::showDirtyRegions || @@ -134,16 +116,12 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw( bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) { + *requireSwap = drew; + // Even if we decided to cancel the frame, from the perspective of jank // metrics the frame was swapped at this point currentFrameInfo->markSwapBuffers(); - if (mHardwareBuffer) { - return false; - } - - *requireSwap = drew; - if (*requireSwap) { vulkanManager().swapBuffers(mVkSurface, screenDirty); } @@ -159,12 +137,6 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { void SkiaVulkanPipeline::onStop() {} -[[nodiscard]] android::base::unique_fd SkiaVulkanPipeline::flush() { - int fence = -1; - vulkanManager().createReleaseFence(&fence, mRenderThread.getGrContext()); - return android::base::unique_fd(fence); -} - // We can safely ignore the swap behavior because VkManager will always operate // in a mode equivalent to EGLManager::SwapBehavior::kBufferAge bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior /*swapBehavior*/) { diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index d921ddb0d0fb..e0884a8390a6 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -16,13 +16,14 @@ #pragma once -#include "SkRefCnt.h" #include "SkiaPipeline.h" -#include "renderstate/RenderState.h" -#include "renderthread/HardwareBufferRenderParams.h" #include "renderthread/VulkanManager.h" #include "renderthread/VulkanSurface.h" +#include "renderstate/RenderState.h" + +#include "SkRefCnt.h" + class SkBitmap; struct SkRect; @@ -37,18 +38,18 @@ public: renderthread::MakeCurrentResult makeCurrent() override; renderthread::Frame getFrame() override; - renderthread::IRenderPipeline::DrawResult draw( - const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector >& renderNodes, FrameInfoVisualizer* profiler, - const renderthread::HardwareBufferRenderParams& bufferParams) override; + renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame, + const SkRect& screenDirty, const SkRect& dirty, + const LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, + const LightInfo& lightInfo, + const std::vector >& renderNodes, + FrameInfoVisualizer* profiler) override; GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; } bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; - [[nodiscard]] android::base::unique_fd flush() override; - bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override; void onStop() override; bool isSurfaceReady() override; @@ -65,6 +66,7 @@ protected: private: renderthread::VulkanManager& vulkanManager(); + renderthread::VulkanSurface* mVkSurface = nullptr; sp mNativeWindow; }; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index ee1c2ec81a91..c0f30865d507 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -153,7 +153,6 @@ void CanvasContext::removeRenderNode(RenderNode* node) { void CanvasContext::destroy() { stopDrawing(); - setHardwareBuffer(nullptr); setSurface(nullptr); setSurfaceControl(nullptr); freePrefetchedLayers(); @@ -177,19 +176,6 @@ static void setBufferCount(ANativeWindow* window) { native_window_set_buffer_count(window, bufferCount); } -void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) { - if (mHardwareBuffer) { - AHardwareBuffer_release(mHardwareBuffer); - mHardwareBuffer = nullptr; - } - - if (buffer) { - AHardwareBuffer_acquire(buffer); - mHardwareBuffer = buffer; - } - mRenderPipeline->setHardwareBuffer(mHardwareBuffer); -} - void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) { ATRACE_CALL(); @@ -275,7 +261,7 @@ void CanvasContext::setStopped(bool stopped) { mRenderThread.removeFrameCallback(this); mRenderPipeline->onStop(); mRenderThread.cacheManager().onContextStopped(this); - } else if (mIsDirty && hasOutputTarget()) { + } else if (mIsDirty && hasSurface()) { mRenderThread.postFrameCallback(this); } } @@ -439,7 +425,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy mIsDirty = true; - if (CC_UNLIKELY(!hasOutputTarget())) { + if (CC_UNLIKELY(!hasSurface())) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); info.out.canDrawThisFrame = false; return; @@ -584,7 +570,7 @@ void CanvasContext::draw() { std::scoped_lock lock(mFrameMetricsReporterMutex); drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds, mOpaque, - mLightInfo, mRenderNodes, &(profiler()), mBufferParams); + mLightInfo, mRenderNodes, &(profiler())); } uint64_t frameCompleteNr = getFrameNumber(); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index a811670e8176..0f6b736e7edf 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -125,13 +125,12 @@ public: // Won't take effect until next EGLSurface creation void setSwapBehavior(SwapBehavior swapBehavior); - void setHardwareBuffer(AHardwareBuffer* buffer); void setSurface(ANativeWindow* window, bool enableTimeout = true); void setSurfaceControl(ASurfaceControl* surfaceControl); bool pauseSurface(); void setStopped(bool stopped); - bool isStopped() { return mStopped || !hasOutputTarget(); } - bool hasOutputTarget() const { return mNativeSurface.get() || mHardwareBuffer; } + bool isStopped() { return mStopped || !hasSurface(); } + bool hasSurface() const { return mNativeSurface.get(); } void allocateBuffers(); void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); @@ -209,10 +208,6 @@ public: mASurfaceTransactionCallback = callback; } - void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) { - mBufferParams = params; - } - bool mergeTransaction(ASurfaceTransaction* transaction, ASurfaceControl* control); void setPrepareSurfaceControlForWebviewCallback(const std::function& callback) { @@ -267,9 +262,6 @@ private: int32_t mLastFrameHeight = 0; RenderThread& mRenderThread; - - AHardwareBuffer* mHardwareBuffer = nullptr; - HardwareBufferRenderParams mBufferParams; std::unique_ptr mNativeSurface; // The SurfaceControl reference is passed from ViewRootImpl, can be set to // NULL to remove the reference diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index fab2f46e91c3..ccdf715e95d8 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -26,7 +26,6 @@ #include "../Properties.h" #include "../RenderNode.h" #include "CanvasContext.h" -#include "HardwareBufferRenderParams.h" #include "RenderThread.h" namespace android { @@ -93,9 +92,6 @@ void DrawFrameTask::run() { mContext->setSyncDelayDuration(systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued); mContext->setTargetSdrHdrRatio(mRenderSdrHdrRatio); - auto hardwareBufferParams = mHardwareBufferParams; - mContext->setHardwareBufferRenderParams(hardwareBufferParams); - IRenderPipeline* pipeline = mContext->getRenderPipeline(); bool canUnblockUiThread; bool canDrawThisFrame; { @@ -155,11 +151,6 @@ void DrawFrameTask::run() { if (!canUnblockUiThread) { unblockUiThread(); } - - if (pipeline->hasHardwareBuffer()) { - auto fence = pipeline->flush(); - hardwareBufferParams.invokeRenderCallback(std::move(fence), 0); - } } bool DrawFrameTask::syncFrameState(TreeInfo& info) { @@ -185,9 +176,8 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { // This is after the prepareTree so that any pending operations // (RenderNode tree state, prefetched layers, etc...) will be flushed. - bool hasTarget = mContext->hasOutputTarget(); - if (CC_UNLIKELY(!hasTarget || !canDraw)) { - if (!hasTarget) { + if (CC_UNLIKELY(!mContext->hasSurface() || !canDraw)) { + if (!mContext->hasSurface()) { mSyncResult |= SyncResult::LostSurfaceRewardIfFound; } else { // If we have a surface but can't draw we must be stopped diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index 4130d4abe09e..4be8f6bc348a 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -27,16 +27,8 @@ #include "../Rect.h" #include "../TreeInfo.h" #include "RenderTask.h" -#include "SkColorSpace.h" -#include "SwapBehavior.h" -#include "utils/TimeUtils.h" -#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration -#include -#endif -#include "HardwareBufferRenderParams.h" namespace android { - namespace uirenderer { class DeferredLayerUpdater; @@ -96,10 +88,6 @@ public: void forceDrawNextFrame() { mForceDrawFrame = true; } - void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) { - mHardwareBufferParams = params; - } - void setRenderSdrHdrRatio(float ratio) { mRenderSdrHdrRatio = ratio; } private: @@ -126,7 +114,6 @@ private: int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE]; - HardwareBufferRenderParams mHardwareBufferParams; std::function(int32_t, int64_t)> mFrameCallback; std::function mFrameCommitCallback; std::function mFrameCompleteCallback; diff --git a/libs/hwui/renderthread/HardwareBufferRenderParams.h b/libs/hwui/renderthread/HardwareBufferRenderParams.h deleted file mode 100644 index 91fe3f6cf273..000000000000 --- a/libs/hwui/renderthread/HardwareBufferRenderParams.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef HARDWAREBUFFERRENDERER_H_ -#define HARDWAREBUFFERRENDERER_H_ - -#include -#include - -#include "SkColorSpace.h" -#include "SkMatrix.h" -#include "SkSurface.h" - -namespace android { -namespace uirenderer { -namespace renderthread { - -using namespace android::uirenderer::renderthread; - -using RenderCallback = std::function; - -class RenderProxy; - -class HardwareBufferRenderParams { -public: - HardwareBufferRenderParams() = default; - HardwareBufferRenderParams(const SkMatrix& transform, const sk_sp& colorSpace, - RenderCallback&& callback) - : mTransform(transform) - , mColorSpace(colorSpace) - , mRenderCallback(std::move(callback)) {} - const SkMatrix& getTransform() const { return mTransform; } - sk_sp getColorSpace() const { return mColorSpace; } - - void invokeRenderCallback(android::base::unique_fd&& fenceFd, int status) { - if (mRenderCallback) { - std::invoke(mRenderCallback, std::move(fenceFd), status); - } - } - -private: - SkMatrix mTransform = SkMatrix::I(); - sk_sp mColorSpace = SkColorSpace::MakeSRGB(); - RenderCallback mRenderCallback = nullptr; -}; - -} // namespace renderthread -} // namespace uirenderer -} // namespace android -#endif // HARDWAREBUFFERRENDERER_H_ diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index c68fcdfc76f2..18cad31fda26 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -16,19 +16,17 @@ #pragma once -#include -#include -#include -#include - -#include "ColorMode.h" #include "DamageAccumulator.h" #include "FrameInfoVisualizer.h" -#include "HardwareBufferRenderParams.h" #include "LayerUpdateQueue.h" #include "Lighting.h" #include "SwapBehavior.h" #include "hwui/Bitmap.h" +#include "ColorMode.h" + +#include +#include +#include class GrDirectContext; @@ -66,14 +64,10 @@ public: const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, const std::vector>& renderNodes, - FrameInfoVisualizer* profiler, - const HardwareBufferRenderParams& bufferParams) = 0; + FrameInfoVisualizer* profiler) = 0; virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) = 0; virtual DeferredLayerUpdater* createTextureLayer() = 0; - [[nodiscard]] virtual android::base::unique_fd flush() = 0; - virtual void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) = 0; - virtual bool hasHardwareBuffer() = 0; virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior) = 0; virtual void onStop() = 0; virtual bool isSurfaceReady() = 0; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 1e011c231343..f8e2deebc3c6 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -85,18 +85,6 @@ void RenderProxy::setName(const char* name) { mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); }); } -void RenderProxy::setHardwareBuffer(AHardwareBuffer* buffer) { - if (buffer) { - AHardwareBuffer_acquire(buffer); - } - mRenderThread.queue().post([this, hardwareBuffer = buffer]() mutable { - mContext->setHardwareBuffer(hardwareBuffer); - if (hardwareBuffer) { - AHardwareBuffer_release(hardwareBuffer); - } - }); -} - void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) { if (window) { ANativeWindow_acquire(window); } mRenderThread.queue().post([this, win = window, enableTimeout]() mutable { @@ -352,10 +340,6 @@ void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) mDrawFrameTask.setContentDrawBounds(left, top, right, bottom); } -void RenderProxy::setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) { - mDrawFrameTask.setHardwareBufferRenderParams(params); -} - void RenderProxy::setPictureCapturedCallback( const std::function&&)>& callback) { mRenderThread.queue().post( diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 82072a6e2499..5dd65a0c8152 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -18,7 +18,6 @@ #define RENDERPROXY_H_ #include -#include #include #include #include @@ -77,7 +76,7 @@ public: void setSwapBehavior(SwapBehavior swapBehavior); bool loadSystemProperties(); void setName(const char* name); - void setHardwareBuffer(AHardwareBuffer* buffer); + void setSurface(ANativeWindow* window, bool enableTimeout = true); void setSurfaceControl(ASurfaceControl* surfaceControl); void allocateBuffers(); @@ -85,7 +84,6 @@ public: void setStopped(bool stopped); void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); void setLightGeometry(const Vector3& lightCenter, float lightRadius); - void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params); void setOpaque(bool opaque); float setColorMode(ColorMode mode); void setRenderSdrHdrRatio(float ratio); diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp index 9e376e32f8ea..88420a5d5c23 100644 --- a/libs/hwui/tests/unit/CanvasContextTests.cpp +++ b/libs/hwui/tests/unit/CanvasContextTests.cpp @@ -38,7 +38,7 @@ RENDERTHREAD_TEST(CanvasContext, create) { std::unique_ptr canvasContext( CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0)); - ASSERT_FALSE(canvasContext->hasOutputTarget()); + ASSERT_FALSE(canvasContext->hasSurface()); canvasContext->destroy(); } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index fdba9a6597c6..e1f6acc96730 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -1136,15 +1136,6 @@ - - - - - - - { - result.getFence().await(Duration.ofMillis(3000)); - handler.post(() -> { - Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, colorSpace); - Bitmap copy = bitmap.copy(Config.ARGB_8888, false); - mImageView.setImageBitmap(copy); - }); - }); - } -} -- cgit v1.2.3-59-g8ed1b From 5432e532386e59c90744235963224dea9c312ee2 Mon Sep 17 00:00:00 2001 From: Devin Cody Date: Tue, 31 Jan 2023 20:03:58 +0000 Subject: Revert "Tonemap in RecordingCanvas" This reverts commit 9776d3e35718efd9ea08400fe0dbea61dc6386ce. Reason for revert: DroidMonitor-triggered revert due to breakage https://android-build.googleplex.com/builds/quarterdeck?branch=git_master&target=errorprone&lkgb=9546489&lkbb=9546754&fkbb=9546547, bug b/267342770 Change-Id: I58f6a0350287a92199a9426f3d3d908a279821d8 --- graphics/java/android/graphics/ColorSpace.java | 4 +- libs/hwui/Android.bp | 2 +- libs/hwui/CanvasTransform.h | 2 +- libs/hwui/RecordingCanvas.cpp | 15 +- libs/hwui/Tonemapper.cpp | 17 +- tests/HwAccelerationTest/AndroidManifest.xml | 9 - .../com/android/test/hwui/ColorBitmapActivity.java | 196 --------------------- 7 files changed, 11 insertions(+), 234 deletions(-) delete mode 100644 tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 4c669b8c34f5..2427dec169d6 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -212,9 +212,9 @@ public abstract class ColorSpace { new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4); private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS = new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f, - 0.28466892f, 0.5599107f, -11 / 12.0f, -3.0f, true); + 0.28466892f, 0.5599107f, 0.0f, -3.0f, true); private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS = - new Rgb.TransferParameters(-107 / 128.0f, 1.0f, 32 / 2523.0f, + new Rgb.TransferParameters(107 / 128.0f, 1.0f, 32 / 2523.0f, 2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true); // See static initialization block next to #get(Named) diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 2245aae29ecc..59e4b7acdba7 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -536,7 +536,6 @@ cc_defaults { "RootRenderNode.cpp", "SkiaCanvas.cpp", "SkiaInterpolator.cpp", - "Tonemapper.cpp", "VectorDrawable.cpp", ], @@ -595,6 +594,7 @@ cc_defaults { "ProfileData.cpp", "ProfileDataContainer.cpp", "Readback.cpp", + "Tonemapper.cpp", "TreeInfo.cpp", "WebViewFunctorManager.cpp", "protos/graphicsstats.proto", diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h index 291f4cf7193b..c46a2d369974 100644 --- a/libs/hwui/CanvasTransform.h +++ b/libs/hwui/CanvasTransform.h @@ -45,4 +45,4 @@ bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette pale SkColor transformColor(ColorTransform transform, SkColor color); SkColor transformColorInverse(ColorTransform transform, SkColor color); -} // namespace android::uirenderer +} // namespace android::uirenderer; \ No newline at end of file diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index bbe79d922b3f..3f21940d35a7 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -43,7 +43,6 @@ #include "SkRegion.h" #include "SkTextBlob.h" #include "SkVertices.h" -#include "Tonemapper.h" #include "VectorDrawable.h" #include "pipeline/skia/AnimatedDrawables.h" #include "pipeline/skia/FunctorDrawable.h" @@ -335,9 +334,7 @@ struct DrawImage final : Op { SkPaint paint; BitmapPalette palette; void draw(SkCanvas* c, const SkMatrix&) const { - SkPaint newPaint = paint; - tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint); - c->drawImage(image.get(), x, y, sampling, &newPaint); + c->drawImage(image.get(), x, y, sampling, &paint); } }; struct DrawImageRect final : Op { @@ -359,9 +356,7 @@ struct DrawImageRect final : Op { SkCanvas::SrcRectConstraint constraint; BitmapPalette palette; void draw(SkCanvas* c, const SkMatrix&) const { - SkPaint newPaint = paint; - tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint); - c->drawImageRect(image.get(), src, dst, sampling, &newPaint, constraint); + c->drawImageRect(image.get(), src, dst, sampling, &paint, constraint); } }; struct DrawImageLattice final : Op { @@ -394,10 +389,8 @@ struct DrawImageLattice final : Op { auto flags = (0 == fs) ? nullptr : pod( this, (xs + ys) * sizeof(int) + fs * sizeof(SkColor)); - SkPaint newPaint = paint; - tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint); - c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, filter, - &newPaint); + c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, + filter, &paint); } }; diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp index 0d39f0e33298..a7e76b631140 100644 --- a/libs/hwui/Tonemapper.cpp +++ b/libs/hwui/Tonemapper.cpp @@ -18,10 +18,7 @@ #include #include -// libshaders only exists on Android devices -#ifdef __ANDROID__ #include -#endif #include "utils/Color.h" @@ -29,8 +26,6 @@ namespace android::uirenderer { namespace { -// custom tonemapping only exists on Android devices -#ifdef __ANDROID__ class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder { public: explicit ColorFilterRuntimeEffectBuilder(sk_sp effect) @@ -64,21 +59,20 @@ static sk_sp createLinearEffectColorFilter(const shaders::LinearE return effectBuilder.makeColorFilter(); } -static ui::Dataspace extractTransfer(ui::Dataspace dataspace) { - return static_cast(dataspace & HAL_DATASPACE_TRANSFER_MASK); +static bool extractTransfer(ui::Dataspace dataspace) { + return dataspace & HAL_DATASPACE_TRANSFER_MASK; } static bool isHdrDataspace(ui::Dataspace dataspace) { const auto transfer = extractTransfer(dataspace); - return transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG; + return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG; } static ui::Dataspace getDataspace(const SkImageInfo& image) { return static_cast( ColorSpaceToADataSpace(image.colorSpace(), image.colorType())); } -#endif } // namespace @@ -86,8 +80,6 @@ static ui::Dataspace getDataspace(const SkImageInfo& image) { // shader and tag it on the supplied paint. void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits, SkPaint& paint) { -// custom tonemapping only exists on Android devices -#ifdef __ANDROID__ const auto sourceDataspace = getDataspace(source); const auto destinationDataspace = getDataspace(destination); @@ -110,9 +102,6 @@ void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, flo paint.setColorFilter(colorFilter); } } -#else - return; -#endif } } // namespace android::uirenderer diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 5a3d28a4dfad..616f21cbe1a4 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -120,15 +120,6 @@ - - - - - - - diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java deleted file mode 100644 index 017de605fe39..000000000000 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.test.hwui; - -import android.app.Activity; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.ColorSpace; -import android.graphics.HardwareBufferRenderer; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.RenderNode; -import android.graphics.Shader; -import android.hardware.HardwareBuffer; -import android.media.Image; -import android.media.ImageWriter; -import android.os.Bundle; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.Spinner; - -import java.time.Duration; -import java.util.concurrent.Executors; -import java.util.concurrent.FutureTask; - -@SuppressWarnings({"UnusedDeclaration"}) -public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callback, - AdapterView.OnItemSelectedListener { - - private static final int WIDTH = 512; - private static final int HEIGHT = 512; - - private ImageView mImageView; - private SurfaceView mSurfaceView; - private HardwareBuffer mGradientBuffer; - private ImageWriter mImageWriter; - private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); - private String[] mColorNames = {"sRGB", "BT2020_HLG", "BT2020_PQ"}; - private String mCurrentColorName = "sRGB"; - - private FutureTask authorGradientBuffer(HardwareBuffer buffer) { - HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer); - RenderNode node = new RenderNode("content"); - node.setPosition(0, 0, buffer.getWidth(), buffer.getHeight()); - - Canvas canvas = node.beginRecording(); - LinearGradient gradient = new LinearGradient( - 0, 0, buffer.getWidth(), buffer.getHeight(), 0xFF000000, - 0xFFFFFFFF, Shader.TileMode.CLAMP); - Paint paint = new Paint(); - paint.setShader(gradient); - canvas.drawRect(0f, 0f, buffer.getWidth(), buffer.getHeight(), paint); - node.endRecording(); - - renderer.setContentRoot(node); - - ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.SRGB); - FutureTask resolvedBuffer = new FutureTask<>(() -> buffer); - renderer.obtainRenderRequest() - .setColorSpace(colorSpace) - .draw(Executors.newSingleThreadExecutor(), result -> { - result.getFence().await(Duration.ofSeconds(3)); - resolvedBuffer.run(); - }); - return resolvedBuffer; - } - - private FutureTask getGradientBuffer() { - HardwareBuffer buffer = HardwareBuffer.create( - WIDTH, HEIGHT, PixelFormat.RGBA_8888, 1, - HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE - | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT); - return authorGradientBuffer(buffer); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - try { - - mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); - - ArrayAdapter adapter = new ArrayAdapter<>( - this, android.R.layout.simple_spinner_item, mColorNames); - - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - Spinner spinner = new Spinner(this); - spinner.setAdapter(adapter); - spinner.setOnItemSelectedListener(this); - - mGradientBuffer = getGradientBuffer().get(); - - LinearLayout linearLayout = new LinearLayout(this); - linearLayout.setOrientation(LinearLayout.VERTICAL); - - mImageView = new ImageView(this); - - mSurfaceView = new SurfaceView(this); - mSurfaceView.getHolder().addCallback(this); - - linearLayout.addView(spinner, new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT)); - - linearLayout.addView(mImageView, new LinearLayout.LayoutParams(WIDTH, HEIGHT)); - linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT)); - - setContentView(linearLayout); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private ColorSpace getFromName(String name) { - if (name.equals("sRGB")) { - return ColorSpace.get(ColorSpace.Named.SRGB); - } else if (name.equals("BT2020_HLG")) { - return ColorSpace.get(ColorSpace.Named.BT2020_HLG); - } else if (name.equals("BT2020_PQ")) { - return ColorSpace.get(ColorSpace.Named.BT2020_PQ); - } - - throw new RuntimeException("Unrecognized Colorspace!"); - } - - private void populateBuffers() { - Bitmap bitmap = Bitmap.wrapHardwareBuffer( - mGradientBuffer, ColorSpace.get(ColorSpace.Named.SRGB)); - Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false); - copy.setColorSpace(mColorSpace); - mImageView.setImageBitmap(copy); - - try (Image image = mImageWriter.dequeueInputImage()) { - authorGradientBuffer(image.getHardwareBuffer()).get(); - image.setDataSpace(mColorSpace.getDataSpace()); - mImageWriter.queueInputImage(image); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - mImageWriter = new ImageWriter.Builder(holder.getSurface()) - .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE - | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT - | HardwareBuffer.USAGE_COMPOSER_OVERLAY) - .build(); - populateBuffers(); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - mImageWriter.close(); - mImageWriter = null; - } - - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - mCurrentColorName = mColorNames[position]; - mColorSpace = getFromName(mCurrentColorName); - populateBuffers(); - } - - @Override - public void onNothingSelected(AdapterView parent) { - - } -} -- cgit v1.2.3-59-g8ed1b From a352185eca2a678fbfdcb8031df1e70e7746a4ff Mon Sep 17 00:00:00 2001 From: Nader Jawad Date: Mon, 30 Jan 2023 20:23:46 -0800 Subject: Revert "Revert "Created HardwareBufferRenderer to support rendering into..."" This reverts commit cca989f2b52725468464534f337ee55d01644fb3. Test: atest CtsUiRenderingTestCases --iterations 10 --armeabi-v7a Change-Id: Iee19edeb489ed54b421ac8de37ee5a70b8f9756a --- core/api/current.txt | 23 ++ core/java/android/hardware/SyncFence.java | 22 +- core/jni/LayoutlibLoader.cpp | 1 + .../android/graphics/HardwareBufferRenderer.java | 390 +++++++++++++++++++++ libs/hwui/Android.bp | 2 + libs/hwui/apex/jni_runtime.cpp | 3 + libs/hwui/jni/Bitmap.cpp | 46 +-- libs/hwui/jni/GraphicsJNI.h | 31 +- libs/hwui/jni/HardwareBufferHelpers.cpp | 68 ++++ libs/hwui/jni/HardwareBufferHelpers.h | 38 ++ libs/hwui/jni/JvmErrorReporter.h | 44 +++ .../android_graphics_HardwareBufferRenderer.cpp | 183 ++++++++++ .../hwui/jni/android_graphics_HardwareRenderer.cpp | 65 +--- libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp | 67 +++- libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h | 16 +- libs/hwui/pipeline/skia/SkiaPipeline.cpp | 25 ++ libs/hwui/pipeline/skia/SkiaPipeline.h | 10 + libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp | 48 ++- libs/hwui/pipeline/skia/SkiaVulkanPipeline.h | 24 +- libs/hwui/renderthread/CanvasContext.cpp | 20 +- libs/hwui/renderthread/CanvasContext.h | 12 +- libs/hwui/renderthread/DrawFrameTask.cpp | 14 +- libs/hwui/renderthread/DrawFrameTask.h | 13 + .../hwui/renderthread/HardwareBufferRenderParams.h | 62 ++++ libs/hwui/renderthread/IRenderPipeline.h | 18 +- libs/hwui/renderthread/RenderProxy.cpp | 16 + libs/hwui/renderthread/RenderProxy.h | 4 +- libs/hwui/tests/unit/CanvasContextTests.cpp | 2 +- tests/HwAccelerationTest/AndroidManifest.xml | 9 + .../test/hwui/HardwareBufferRendererActivity.java | 86 +++++ 30 files changed, 1205 insertions(+), 157 deletions(-) create mode 100644 graphics/java/android/graphics/HardwareBufferRenderer.java create mode 100644 libs/hwui/jni/HardwareBufferHelpers.cpp create mode 100644 libs/hwui/jni/HardwareBufferHelpers.h create mode 100644 libs/hwui/jni/JvmErrorReporter.h create mode 100644 libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp create mode 100644 libs/hwui/renderthread/HardwareBufferRenderParams.h create mode 100644 tests/HwAccelerationTest/src/com/android/test/hwui/HardwareBufferRendererActivity.java (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index eb1c7f5b7f01..bca9913ff94e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -15310,6 +15310,29 @@ package android.graphics { ctor @Deprecated public EmbossMaskFilter(float[], float, float, float); } + public class HardwareBufferRenderer implements java.lang.AutoCloseable { + ctor public HardwareBufferRenderer(@NonNull android.hardware.HardwareBuffer); + method public void close(); + method public boolean isClosed(); + method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest obtainRenderRequest(); + method public void setContentRoot(@Nullable android.graphics.RenderNode); + method public void setLightSourceAlpha(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float); + method public void setLightSourceGeometry(float, float, @FloatRange(from=0.0f) float, @FloatRange(from=0.0f) float); + } + + public final class HardwareBufferRenderer.RenderRequest { + method public void draw(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest setBufferTransform(int); + method @NonNull public android.graphics.HardwareBufferRenderer.RenderRequest setColorSpace(@Nullable android.graphics.ColorSpace); + } + + public static final class HardwareBufferRenderer.RenderResult { + method @NonNull public android.hardware.SyncFence getFence(); + method public int getStatus(); + field public static final int ERROR_UNKNOWN = 1; // 0x1 + field public static final int SUCCESS = 0; // 0x0 + } + public class HardwareRenderer { ctor public HardwareRenderer(); method public void clearContent(); diff --git a/core/java/android/hardware/SyncFence.java b/core/java/android/hardware/SyncFence.java index 166001347bd4..d6052cd4c67f 100644 --- a/core/java/android/hardware/SyncFence.java +++ b/core/java/android/hardware/SyncFence.java @@ -87,8 +87,8 @@ public final class SyncFence implements AutoCloseable, Parcelable { // is well worth making. private final Runnable mCloser; - private SyncFence(@NonNull ParcelFileDescriptor wrapped) { - mNativePtr = nCreate(wrapped.detachFd()); + private SyncFence(int fileDescriptor) { + mNativePtr = nCreate(fileDescriptor); mCloser = sRegistry.registerNativeAllocation(this, mNativePtr); } @@ -136,14 +136,26 @@ public final class SyncFence implements AutoCloseable, Parcelable { } /** - * Create a new SyncFence wrapped around another descriptor. By default, all method calls are - * delegated to the wrapped descriptor. + * Create a new SyncFence wrapped around another {@link ParcelFileDescriptor}. By default, all + * method calls are delegated to the wrapped descriptor. This takes ownership of the + * {@link ParcelFileDescriptor}. * * @param wrapped The descriptor to be wrapped. * @hide */ public static @NonNull SyncFence create(@NonNull ParcelFileDescriptor wrapped) { - return new SyncFence(wrapped); + return new SyncFence(wrapped.detachFd()); + } + + /** + * Create a new SyncFence wrapped around another descriptor. The returned {@link SyncFence} + * instance takes ownership of the file descriptor. + * + * @param fileDescriptor The descriptor to be wrapped. + * @hide + */ + public static @NonNull SyncFence adopt(int fileDescriptor) { + return new SyncFence(fileDescriptor); } /** diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index 93ba23bfdf84..d7cbf74d421a 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -102,6 +102,7 @@ extern int register_android_view_KeyCharacterMap(JNIEnv* env); extern int register_android_view_KeyEvent(JNIEnv* env); extern int register_android_view_MotionEvent(JNIEnv* env); extern int register_android_view_ThreadedRenderer(JNIEnv* env); +extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); extern int register_android_view_VelocityTracker(JNIEnv* env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); diff --git a/graphics/java/android/graphics/HardwareBufferRenderer.java b/graphics/java/android/graphics/HardwareBufferRenderer.java new file mode 100644 index 000000000000..361dc594f2c6 --- /dev/null +++ b/graphics/java/android/graphics/HardwareBufferRenderer.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import android.annotation.FloatRange; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.ColorSpace.Named; +import android.hardware.HardwareBuffer; +import android.hardware.SyncFence; +import android.view.SurfaceControl; + +import libcore.util.NativeAllocationRegistry; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + *

    Creates an instance of a hardware-accelerated renderer. This is used to render a scene built + * from {@link RenderNode}s to an output {@link HardwareBuffer}. There can be as many + * HardwareBufferRenderer instances as desired.

    + * + *

    Resources & lifecycle

    + * + *

    All HardwareBufferRenderer and {@link HardwareRenderer} instances share a common render + * thread. Therefore HardwareBufferRenderer will share common resources and GPU utilization with + * hardware accelerated rendering initiated by the UI thread of an application. + * The render thread contains the GPU context & resources necessary to do GPU-accelerated + * rendering. As such, the first HardwareBufferRenderer created comes with the cost of also creating + * the associated GPU contexts, however each incremental HardwareBufferRenderer thereafter is fairly + * cheap. The expected usage is to have a HardwareBufferRenderer instance for every active {@link + * HardwareBuffer}.

    + * + * This is useful in situations where a scene built with {@link RenderNode}s can be consumed + * directly by the system compositor through + * {@link SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}. + * + * HardwareBufferRenderer will never clear contents before each draw invocation so previous contents + * in the {@link HardwareBuffer} target will be preserved across renders. + */ +public class HardwareBufferRenderer implements AutoCloseable { + + private static final ColorSpace DEFAULT_COLORSPACE = ColorSpace.get(Named.SRGB); + + private static class HardwareBufferRendererHolder { + public static final NativeAllocationRegistry REGISTRY = + NativeAllocationRegistry.createMalloced( + HardwareBufferRenderer.class.getClassLoader(), nGetFinalizer()); + } + + private final HardwareBuffer mHardwareBuffer; + private final RenderRequest mRenderRequest; + private final RenderNode mRootNode; + private final Runnable mCleaner; + + private long mProxy; + + /** + * Creates a new instance of {@link HardwareBufferRenderer} with the provided {@link + * HardwareBuffer} as the output of the rendered scene. + */ + public HardwareBufferRenderer(@NonNull HardwareBuffer buffer) { + RenderNode rootNode = RenderNode.adopt(nCreateRootRenderNode()); + rootNode.setClipToBounds(false); + mProxy = nCreateHardwareBufferRenderer(buffer, rootNode.mNativeRenderNode); + mCleaner = HardwareBufferRendererHolder.REGISTRY.registerNativeAllocation(this, mProxy); + mRenderRequest = new RenderRequest(); + mRootNode = rootNode; + mHardwareBuffer = buffer; + } + + /** + * Sets the content root to render. It is not necessary to call this whenever the content + * recording changes. Any mutations to the RenderNode content, or any of the RenderNodes + * contained within the content node, will be applied whenever a new {@link RenderRequest} is + * issued via {@link #obtainRenderRequest()} and {@link RenderRequest#draw(Executor, + * Consumer)}. + * + * @param content The content to set as the root RenderNode. If null the content root is removed + * and the renderer will draw nothing. + */ + public void setContentRoot(@Nullable RenderNode content) { + RecordingCanvas canvas = mRootNode.beginRecording(); + if (content != null) { + canvas.drawRenderNode(content); + } + mRootNode.endRecording(); + } + + /** + * Returns a {@link RenderRequest} that can be used to render into the provided {@link + * HardwareBuffer}. This is used to synchronize the RenderNode content provided by {@link + * #setContentRoot(RenderNode)}. + * + * @return An instance of {@link RenderRequest}. The instance may be reused for every frame, so + * the caller should not hold onto it for longer than a single render request. + */ + @NonNull + public RenderRequest obtainRenderRequest() { + mRenderRequest.reset(); + return mRenderRequest; + } + + /** + * Returns if the {@link HardwareBufferRenderer} has already been closed. That is + * {@link HardwareBufferRenderer#close()} has been invoked. + * @return True if the {@link HardwareBufferRenderer} has been closed, false otherwise. + */ + public boolean isClosed() { + return mProxy == 0L; + } + + /** + * Releases the resources associated with this {@link HardwareBufferRenderer} instance. **Note** + * this does not call {@link HardwareBuffer#close()} on the provided {@link HardwareBuffer} + * instance + */ + @Override + public void close() { + // Note we explicitly call this only here to clean-up potential animator state + // This is not done as part of the NativeAllocationRegistry as it would invoke animator + // callbacks on the wrong thread + nDestroyRootRenderNode(mRootNode.mNativeRenderNode); + if (mProxy != 0L) { + mCleaner.run(); + mProxy = 0L; + } + } + + /** + * Sets the center of the light source. The light source point controls the directionality and + * shape of shadows rendered by RenderNode Z & elevation. + * + *

    The light source should be setup both as part of initial configuration, and whenever + * the window moves to ensure the light source stays anchored in display space instead of in + * window space. + * + *

    This must be set at least once along with {@link #setLightSourceAlpha(float, float)} + * before shadows will work. + * + * @param lightX The X position of the light source. If unsure, a reasonable default + * is 'displayWidth / 2f - windowLeft'. + * @param lightY The Y position of the light source. If unsure, a reasonable default + * is '0 - windowTop' + * @param lightZ The Z position of the light source. Must be >= 0. If unsure, a reasonable + * default is 600dp. + * @param lightRadius The radius of the light source. Smaller radius will have sharper edges, + * larger radius will have softer shadows. If unsure, a reasonable default is 800 dp. + */ + public void setLightSourceGeometry( + float lightX, + float lightY, + @FloatRange(from = 0f) float lightZ, + @FloatRange(from = 0f) float lightRadius + ) { + validateFinite(lightX, "lightX"); + validateFinite(lightY, "lightY"); + validatePositive(lightZ, "lightZ"); + validatePositive(lightRadius, "lightRadius"); + nSetLightGeometry(mProxy, lightX, lightY, lightZ, lightRadius); + } + + /** + * Configures the ambient & spot shadow alphas. This is the alpha used when the shadow has max + * alpha, and ramps down from the values provided to zero. + * + *

    These values are typically provided by the current theme, see + * {@link android.R.attr#spotShadowAlpha} and {@link android.R.attr#ambientShadowAlpha}. + * + *

    This must be set at least once along with + * {@link #setLightSourceGeometry(float, float, float, float)} before shadows will work. + * + * @param ambientShadowAlpha The alpha for the ambient shadow. If unsure, a reasonable default + * is 0.039f. + * @param spotShadowAlpha The alpha for the spot shadow. If unsure, a reasonable default is + * 0.19f. + */ + public void setLightSourceAlpha(@FloatRange(from = 0.0f, to = 1.0f) float ambientShadowAlpha, + @FloatRange(from = 0.0f, to = 1.0f) float spotShadowAlpha) { + validateAlpha(ambientShadowAlpha, "ambientShadowAlpha"); + validateAlpha(spotShadowAlpha, "spotShadowAlpha"); + nSetLightAlpha(mProxy, ambientShadowAlpha, spotShadowAlpha); + } + + /** + * Class that contains data regarding the result of the render request. + * Consumers are to wait on the provided {@link SyncFence} before consuming the HardwareBuffer + * provided to {@link HardwareBufferRenderer} as well as verify that the status returned by + * {@link RenderResult#getStatus()} returns {@link RenderResult#SUCCESS}. + */ + public static final class RenderResult { + + /** + * Render request was completed successfully + */ + public static final int SUCCESS = 0; + + /** + * Render request failed with an unknown error + */ + public static final int ERROR_UNKNOWN = 1; + + /** @hide **/ + @IntDef(value = {SUCCESS, ERROR_UNKNOWN}) + @Retention(RetentionPolicy.SOURCE) + public @interface RenderResultStatus{} + + private final SyncFence mFence; + private final int mResultStatus; + + private RenderResult(@NonNull SyncFence fence, @RenderResultStatus int resultStatus) { + mFence = fence; + mResultStatus = resultStatus; + } + + @NonNull + public SyncFence getFence() { + return mFence; + } + + @RenderResultStatus + public int getStatus() { + return mResultStatus; + } + } + + /** + * Sets the parameters that can be used to control a render request for a {@link + * HardwareBufferRenderer}. This is not thread-safe and must not be held on to for longer than a + * single request. + */ + public final class RenderRequest { + + private ColorSpace mColorSpace = DEFAULT_COLORSPACE; + private int mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY; + + private RenderRequest() { } + + /** + * Syncs the RenderNode tree to the render thread and requests content to be drawn. This + * {@link RenderRequest} instance should no longer be used after calling this method. The + * system internally may reuse instances of {@link RenderRequest} to reduce allocation + * churn. + * + * @param executor Executor used to deliver callbacks + * @param renderCallback Callback invoked when rendering is complete. This includes a + * {@link RenderResult} that provides a {@link SyncFence} that should be waited upon for + * completion before consuming the rendered output in the provided {@link HardwareBuffer} + * instance. + * + * @throws IllegalStateException if attempt to draw is made when + * {@link HardwareBufferRenderer#isClosed()} returns true + */ + public void draw( + @NonNull Executor executor, + @NonNull Consumer renderCallback + ) { + Consumer wrapped = consumable -> executor.execute( + () -> renderCallback.accept(consumable)); + if (!isClosed()) { + nRender( + mProxy, + mTransform, + mHardwareBuffer.getWidth(), + mHardwareBuffer.getHeight(), + mColorSpace.getNativeInstance(), + wrapped); + } else { + throw new IllegalStateException("Attempt to draw with a HardwareBufferRenderer " + + "instance that has already been closed"); + } + } + + private void reset() { + mColorSpace = DEFAULT_COLORSPACE; + mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY; + } + + /** + * Configures the color space which the content should be rendered in. This affects + * how the framework will interpret the color at each pixel. The color space provided here + * must be non-null, RGB based and leverage an ICC parametric curve. The min/max values + * of the components should not reduce the numerical range compared to the previously + * assigned color space. If left unspecified, the default color space of SRGB will be used. + * + * @param colorSpace The color space the content should be rendered in. If null is provided + * the default of SRGB will be used. + */ + @NonNull + public RenderRequest setColorSpace(@Nullable ColorSpace colorSpace) { + if (colorSpace == null) { + mColorSpace = DEFAULT_COLORSPACE; + } else { + mColorSpace = colorSpace; + } + return this; + } + + /** + * Specifies a transform to be applied before content is rendered. This is useful + * for pre-rotating content for the current display orientation to increase performance + * of displaying the associated buffer. This transformation will also adjust the light + * source position for the specified rotation. + * @see SurfaceControl.Transaction#setBufferTransform(SurfaceControl, int) + */ + @NonNull + public RenderRequest setBufferTransform( + @SurfaceControl.BufferTransform int bufferTransform) { + boolean validTransform = bufferTransform == SurfaceControl.BUFFER_TRANSFORM_IDENTITY + || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_90 + || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_180 + || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_270; + if (validTransform) { + mTransform = bufferTransform; + } else { + throw new IllegalArgumentException("Invalid transform provided, must be one of" + + "the SurfaceControl.BufferTransform values"); + } + return this; + } + } + + /** + * @hide + */ + /* package */ + static native int nRender(long renderer, int transform, int width, int height, long colorSpace, + Consumer callback); + + private static native long nCreateRootRenderNode(); + + private static native void nDestroyRootRenderNode(long rootRenderNode); + + private static native long nCreateHardwareBufferRenderer(HardwareBuffer buffer, + long rootRenderNode); + + private static native void nSetLightGeometry(long bufferRenderer, float lightX, float lightY, + float lightZ, float radius); + + private static native void nSetLightAlpha(long nativeProxy, float ambientShadowAlpha, + float spotShadowAlpha); + + private static native long nGetFinalizer(); + + // Called by native + private static void invokeRenderCallback( + @NonNull Consumer callback, + int fd, + int status + ) { + callback.accept(new RenderResult(SyncFence.adopt(fd), status)); + } + + private static void validateAlpha(float alpha, String argumentName) { + if (!(alpha >= 0.0f && alpha <= 1.0f)) { + throw new IllegalArgumentException(argumentName + " must be a valid alpha, " + + alpha + " is not in the range of 0.0f to 1.0f"); + } + } + + private static void validateFinite(float f, String argumentName) { + if (!Float.isFinite(f)) { + throw new IllegalArgumentException(argumentName + " must be finite, given=" + f); + } + } + + private static void validatePositive(float f, String argumentName) { + if (!(Float.isFinite(f) && f >= 0.0f)) { + throw new IllegalArgumentException(argumentName + + " must be a finite positive, given=" + f); + } + } +} diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index d116ed828527..dd6c2bc326fd 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -338,6 +338,7 @@ cc_defaults { "jni/android_util_PathParser.cpp", "jni/Bitmap.cpp", + "jni/HardwareBufferHelpers.cpp", "jni/BitmapFactory.cpp", "jni/ByteBufferStreamAdaptor.cpp", "jni/Camera.cpp", @@ -414,6 +415,7 @@ cc_defaults { "jni/AnimatedImageDrawable.cpp", "jni/android_graphics_TextureLayer.cpp", "jni/android_graphics_HardwareRenderer.cpp", + "jni/android_graphics_HardwareBufferRenderer.cpp", "jni/BitmapRegionDecoder.cpp", "jni/GIFMovie.cpp", "jni/GraphicsStatsService.cpp", diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp index d07fc5d36ca5..c509ed4dfd21 100644 --- a/libs/hwui/apex/jni_runtime.cpp +++ b/libs/hwui/apex/jni_runtime.cpp @@ -84,6 +84,7 @@ extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_view_DisplayListCanvas(JNIEnv* env); extern int register_android_view_RenderNode(JNIEnv* env); extern int register_android_view_ThreadedRenderer(JNIEnv* env); +extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); #ifdef NDEBUG #define REG_JNI(name) { name } @@ -153,6 +154,8 @@ extern int register_android_view_ThreadedRenderer(JNIEnv* env); REG_JNI(register_android_util_PathParser), REG_JNI(register_android_view_RenderNode), REG_JNI(register_android_view_DisplayListCanvas), + REG_JNI(register_android_graphics_HardwareBufferRenderer), + REG_JNI(register_android_view_ThreadedRenderer), }; diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index 1f5a1d3362c7..10c287d1e07d 100644 --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -2,49 +2,42 @@ #define LOG_TAG "Bitmap" #include "Bitmap.h" +#include +#include + +#include "CreateJavaOutputStreamAdaptor.h" #include "Gainmap.h" #include "GraphicsJNI.h" +#include "HardwareBufferHelpers.h" #include "SkBitmap.h" #include "SkBlendMode.h" #include "SkCanvas.h" #include "SkColor.h" #include "SkColorSpace.h" #include "SkData.h" -#include "SkImageEncoder.h" #include "SkImageInfo.h" #include "SkPaint.h" -#include "SkPixelRef.h" #include "SkPixmap.h" #include "SkPoint.h" #include "SkRefCnt.h" #include "SkStream.h" #include "SkTypes.h" -#include "SkWebpEncoder.h" - - #include "android_nio_utils.h" -#include "CreateJavaOutputStreamAdaptor.h" -#include -#include -#include #ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread #include #include #include #include -#include -#include #include -#include #include #include #endif #include #include + #include -#include #define DEBUG_PARCEL 0 @@ -1205,18 +1198,11 @@ static jobject Bitmap_copyPreserveInternalConfig(JNIEnv* env, jobject, jlong bit return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(false)); } -#ifdef __ANDROID__ // Layoutlib does not support graphic buffer -typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject); -AHB_from_HB AHardwareBuffer_fromHardwareBuffer; - -typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*); -AHB_to_HB AHardwareBuffer_toHardwareBuffer; -#endif - static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer, jlong colorSpacePtr) { #ifdef __ANDROID__ // Layoutlib does not support graphic buffer - AHardwareBuffer* buffer = AHardwareBuffer_fromHardwareBuffer(env, hardwareBuffer); + AHardwareBuffer* buffer = uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer( + env, hardwareBuffer); sk_sp bitmap = Bitmap::createFrom(buffer, GraphicsJNI::getNativeColorSpace(colorSpacePtr)); if (!bitmap.get()) { @@ -1239,7 +1225,8 @@ static jobject Bitmap_getHardwareBuffer(JNIEnv* env, jobject, jlong bitmapPtr) { } Bitmap& bitmap = bitmapHandle->bitmap(); - return AHardwareBuffer_toHardwareBuffer(env, bitmap.hardwareBuffer()); + return uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer( + env, bitmap.hardwareBuffer()); #else return nullptr; #endif @@ -1347,18 +1334,7 @@ int register_android_graphics_Bitmap(JNIEnv* env) gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J"); gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V"); gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V"); - -#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel - void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); - AHardwareBuffer_fromHardwareBuffer = - (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer"); - LOG_ALWAYS_FATAL_IF(AHardwareBuffer_fromHardwareBuffer == nullptr, - "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!"); - - AHardwareBuffer_toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer"); - LOG_ALWAYS_FATAL_IF(AHardwareBuffer_toHardwareBuffer == nullptr, - " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!"); -#endif + uirenderer::HardwareBufferHelpers::init(); return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods, NELEM(gBitmapMethods)); } diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h index 2c556af8a639..6b983c10bdca 100644 --- a/libs/hwui/jni/GraphicsJNI.h +++ b/libs/hwui/jni/GraphicsJNI.h @@ -2,19 +2,18 @@ #define _ANDROID_GRAPHICS_GRAPHICS_JNI_H_ #include +#include +#include -#include "Bitmap.h" #include "BRDAllocator.h" +#include "Bitmap.h" #include "SkBitmap.h" #include "SkCodec.h" -#include "SkPixelRef.h" +#include "SkColorSpace.h" #include "SkMallocPixelRef.h" +#include "SkPixelRef.h" #include "SkPoint.h" #include "SkRect.h" -#include "SkColorSpace.h" -#include -#include - #include "graphics_jni_helpers.h" class SkCanvas; @@ -333,6 +332,26 @@ private: int fLen; }; +class JGlobalRefHolder { +public: + JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {} + + virtual ~JGlobalRefHolder() { + GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject); + mObject = nullptr; + } + + jobject object() { return mObject; } + JavaVM* vm() { return mVm; } + +private: + JGlobalRefHolder(const JGlobalRefHolder&) = delete; + void operator=(const JGlobalRefHolder&) = delete; + + JavaVM* mVm; + jobject mObject; +}; + void doThrowNPE(JNIEnv* env); void doThrowAIOOBE(JNIEnv* env); // Array Index Out Of Bounds Exception void doThrowIAE(JNIEnv* env, const char* msg = NULL); // Illegal Argument diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp new file mode 100644 index 000000000000..7e3f771b6b3d --- /dev/null +++ b/libs/hwui/jni/HardwareBufferHelpers.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "HardwareBufferHelpers.h" + +#include +#include + +#ifdef __ANDROID__ +typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject); +typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*); +static AHB_from_HB fromHardwareBuffer = nullptr; +static AHB_to_HB toHardwareBuffer = nullptr; +#endif + +void android::uirenderer::HardwareBufferHelpers::init() { +#ifdef __ANDROID__ // Layoutlib does not support graphic buffer or parcel + void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); + fromHardwareBuffer = (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer"); + LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr, + "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!"); + + toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer"); + LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr, + " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!"); +#endif +} + +AHardwareBuffer* android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer( + JNIEnv* env, jobject hardwarebuffer) { +#ifdef __ANDROID__ + LOG_ALWAYS_FATAL_IF(fromHardwareBuffer == nullptr, + "Failed to find symbol AHardwareBuffer_fromHardwareBuffer, did you forget " + "to call HardwareBufferHelpers::init?"); + return fromHardwareBuffer(env, hardwarebuffer); +#else + ALOGE("ERROR attempting to invoke AHardwareBuffer_fromHardwareBuffer on non Android " + "configuration"); + return nullptr; +#endif +} + +jobject android::uirenderer::HardwareBufferHelpers::AHardwareBuffer_toHardwareBuffer( + JNIEnv* env, AHardwareBuffer* ahardwarebuffer) { +#ifdef __ANDROID__ + LOG_ALWAYS_FATAL_IF(toHardwareBuffer == nullptr, + "Failed to find symbol AHardwareBuffer_toHardwareBuffer, did you forget to " + "call HardwareBufferHelpers::init?"); + return toHardwareBuffer(env, ahardwarebuffer); +#else + ALOGE("ERROR attempting to invoke AHardwareBuffer_toHardwareBuffer on non Android " + "configuration"); + return nullptr; +#endif +} \ No newline at end of file diff --git a/libs/hwui/jni/HardwareBufferHelpers.h b/libs/hwui/jni/HardwareBufferHelpers.h new file mode 100644 index 000000000000..326babfb0b34 --- /dev/null +++ b/libs/hwui/jni/HardwareBufferHelpers.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HARDWAREBUFFER_JNI_HELPERS_H +#define HARDWAREBUFFER_JNI_HELPERS_H + +#include +#include + +namespace android { +namespace uirenderer { + +class HardwareBufferHelpers { +public: + static void init(); + static AHardwareBuffer* AHardwareBuffer_fromHardwareBuffer(JNIEnv*, jobject); + static jobject AHardwareBuffer_toHardwareBuffer(JNIEnv*, AHardwareBuffer*); + +private: + HardwareBufferHelpers() = default; // not to be instantiated +}; + +} // namespace uirenderer +} // namespace android + +#endif // HARDWAREBUFFER_JNI_HELPERS_H \ No newline at end of file diff --git a/libs/hwui/jni/JvmErrorReporter.h b/libs/hwui/jni/JvmErrorReporter.h new file mode 100644 index 000000000000..5e10b9d93275 --- /dev/null +++ b/libs/hwui/jni/JvmErrorReporter.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef JVMERRORREPORTER_H +#define JVMERRORREPORTER_H + +#include +#include +#include + +#include "GraphicsJNI.h" + +namespace android { +namespace uirenderer { + +class JvmErrorReporter : public android::uirenderer::ErrorHandler { +public: + JvmErrorReporter(JNIEnv* env) { env->GetJavaVM(&mVm); } + + virtual void onError(const std::string& message) override { + JNIEnv* env = GraphicsJNI::getJNIEnv(); + jniThrowException(env, "java/lang/IllegalStateException", message.c_str()); + } + +private: + JavaVM* mVm; +}; + +} // namespace uirenderer +} // namespace android + +#endif // JVMERRORREPORTER_H diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp new file mode 100644 index 000000000000..3e453e65ae92 --- /dev/null +++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#undef LOG_TAG +#define LOG_TAG "HardwareBufferRenderer" +#define ATRACE_TAG ATRACE_TAG_VIEW + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "HardwareBufferHelpers.h" +#include "JvmErrorReporter.h" + +namespace android { + +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; + +struct { + jclass clazz; + jmethodID invokeRenderCallback; +} gHardwareBufferRendererClassInfo; + +static RenderCallback createRenderCallback(JNIEnv* env, jobject releaseCallback) { + if (releaseCallback == nullptr) return nullptr; + + JavaVM* vm = nullptr; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); + auto globalCallbackRef = + std::make_shared(vm, env->NewGlobalRef(releaseCallback)); + return [globalCallbackRef](android::base::unique_fd&& fd, int status) { + GraphicsJNI::getJNIEnv()->CallStaticVoidMethod( + gHardwareBufferRendererClassInfo.clazz, + gHardwareBufferRendererClassInfo.invokeRenderCallback, globalCallbackRef->object(), + reinterpret_cast(fd.release()), reinterpret_cast(status)); + }; +} + +static long android_graphics_HardwareBufferRenderer_createRootNode(JNIEnv* env, jobject) { + auto* node = new RootRenderNode(std::make_unique(env)); + node->incStrong(nullptr); + node->setName("RootRenderNode"); + return reinterpret_cast(node); +} + +static void android_graphics_hardwareBufferRenderer_destroyRootNode(JNIEnv*, jobject, + jlong renderNodePtr) { + auto* node = reinterpret_cast(renderNodePtr); + node->destroy(); +} + +static long android_graphics_HardwareBufferRenderer_create(JNIEnv* env, jobject, jobject buffer, + jlong renderNodePtr) { + auto* hardwareBuffer = HardwareBufferHelpers::AHardwareBuffer_fromHardwareBuffer(env, buffer); + auto* rootRenderNode = reinterpret_cast(renderNodePtr); + ContextFactoryImpl factory(rootRenderNode); + auto* proxy = new RenderProxy(true, rootRenderNode, &factory); + proxy->setHardwareBuffer(hardwareBuffer); + return (jlong)proxy; +} + +static void HardwareBufferRenderer_destroy(jlong renderProxy) { + auto* proxy = reinterpret_cast(renderProxy); + delete proxy; +} + +static SkMatrix createMatrixFromBufferTransform(SkScalar width, SkScalar height, int transform) { + auto matrix = SkMatrix(); + switch (transform) { + case ANATIVEWINDOW_TRANSFORM_ROTATE_90: + matrix.setRotate(90); + matrix.postTranslate(width, 0); + break; + case ANATIVEWINDOW_TRANSFORM_ROTATE_180: + matrix.setRotate(180); + matrix.postTranslate(width, height); + break; + case ANATIVEWINDOW_TRANSFORM_ROTATE_270: + matrix.setRotate(270); + matrix.postTranslate(0, width); + break; + default: + ALOGE("Invalid transform provided. Transform should be validated from" + "the java side. Leveraging identity transform as a fallback"); + [[fallthrough]]; + case ANATIVEWINDOW_TRANSFORM_IDENTITY: + break; + } + return matrix; +} + +static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, jlong renderProxy, + jint transform, jint width, jint height, + jlong colorspacePtr, jobject consumer) { + auto* proxy = reinterpret_cast(renderProxy); + auto skWidth = static_cast(width); + auto skHeight = static_cast(height); + auto matrix = createMatrixFromBufferTransform(skWidth, skHeight, transform); + auto colorSpace = GraphicsJNI::getNativeColorSpace(colorspacePtr); + proxy->setHardwareBufferRenderParams( + HardwareBufferRenderParams(matrix, colorSpace, createRenderCallback(env, consumer))); + nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC); + UiFrameInfoBuilder(proxy->frameInfo()) + .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID, + UiFrameInfoBuilder::UNKNOWN_DEADLINE, + UiFrameInfoBuilder::UNKNOWN_FRAME_INTERVAL) + .addFlag(FrameInfoFlags::SurfaceCanvas); + return proxy->syncAndDrawFrame(); +} + +static void android_graphics_HardwareBufferRenderer_setLightGeometry(JNIEnv*, jobject, + jlong renderProxyPtr, + jfloat lightX, jfloat lightY, + jfloat lightZ, + jfloat lightRadius) { + auto* proxy = reinterpret_cast(renderProxyPtr); + proxy->setLightGeometry((Vector3){lightX, lightY, lightZ}, lightRadius); +} + +static void android_graphics_HardwareBufferRenderer_setLightAlpha(JNIEnv* env, jobject, + jlong renderProxyPtr, + jfloat ambientShadowAlpha, + jfloat spotShadowAlpha) { + auto* proxy = reinterpret_cast(renderProxyPtr); + proxy->setLightAlpha((uint8_t)(255 * ambientShadowAlpha), (uint8_t)(255 * spotShadowAlpha)); +} + +static jlong android_graphics_HardwareBufferRenderer_getFinalizer() { + return static_cast(reinterpret_cast(&HardwareBufferRenderer_destroy)); +} + +// ---------------------------------------------------------------------------- +// JNI Glue +// ---------------------------------------------------------------------------- + +const char* const kClassPathName = "android/graphics/HardwareBufferRenderer"; + +static const JNINativeMethod gMethods[] = { + {"nCreateHardwareBufferRenderer", "(Landroid/hardware/HardwareBuffer;J)J", + (void*)android_graphics_HardwareBufferRenderer_create}, + {"nRender", "(JIIIJLjava/util/function/Consumer;)I", + (void*)android_graphics_HardwareBufferRenderer_render}, + {"nCreateRootRenderNode", "()J", + (void*)android_graphics_HardwareBufferRenderer_createRootNode}, + {"nSetLightGeometry", "(JFFFF)V", + (void*)android_graphics_HardwareBufferRenderer_setLightGeometry}, + {"nSetLightAlpha", "(JFF)V", (void*)android_graphics_HardwareBufferRenderer_setLightAlpha}, + {"nGetFinalizer", "()J", (void*)android_graphics_HardwareBufferRenderer_getFinalizer}, + {"nDestroyRootRenderNode", "(J)V", + (void*)android_graphics_hardwareBufferRenderer_destroyRootNode}}; + +int register_android_graphics_HardwareBufferRenderer(JNIEnv* env) { + jclass hardwareBufferRendererClazz = + FindClassOrDie(env, "android/graphics/HardwareBufferRenderer"); + gHardwareBufferRendererClassInfo.clazz = hardwareBufferRendererClazz; + gHardwareBufferRendererClassInfo.invokeRenderCallback = + GetStaticMethodIDOrDie(env, hardwareBufferRendererClazz, "invokeRenderCallback", + "(Ljava/util/function/Consumer;II)V"); + HardwareBufferHelpers::init(); + return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); +} + +} // namespace android \ No newline at end of file diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index a30fc496b2fd..d6aad7d3eede 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -56,6 +56,7 @@ #include #include +#include "JvmErrorReporter.h" #include "android_graphics_HardwareRendererObserver.h" namespace android { @@ -93,35 +94,12 @@ struct { jmethodID getDestinationBitmap; } gCopyRequest; -static JNIEnv* getenv(JavaVM* vm) { - JNIEnv* env; - if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { - LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm); - } - return env; -} - typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface); ANW_fromSurface fromSurface; -class JvmErrorReporter : public ErrorHandler { -public: - JvmErrorReporter(JNIEnv* env) { - env->GetJavaVM(&mVm); - } - - virtual void onError(const std::string& message) override { - JNIEnv* env = getenv(mVm); - jniThrowException(env, "java/lang/IllegalStateException", message.c_str()); - } -private: - JavaVM* mVm; -}; - class FrameCommitWrapper : public LightRefBase { public: explicit FrameCommitWrapper(JNIEnv* env, jobject jobject) { - env->GetJavaVM(&mVm); mObject = env->NewGlobalRef(jobject); LOG_ALWAYS_FATAL_IF(!mObject, "Failed to make global ref"); } @@ -131,19 +109,18 @@ public: void onFrameCommit(bool didProduceBuffer) { if (mObject) { ATRACE_FORMAT("frameCommit success=%d", didProduceBuffer); - getenv(mVm)->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit, - didProduceBuffer); + GraphicsJNI::getJNIEnv()->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit, + didProduceBuffer); releaseObject(); } } private: - JavaVM* mVm; jobject mObject; void releaseObject() { if (mObject) { - getenv(mVm)->DeleteGlobalRef(mObject); + GraphicsJNI::getJNIEnv()->DeleteGlobalRef(mObject); mObject = nullptr; } } @@ -449,26 +426,6 @@ static void android_view_ThreadedRenderer_forceDrawNextFrame(JNIEnv* env, jobjec proxy->forceDrawNextFrame(); } -class JGlobalRefHolder { -public: - JGlobalRefHolder(JavaVM* vm, jobject object) : mVm(vm), mObject(object) {} - - virtual ~JGlobalRefHolder() { - getenv(mVm)->DeleteGlobalRef(mObject); - mObject = nullptr; - } - - jobject object() { return mObject; } - JavaVM* vm() { return mVm; } - -private: - JGlobalRefHolder(const JGlobalRefHolder&) = delete; - void operator=(const JGlobalRefHolder&) = delete; - - JavaVM* mVm; - jobject mObject; -}; - using TextureMap = std::unordered_map>; struct PictureCaptureState { @@ -584,7 +541,7 @@ static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* auto pictureState = std::make_shared(); proxy->setPictureCapturedCallback([globalCallbackRef, pictureState](sk_sp&& picture) { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); Picture* wrapper = new PictureWrapper{std::move(picture), pictureState}; env->CallStaticVoidMethod(gHardwareRenderer.clazz, gHardwareRenderer.invokePictureCapturedCallback, @@ -606,7 +563,7 @@ static void android_view_ThreadedRenderer_setASurfaceTransactionCallback( vm, env->NewGlobalRef(aSurfaceTransactionCallback)); proxy->setASurfaceTransactionCallback( [globalCallbackRef](int64_t transObj, int64_t scObj, int64_t frameNr) -> bool { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); jboolean ret = env->CallBooleanMethod( globalCallbackRef->object(), gASurfaceTransactionCallback.onMergeTransaction, @@ -628,7 +585,7 @@ static void android_view_ThreadedRenderer_setPrepareSurfaceControlForWebviewCall auto globalCallbackRef = std::make_shared(vm, env->NewGlobalRef(callback)); proxy->setPrepareSurfaceControlForWebviewCallback([globalCallbackRef]() { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); env->CallVoidMethod(globalCallbackRef->object(), gPrepareSurfaceControlForWebviewCallback.prepare); }); @@ -647,7 +604,7 @@ static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env, env->NewGlobalRef(frameCallback)); proxy->setFrameCallback([globalCallbackRef](int32_t syncResult, int64_t frameNr) -> std::function { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); ScopedLocalRef frameCommitCallback( env, env->CallObjectMethod( globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw, @@ -686,7 +643,7 @@ static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env, auto globalCallbackRef = std::make_shared(vm, env->NewGlobalRef(callback)); proxy->setFrameCompleteCallback([globalCallbackRef]() { - JNIEnv* env = getenv(globalCallbackRef->vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); env->CallVoidMethod(globalCallbackRef->object(), gFrameCompleteCallback.onFrameComplete); }); @@ -699,7 +656,7 @@ public: : CopyRequest(srcRect), mRefHolder(vm, jCopyRequest) {} virtual SkBitmap getDestinationBitmap(int srcWidth, int srcHeight) override { - JNIEnv* env = getenv(mRefHolder.vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); jlong bitmapPtr = env->CallLongMethod( mRefHolder.object(), gCopyRequest.getDestinationBitmap, srcWidth, srcHeight); SkBitmap bitmap; @@ -708,7 +665,7 @@ public: } virtual void onCopyFinished(CopyResult result) override { - JNIEnv* env = getenv(mRefHolder.vm()); + JNIEnv* env = GraphicsJNI::getJNIEnv(); env->CallVoidMethod(mRefHolder.object(), gCopyRequest.onCopyFinished, static_cast(result)); } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 19cd7bdd6fcb..202a62cf320c 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -55,7 +55,9 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() { MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { // In case the surface was destroyed (e.g. a previous trimMemory call) we // need to recreate it here. - if (!isSurfaceReady() && mNativeWindow) { + if (mHardwareBuffer) { + mRenderThread.requireGlContext(); + } else if (!isSurfaceReady() && mNativeWindow) { setSurface(mNativeWindow.get(), mSwapBehavior); } @@ -67,17 +69,24 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { } Frame SkiaOpenGLPipeline::getFrame() { - LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, - "drawRenderNode called on a context with no surface!"); - return mEglManager.beginFrame(mEglSurface); + if (mHardwareBuffer) { + AHardwareBuffer_Desc description; + AHardwareBuffer_describe(mHardwareBuffer, &description); + return Frame(description.width, description.height, 0); + } else { + LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, + "drawRenderNode called on a context with no surface!"); + return mEglManager.beginFrame(mEglSurface); + } } IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector>& renderNodes, FrameInfoVisualizer* profiler) { - if (!isCapturingSkp()) { + const std::vector>& renderNodes, FrameInfoVisualizer* profiler, + const HardwareBufferRenderParams& bufferParams) { + if (!isCapturingSkp() && !mHardwareBuffer) { mEglManager.damageFrame(frame, dirty); } @@ -104,13 +113,25 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( SkSurfaceProps props(0, kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); - sk_sp surface(SkSurface::MakeFromBackendRenderTarget( - mRenderThread.getGrContext(), backendRT, this->getSurfaceOrigin(), colorType, - mSurfaceColorSpace, &props)); + sk_sp surface; + SkMatrix preTransform; + if (mHardwareBuffer) { + surface = getBufferSkSurface(bufferParams); + preTransform = bufferParams.getTransform(); + } else { + surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT, + getSurfaceOrigin(), colorType, + mSurfaceColorSpace, &props); + preTransform = SkMatrix::I(); + } - LightingInfo::updateLighting(lightGeometry, lightInfo); + SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y); + LightGeometry localGeometry = lightGeometry; + localGeometry.center.x = lightCenter.fX; + localGeometry.center.y = lightCenter.fY; + LightingInfo::updateLighting(localGeometry, lightInfo); renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, - SkMatrix::I()); + preTransform); // Draw visual debugging features if (CC_UNLIKELY(Properties::showDirtyRegions || @@ -142,6 +163,10 @@ bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect // metrics the frame was swapped at this point currentFrameInfo->markSwapBuffers(); + if (mHardwareBuffer) { + return false; + } + *requireSwap = drew || mEglManager.damageRequiresSwap(); if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) { @@ -197,6 +222,26 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh return false; } +[[nodiscard]] android::base::unique_fd SkiaOpenGLPipeline::flush() { + int fence = -1; + EGLSyncKHR sync = EGL_NO_SYNC_KHR; + mEglManager.createReleaseFence(true, &sync, &fence); + // If a sync object is returned here then the device does not support native + // fences, we block on the returned sync and return -1 as a file descriptor + if (sync != EGL_NO_SYNC_KHR) { + EGLDisplay display = mEglManager.eglDisplay(); + EGLint result = eglClientWaitSyncKHR(display, sync, 0, 1000000000); + if (result == EGL_FALSE) { + ALOGE("EglManager::createReleaseFence: error waiting for previous fence: %#x", + eglGetError()); + } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { + ALOGE("EglManager::createReleaseFence: timeout waiting for previous fence"); + } + eglDestroySyncKHR(display, sync); + } + return android::base::unique_fd(fence); +} + bool SkiaOpenGLPipeline::isSurfaceReady() { return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE); } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index a80c613697f2..940d6bfdb83c 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -21,6 +21,7 @@ #include "SkiaPipeline.h" #include "renderstate/RenderState.h" +#include "renderthread/HardwareBufferRenderParams.h" namespace android { @@ -36,19 +37,18 @@ public: renderthread::MakeCurrentResult makeCurrent() override; renderthread::Frame getFrame() override; - renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame, - const SkRect& screenDirty, const SkRect& dirty, - const LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, - const LightInfo& lightInfo, - const std::vector >& renderNodes, - FrameInfoVisualizer* profiler) override; + renderthread::IRenderPipeline::DrawResult draw( + const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, + const std::vector >& renderNodes, FrameInfoVisualizer* profiler, + const renderthread::HardwareBufferRenderParams& bufferParams) override; GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; } bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override; + [[nodiscard]] android::base::unique_fd flush() override; void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 09c3120a9f71..2017eb6eb7da 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -605,6 +605,31 @@ void SkiaPipeline::dumpResourceCacheUsage() const { ALOGD("%s", log.c_str()); } +void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) { + if (mHardwareBuffer) { + AHardwareBuffer_release(mHardwareBuffer); + mHardwareBuffer = nullptr; + } + + if (buffer) { + AHardwareBuffer_acquire(buffer); + mHardwareBuffer = buffer; + } +} + +sk_sp SkiaPipeline::getBufferSkSurface( + const renderthread::HardwareBufferRenderParams& bufferParams) { + auto bufferColorSpace = bufferParams.getColorSpace(); + if (mBufferSurface == nullptr || mBufferColorSpace == nullptr || + !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) { + mBufferSurface = SkSurface::MakeFromAHardwareBuffer( + mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin, + bufferColorSpace, nullptr, true); + mBufferColorSpace = bufferColorSpace; + } + return mBufferSurface; +} + void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { mColorMode = colorMode; switch (colorMode) { diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index b7d799f705b3..befee8989383 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -20,9 +20,11 @@ #include #include #include + #include "Lighting.h" #include "hwui/AnimatedImageDrawable.h" #include "renderthread/CanvasContext.h" +#include "renderthread/HardwareBufferRenderParams.h" #include "renderthread/IRenderPipeline.h" class SkFILEWStream; @@ -73,14 +75,22 @@ public: mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None; } + virtual void setHardwareBuffer(AHardwareBuffer* buffer) override; + bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; } void setTargetSdrHdrRatio(float ratio) override; protected: + sk_sp getBufferSkSurface( + const renderthread::HardwareBufferRenderParams& bufferParams); void dumpResourceCacheUsage() const; renderthread::RenderThread& mRenderThread; + AHardwareBuffer* mHardwareBuffer = nullptr; + sk_sp mBufferSurface = nullptr; + sk_sp mBufferColorSpace = nullptr; + ColorMode mColorMode = ColorMode::Default; SkColorType mSurfaceColorType; sk_sp mSurfaceColorSpace; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index a8c752f0e0b7..99298bc0fe9b 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -57,37 +57,55 @@ VulkanManager& SkiaVulkanPipeline::vulkanManager() { MakeCurrentResult SkiaVulkanPipeline::makeCurrent() { // In case the surface was destroyed (e.g. a previous trimMemory call) we // need to recreate it here. - if (!isSurfaceReady() && mNativeWindow) { + if (mHardwareBuffer) { + mRenderThread.requireVkContext(); + } else if (!isSurfaceReady() && mNativeWindow) { setSurface(mNativeWindow.get(), SwapBehavior::kSwap_default); } return isContextReady() ? MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed; } Frame SkiaVulkanPipeline::getFrame() { - LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, "getFrame() called on a context with no surface!"); - return vulkanManager().dequeueNextBuffer(mVkSurface); + if (mHardwareBuffer) { + AHardwareBuffer_Desc description; + AHardwareBuffer_describe(mHardwareBuffer, &description); + return Frame(description.width, description.height, 0); + } else { + LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, + "getFrame() called on a context with no surface!"); + return vulkanManager().dequeueNextBuffer(mVkSurface); + } } IRenderPipeline::DrawResult SkiaVulkanPipeline::draw( const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, - const std::vector>& renderNodes, FrameInfoVisualizer* profiler) { - sk_sp backBuffer = mVkSurface->getCurrentSkSurface(); + const std::vector>& renderNodes, FrameInfoVisualizer* profiler, + const HardwareBufferRenderParams& bufferParams) { + sk_sp backBuffer; + SkMatrix preTransform; + if (mHardwareBuffer) { + backBuffer = getBufferSkSurface(bufferParams); + preTransform = bufferParams.getTransform(); + } else { + backBuffer = mVkSurface->getCurrentSkSurface(); + preTransform = mVkSurface->getCurrentPreTransform(); + } + if (backBuffer.get() == nullptr) { return {false, -1}; } // update the coordinates of the global light position based on surface rotation - SkPoint lightCenter = mVkSurface->getCurrentPreTransform().mapXY(lightGeometry.center.x, - lightGeometry.center.y); + SkPoint lightCenter = preTransform.mapXY(lightGeometry.center.x, lightGeometry.center.y); LightGeometry localGeometry = lightGeometry; localGeometry.center.x = lightCenter.fX; localGeometry.center.y = lightCenter.fY; LightingInfo::updateLighting(localGeometry, lightInfo); renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer, - mVkSurface->getCurrentPreTransform()); + preTransform); // Draw visual debugging features if (CC_UNLIKELY(Properties::showDirtyRegions || @@ -116,12 +134,16 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw( bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) { - *requireSwap = drew; - // Even if we decided to cancel the frame, from the perspective of jank // metrics the frame was swapped at this point currentFrameInfo->markSwapBuffers(); + if (mHardwareBuffer) { + return false; + } + + *requireSwap = drew; + if (*requireSwap) { vulkanManager().swapBuffers(mVkSurface, screenDirty); } @@ -137,6 +159,12 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { void SkiaVulkanPipeline::onStop() {} +[[nodiscard]] android::base::unique_fd SkiaVulkanPipeline::flush() { + int fence = -1; + vulkanManager().createReleaseFence(&fence, mRenderThread.getGrContext()); + return android::base::unique_fd(fence); +} + // We can safely ignore the swap behavior because VkManager will always operate // in a mode equivalent to EGLManager::SwapBehavior::kBufferAge bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior /*swapBehavior*/) { diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index e0884a8390a6..d921ddb0d0fb 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -16,14 +16,13 @@ #pragma once +#include "SkRefCnt.h" #include "SkiaPipeline.h" +#include "renderstate/RenderState.h" +#include "renderthread/HardwareBufferRenderParams.h" #include "renderthread/VulkanManager.h" #include "renderthread/VulkanSurface.h" -#include "renderstate/RenderState.h" - -#include "SkRefCnt.h" - class SkBitmap; struct SkRect; @@ -38,18 +37,18 @@ public: renderthread::MakeCurrentResult makeCurrent() override; renderthread::Frame getFrame() override; - renderthread::IRenderPipeline::DrawResult draw(const renderthread::Frame& frame, - const SkRect& screenDirty, const SkRect& dirty, - const LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, - const LightInfo& lightInfo, - const std::vector >& renderNodes, - FrameInfoVisualizer* profiler) override; + renderthread::IRenderPipeline::DrawResult draw( + const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, + const std::vector >& renderNodes, FrameInfoVisualizer* profiler, + const renderthread::HardwareBufferRenderParams& bufferParams) override; GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; } bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; + [[nodiscard]] android::base::unique_fd flush() override; + bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override; void onStop() override; bool isSurfaceReady() override; @@ -66,7 +65,6 @@ protected: private: renderthread::VulkanManager& vulkanManager(); - renderthread::VulkanSurface* mVkSurface = nullptr; sp mNativeWindow; }; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index c0f30865d507..ee1c2ec81a91 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -153,6 +153,7 @@ void CanvasContext::removeRenderNode(RenderNode* node) { void CanvasContext::destroy() { stopDrawing(); + setHardwareBuffer(nullptr); setSurface(nullptr); setSurfaceControl(nullptr); freePrefetchedLayers(); @@ -176,6 +177,19 @@ static void setBufferCount(ANativeWindow* window) { native_window_set_buffer_count(window, bufferCount); } +void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) { + if (mHardwareBuffer) { + AHardwareBuffer_release(mHardwareBuffer); + mHardwareBuffer = nullptr; + } + + if (buffer) { + AHardwareBuffer_acquire(buffer); + mHardwareBuffer = buffer; + } + mRenderPipeline->setHardwareBuffer(mHardwareBuffer); +} + void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) { ATRACE_CALL(); @@ -261,7 +275,7 @@ void CanvasContext::setStopped(bool stopped) { mRenderThread.removeFrameCallback(this); mRenderPipeline->onStop(); mRenderThread.cacheManager().onContextStopped(this); - } else if (mIsDirty && hasSurface()) { + } else if (mIsDirty && hasOutputTarget()) { mRenderThread.postFrameCallback(this); } } @@ -425,7 +439,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy mIsDirty = true; - if (CC_UNLIKELY(!hasSurface())) { + if (CC_UNLIKELY(!hasOutputTarget())) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); info.out.canDrawThisFrame = false; return; @@ -570,7 +584,7 @@ void CanvasContext::draw() { std::scoped_lock lock(mFrameMetricsReporterMutex); drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds, mOpaque, - mLightInfo, mRenderNodes, &(profiler())); + mLightInfo, mRenderNodes, &(profiler()), mBufferParams); } uint64_t frameCompleteNr = getFrameNumber(); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 0f6b736e7edf..a811670e8176 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -125,12 +125,13 @@ public: // Won't take effect until next EGLSurface creation void setSwapBehavior(SwapBehavior swapBehavior); + void setHardwareBuffer(AHardwareBuffer* buffer); void setSurface(ANativeWindow* window, bool enableTimeout = true); void setSurfaceControl(ASurfaceControl* surfaceControl); bool pauseSurface(); void setStopped(bool stopped); - bool isStopped() { return mStopped || !hasSurface(); } - bool hasSurface() const { return mNativeSurface.get(); } + bool isStopped() { return mStopped || !hasOutputTarget(); } + bool hasOutputTarget() const { return mNativeSurface.get() || mHardwareBuffer; } void allocateBuffers(); void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); @@ -208,6 +209,10 @@ public: mASurfaceTransactionCallback = callback; } + void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) { + mBufferParams = params; + } + bool mergeTransaction(ASurfaceTransaction* transaction, ASurfaceControl* control); void setPrepareSurfaceControlForWebviewCallback(const std::function& callback) { @@ -262,6 +267,9 @@ private: int32_t mLastFrameHeight = 0; RenderThread& mRenderThread; + + AHardwareBuffer* mHardwareBuffer = nullptr; + HardwareBufferRenderParams mBufferParams; std::unique_ptr mNativeSurface; // The SurfaceControl reference is passed from ViewRootImpl, can be set to // NULL to remove the reference diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index ccdf715e95d8..fab2f46e91c3 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -26,6 +26,7 @@ #include "../Properties.h" #include "../RenderNode.h" #include "CanvasContext.h" +#include "HardwareBufferRenderParams.h" #include "RenderThread.h" namespace android { @@ -92,6 +93,9 @@ void DrawFrameTask::run() { mContext->setSyncDelayDuration(systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued); mContext->setTargetSdrHdrRatio(mRenderSdrHdrRatio); + auto hardwareBufferParams = mHardwareBufferParams; + mContext->setHardwareBufferRenderParams(hardwareBufferParams); + IRenderPipeline* pipeline = mContext->getRenderPipeline(); bool canUnblockUiThread; bool canDrawThisFrame; { @@ -151,6 +155,11 @@ void DrawFrameTask::run() { if (!canUnblockUiThread) { unblockUiThread(); } + + if (pipeline->hasHardwareBuffer()) { + auto fence = pipeline->flush(); + hardwareBufferParams.invokeRenderCallback(std::move(fence), 0); + } } bool DrawFrameTask::syncFrameState(TreeInfo& info) { @@ -176,8 +185,9 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { // This is after the prepareTree so that any pending operations // (RenderNode tree state, prefetched layers, etc...) will be flushed. - if (CC_UNLIKELY(!mContext->hasSurface() || !canDraw)) { - if (!mContext->hasSurface()) { + bool hasTarget = mContext->hasOutputTarget(); + if (CC_UNLIKELY(!hasTarget || !canDraw)) { + if (!hasTarget) { mSyncResult |= SyncResult::LostSurfaceRewardIfFound; } else { // If we have a surface but can't draw we must be stopped diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index 4be8f6bc348a..4130d4abe09e 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -27,8 +27,16 @@ #include "../Rect.h" #include "../TreeInfo.h" #include "RenderTask.h" +#include "SkColorSpace.h" +#include "SwapBehavior.h" +#include "utils/TimeUtils.h" +#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration +#include +#endif +#include "HardwareBufferRenderParams.h" namespace android { + namespace uirenderer { class DeferredLayerUpdater; @@ -88,6 +96,10 @@ public: void forceDrawNextFrame() { mForceDrawFrame = true; } + void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) { + mHardwareBufferParams = params; + } + void setRenderSdrHdrRatio(float ratio) { mRenderSdrHdrRatio = ratio; } private: @@ -114,6 +126,7 @@ private: int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE]; + HardwareBufferRenderParams mHardwareBufferParams; std::function(int32_t, int64_t)> mFrameCallback; std::function mFrameCommitCallback; std::function mFrameCompleteCallback; diff --git a/libs/hwui/renderthread/HardwareBufferRenderParams.h b/libs/hwui/renderthread/HardwareBufferRenderParams.h new file mode 100644 index 000000000000..91fe3f6cf273 --- /dev/null +++ b/libs/hwui/renderthread/HardwareBufferRenderParams.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HARDWAREBUFFERRENDERER_H_ +#define HARDWAREBUFFERRENDERER_H_ + +#include +#include + +#include "SkColorSpace.h" +#include "SkMatrix.h" +#include "SkSurface.h" + +namespace android { +namespace uirenderer { +namespace renderthread { + +using namespace android::uirenderer::renderthread; + +using RenderCallback = std::function; + +class RenderProxy; + +class HardwareBufferRenderParams { +public: + HardwareBufferRenderParams() = default; + HardwareBufferRenderParams(const SkMatrix& transform, const sk_sp& colorSpace, + RenderCallback&& callback) + : mTransform(transform) + , mColorSpace(colorSpace) + , mRenderCallback(std::move(callback)) {} + const SkMatrix& getTransform() const { return mTransform; } + sk_sp getColorSpace() const { return mColorSpace; } + + void invokeRenderCallback(android::base::unique_fd&& fenceFd, int status) { + if (mRenderCallback) { + std::invoke(mRenderCallback, std::move(fenceFd), status); + } + } + +private: + SkMatrix mTransform = SkMatrix::I(); + sk_sp mColorSpace = SkColorSpace::MakeSRGB(); + RenderCallback mRenderCallback = nullptr; +}; + +} // namespace renderthread +} // namespace uirenderer +} // namespace android +#endif // HARDWAREBUFFERRENDERER_H_ diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index 18cad31fda26..c68fcdfc76f2 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -16,17 +16,19 @@ #pragma once +#include +#include +#include +#include + +#include "ColorMode.h" #include "DamageAccumulator.h" #include "FrameInfoVisualizer.h" +#include "HardwareBufferRenderParams.h" #include "LayerUpdateQueue.h" #include "Lighting.h" #include "SwapBehavior.h" #include "hwui/Bitmap.h" -#include "ColorMode.h" - -#include -#include -#include class GrDirectContext; @@ -64,10 +66,14 @@ public: const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, const std::vector>& renderNodes, - FrameInfoVisualizer* profiler) = 0; + FrameInfoVisualizer* profiler, + const HardwareBufferRenderParams& bufferParams) = 0; virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) = 0; virtual DeferredLayerUpdater* createTextureLayer() = 0; + [[nodiscard]] virtual android::base::unique_fd flush() = 0; + virtual void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) = 0; + virtual bool hasHardwareBuffer() = 0; virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior) = 0; virtual void onStop() = 0; virtual bool isSurfaceReady() = 0; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index f8e2deebc3c6..1e011c231343 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -85,6 +85,18 @@ void RenderProxy::setName(const char* name) { mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); }); } +void RenderProxy::setHardwareBuffer(AHardwareBuffer* buffer) { + if (buffer) { + AHardwareBuffer_acquire(buffer); + } + mRenderThread.queue().post([this, hardwareBuffer = buffer]() mutable { + mContext->setHardwareBuffer(hardwareBuffer); + if (hardwareBuffer) { + AHardwareBuffer_release(hardwareBuffer); + } + }); +} + void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) { if (window) { ANativeWindow_acquire(window); } mRenderThread.queue().post([this, win = window, enableTimeout]() mutable { @@ -340,6 +352,10 @@ void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) mDrawFrameTask.setContentDrawBounds(left, top, right, bottom); } +void RenderProxy::setHardwareBufferRenderParams(const HardwareBufferRenderParams& params) { + mDrawFrameTask.setHardwareBufferRenderParams(params); +} + void RenderProxy::setPictureCapturedCallback( const std::function&&)>& callback) { mRenderThread.queue().post( diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 5dd65a0c8152..82072a6e2499 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -18,6 +18,7 @@ #define RENDERPROXY_H_ #include +#include #include #include #include @@ -76,7 +77,7 @@ public: void setSwapBehavior(SwapBehavior swapBehavior); bool loadSystemProperties(); void setName(const char* name); - + void setHardwareBuffer(AHardwareBuffer* buffer); void setSurface(ANativeWindow* window, bool enableTimeout = true); void setSurfaceControl(ASurfaceControl* surfaceControl); void allocateBuffers(); @@ -84,6 +85,7 @@ public: void setStopped(bool stopped); void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); void setLightGeometry(const Vector3& lightCenter, float lightRadius); + void setHardwareBufferRenderParams(const HardwareBufferRenderParams& params); void setOpaque(bool opaque); float setColorMode(ColorMode mode); void setRenderSdrHdrRatio(float ratio); diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp index 88420a5d5c23..9e376e32f8ea 100644 --- a/libs/hwui/tests/unit/CanvasContextTests.cpp +++ b/libs/hwui/tests/unit/CanvasContextTests.cpp @@ -38,7 +38,7 @@ RENDERTHREAD_TEST(CanvasContext, create) { std::unique_ptr canvasContext( CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory, 0, 0)); - ASSERT_FALSE(canvasContext->hasSurface()); + ASSERT_FALSE(canvasContext->hasOutputTarget()); canvasContext->destroy(); } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index e1f6acc96730..fdba9a6597c6 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -1136,6 +1136,15 @@ + + + + + + + { + result.getFence().await(Duration.ofMillis(3000)); + handler.post(() -> { + Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, colorSpace); + Bitmap copy = bitmap.copy(Config.ARGB_8888, false); + mImageView.setImageBitmap(copy); + }); + }); + } +} -- cgit v1.2.3-59-g8ed1b From 0b3f331ef566bc9b3f17e92e25248beb3d06dcf2 Mon Sep 17 00:00:00 2001 From: John Reck Date: Tue, 31 Jan 2023 16:21:28 -0500 Subject: Address VRI review comments Test: make Bug: 266628247 Change-Id: I8652d1e33ad01be48a2efa4c323f60f6f65e73bd --- graphics/java/android/graphics/HardwareRenderer.java | 2 +- libs/hwui/ColorMode.h | 3 +++ libs/hwui/pipeline/skia/SkiaPipeline.cpp | 7 ++++++- libs/hwui/renderthread/CanvasContext.cpp | 7 +++++-- libs/hwui/renderthread/EglManager.cpp | 1 + libs/hwui/renderthread/RenderProxy.cpp | 2 +- libs/hwui/renderthread/VulkanSurface.cpp | 6 +++--- 7 files changed, 20 insertions(+), 8 deletions(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index c3eb7aa454ae..0488b9d8b201 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -668,7 +668,7 @@ public class HardwareRenderer { /** @hide */ public void setTargetSdrHdrRatio(float ratio) { - if (ratio < 1.f || Float.isNaN(ratio) || Float.isInfinite(ratio)) ratio = 1.f; + if (ratio < 1.f || !Float.isFinite(ratio)) ratio = 1.f; nSetTargetSdrHdrRatio(mNativeProxy, ratio); } diff --git a/libs/hwui/ColorMode.h b/libs/hwui/ColorMode.h index e45db01c0e34..959cf742c8e4 100644 --- a/libs/hwui/ColorMode.h +++ b/libs/hwui/ColorMode.h @@ -27,6 +27,9 @@ enum class ColorMode { WideColorGamut = 1, // Extended range Display P3 Hdr = 2, + // Extended range Display P3 10-bit + // for test purposes only, not shippable due to insuffient alpha + Hdr10 = 3, // Alpha 8 A8 = 4, }; diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 09c3120a9f71..fd2f6f096783 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -621,6 +621,11 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { mSurfaceColorSpace = SkColorSpace::MakeRGB( GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3); break; + case ColorMode::Hdr10: + mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType; + mSurfaceColorSpace = SkColorSpace::MakeRGB( + GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3); + break; case ColorMode::A8: mSurfaceColorType = SkColorType::kAlpha_8_SkColorType; mSurfaceColorSpace = nullptr; @@ -629,7 +634,7 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { } void SkiaPipeline::setTargetSdrHdrRatio(float ratio) { - if (mColorMode == ColorMode::Hdr) { + if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) { mTargetSdrHdrRatio = ratio; mSurfaceColorSpace = SkColorSpace::MakeRGB(GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index c0f30865d507..5b51b642c6bc 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -289,7 +289,8 @@ void CanvasContext::setOpaque(bool opaque) { float CanvasContext::setColorMode(ColorMode mode) { if (mode != mColorMode) { - if (mode == ColorMode::Hdr && !mRenderPipeline->supportsExtendedRangeHdr()) { + const bool isHdr = mode == ColorMode::Hdr || mode == ColorMode::Hdr10; + if (isHdr && !mRenderPipeline->supportsExtendedRangeHdr()) { mode = ColorMode::WideColorGamut; } mColorMode = mode; @@ -299,13 +300,15 @@ float CanvasContext::setColorMode(ColorMode mode) { switch (mColorMode) { case ColorMode::Hdr: return 3.f; // TODO: Refine this number + case ColorMode::Hdr10: + return 10.f; default: return 1.f; } } float CanvasContext::targetSdrHdrRatio() const { - if (mColorMode == ColorMode::Hdr) { + if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) { return mTargetSdrHdrRatio; } else { return 1.f; diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 5b7cf7538bd7..4fb114b71bf5 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -455,6 +455,7 @@ Result EglManager::createSurface(EGLNativeWindowType window, // composer3 support, just treat HDR as equivalent to wide color gamut if // the GLES path is still being hit case ColorMode::Hdr: + case ColorMode::Hdr10: case ColorMode::WideColorGamut: { skcms_Matrix3x3 colorGamut; LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut), diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index f8e2deebc3c6..cdfbf021805d 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -134,7 +134,7 @@ void RenderProxy::setOpaque(bool opaque) { float RenderProxy::setColorMode(ColorMode mode) { // We only need to figure out what the renderer supports for HDR, otherwise this can stay // an async call since we already know the return value - if (mode == ColorMode::Hdr) { + if (mode == ColorMode::Hdr || mode == ColorMode::Hdr10) { return mRenderThread.queue().runSync( [=]() -> float { return mContext->setColorMode(mode); }); } else { diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp index 2efa5d691ca5..21b6c44e997e 100644 --- a/libs/hwui/renderthread/VulkanSurface.cpp +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -201,7 +201,7 @@ bool VulkanSurface::InitializeWindowInfoStruct(ANativeWindow* window, ColorMode outWindowInfo->colorspace = colorSpace; outWindowInfo->colorMode = colorMode; - if (colorMode == ColorMode::Hdr) { + if (colorMode == ColorMode::Hdr || colorMode == ColorMode::Hdr10) { outWindowInfo->dataspace = static_cast(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED); } else { @@ -509,7 +509,7 @@ void VulkanSurface::setColorSpace(sk_sp colorSpace) { mNativeBuffers[i].skSurface.reset(); } - if (mWindowInfo.colorMode == ColorMode::Hdr) { + if (mWindowInfo.colorMode == ColorMode::Hdr || mWindowInfo.colorMode == ColorMode::Hdr10) { mWindowInfo.dataspace = static_cast(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED); } else { @@ -521,7 +521,7 @@ void VulkanSurface::setColorSpace(sk_sp colorSpace) { "Unsupported colorspace"); if (mNativeWindow) { - int err = native_window_set_buffers_data_space(mNativeWindow.get(), mWindowInfo.dataspace); + int err = ANativeWindow_setBuffersDataSpace(mNativeWindow.get(), mWindowInfo.dataspace); if (err != 0) { ALOGE("VulkanSurface::setColorSpace() native_window_set_buffers_data_space(%d) " "failed: %s (%d)", -- cgit v1.2.3-59-g8ed1b From dded51fc97be995c2dbd71816779b828b3f3b82d Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Wed, 1 Feb 2023 06:04:55 +0000 Subject: Revert "Revert "Tonemap in RecordingCanvas"" This reverts commit 5432e532386e59c90744235963224dea9c312ee2. Reason for revert: HardwareBufferRenderer relanded Change-Id: I07c0bcc6ec019eb41baba43eb887532a4333e063 --- graphics/java/android/graphics/ColorSpace.java | 4 +- libs/hwui/Android.bp | 2 +- libs/hwui/CanvasTransform.h | 2 +- libs/hwui/RecordingCanvas.cpp | 15 +- libs/hwui/Tonemapper.cpp | 17 +- tests/HwAccelerationTest/AndroidManifest.xml | 9 + .../com/android/test/hwui/ColorBitmapActivity.java | 196 +++++++++++++++++++++ 7 files changed, 234 insertions(+), 11 deletions(-) create mode 100644 tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 2427dec169d6..4c669b8c34f5 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -212,9 +212,9 @@ public abstract class ColorSpace { new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4); private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS = new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f, - 0.28466892f, 0.5599107f, 0.0f, -3.0f, true); + 0.28466892f, 0.5599107f, -11 / 12.0f, -3.0f, true); private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS = - new Rgb.TransferParameters(107 / 128.0f, 1.0f, 32 / 2523.0f, + new Rgb.TransferParameters(-107 / 128.0f, 1.0f, 32 / 2523.0f, 2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true); // See static initialization block next to #get(Named) diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 59e4b7acdba7..2245aae29ecc 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -536,6 +536,7 @@ cc_defaults { "RootRenderNode.cpp", "SkiaCanvas.cpp", "SkiaInterpolator.cpp", + "Tonemapper.cpp", "VectorDrawable.cpp", ], @@ -594,7 +595,6 @@ cc_defaults { "ProfileData.cpp", "ProfileDataContainer.cpp", "Readback.cpp", - "Tonemapper.cpp", "TreeInfo.cpp", "WebViewFunctorManager.cpp", "protos/graphicsstats.proto", diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h index c46a2d369974..291f4cf7193b 100644 --- a/libs/hwui/CanvasTransform.h +++ b/libs/hwui/CanvasTransform.h @@ -45,4 +45,4 @@ bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette pale SkColor transformColor(ColorTransform transform, SkColor color); SkColor transformColorInverse(ColorTransform transform, SkColor color); -} // namespace android::uirenderer; \ No newline at end of file +} // namespace android::uirenderer diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 3f21940d35a7..bbe79d922b3f 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -43,6 +43,7 @@ #include "SkRegion.h" #include "SkTextBlob.h" #include "SkVertices.h" +#include "Tonemapper.h" #include "VectorDrawable.h" #include "pipeline/skia/AnimatedDrawables.h" #include "pipeline/skia/FunctorDrawable.h" @@ -334,7 +335,9 @@ struct DrawImage final : Op { SkPaint paint; BitmapPalette palette; void draw(SkCanvas* c, const SkMatrix&) const { - c->drawImage(image.get(), x, y, sampling, &paint); + SkPaint newPaint = paint; + tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint); + c->drawImage(image.get(), x, y, sampling, &newPaint); } }; struct DrawImageRect final : Op { @@ -356,7 +359,9 @@ struct DrawImageRect final : Op { SkCanvas::SrcRectConstraint constraint; BitmapPalette palette; void draw(SkCanvas* c, const SkMatrix&) const { - c->drawImageRect(image.get(), src, dst, sampling, &paint, constraint); + SkPaint newPaint = paint; + tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint); + c->drawImageRect(image.get(), src, dst, sampling, &newPaint, constraint); } }; struct DrawImageLattice final : Op { @@ -389,8 +394,10 @@ struct DrawImageLattice final : Op { auto flags = (0 == fs) ? nullptr : pod( this, (xs + ys) * sizeof(int) + fs * sizeof(SkColor)); - c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, - filter, &paint); + SkPaint newPaint = paint; + tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint); + c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, filter, + &newPaint); } }; diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp index a7e76b631140..0d39f0e33298 100644 --- a/libs/hwui/Tonemapper.cpp +++ b/libs/hwui/Tonemapper.cpp @@ -18,7 +18,10 @@ #include #include +// libshaders only exists on Android devices +#ifdef __ANDROID__ #include +#endif #include "utils/Color.h" @@ -26,6 +29,8 @@ namespace android::uirenderer { namespace { +// custom tonemapping only exists on Android devices +#ifdef __ANDROID__ class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder { public: explicit ColorFilterRuntimeEffectBuilder(sk_sp effect) @@ -59,20 +64,21 @@ static sk_sp createLinearEffectColorFilter(const shaders::LinearE return effectBuilder.makeColorFilter(); } -static bool extractTransfer(ui::Dataspace dataspace) { - return dataspace & HAL_DATASPACE_TRANSFER_MASK; +static ui::Dataspace extractTransfer(ui::Dataspace dataspace) { + return static_cast(dataspace & HAL_DATASPACE_TRANSFER_MASK); } static bool isHdrDataspace(ui::Dataspace dataspace) { const auto transfer = extractTransfer(dataspace); - return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG; + return transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG; } static ui::Dataspace getDataspace(const SkImageInfo& image) { return static_cast( ColorSpaceToADataSpace(image.colorSpace(), image.colorType())); } +#endif } // namespace @@ -80,6 +86,8 @@ static ui::Dataspace getDataspace(const SkImageInfo& image) { // shader and tag it on the supplied paint. void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits, SkPaint& paint) { +// custom tonemapping only exists on Android devices +#ifdef __ANDROID__ const auto sourceDataspace = getDataspace(source); const auto destinationDataspace = getDataspace(destination); @@ -102,6 +110,9 @@ void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, flo paint.setColorFilter(colorFilter); } } +#else + return; +#endif } } // namespace android::uirenderer diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 616f21cbe1a4..5a3d28a4dfad 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -120,6 +120,15 @@ + + + + + + + diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java new file mode 100644 index 000000000000..017de605fe39 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.hwui; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorSpace; +import android.graphics.HardwareBufferRenderer; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.RenderNode; +import android.graphics.Shader; +import android.hardware.HardwareBuffer; +import android.media.Image; +import android.media.ImageWriter; +import android.os.Bundle; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.Spinner; + +import java.time.Duration; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; + +@SuppressWarnings({"UnusedDeclaration"}) +public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callback, + AdapterView.OnItemSelectedListener { + + private static final int WIDTH = 512; + private static final int HEIGHT = 512; + + private ImageView mImageView; + private SurfaceView mSurfaceView; + private HardwareBuffer mGradientBuffer; + private ImageWriter mImageWriter; + private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + private String[] mColorNames = {"sRGB", "BT2020_HLG", "BT2020_PQ"}; + private String mCurrentColorName = "sRGB"; + + private FutureTask authorGradientBuffer(HardwareBuffer buffer) { + HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer); + RenderNode node = new RenderNode("content"); + node.setPosition(0, 0, buffer.getWidth(), buffer.getHeight()); + + Canvas canvas = node.beginRecording(); + LinearGradient gradient = new LinearGradient( + 0, 0, buffer.getWidth(), buffer.getHeight(), 0xFF000000, + 0xFFFFFFFF, Shader.TileMode.CLAMP); + Paint paint = new Paint(); + paint.setShader(gradient); + canvas.drawRect(0f, 0f, buffer.getWidth(), buffer.getHeight(), paint); + node.endRecording(); + + renderer.setContentRoot(node); + + ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + FutureTask resolvedBuffer = new FutureTask<>(() -> buffer); + renderer.obtainRenderRequest() + .setColorSpace(colorSpace) + .draw(Executors.newSingleThreadExecutor(), result -> { + result.getFence().await(Duration.ofSeconds(3)); + resolvedBuffer.run(); + }); + return resolvedBuffer; + } + + private FutureTask getGradientBuffer() { + HardwareBuffer buffer = HardwareBuffer.create( + WIDTH, HEIGHT, PixelFormat.RGBA_8888, 1, + HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE + | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT); + return authorGradientBuffer(buffer); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + try { + + mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + + ArrayAdapter adapter = new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, mColorNames); + + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + Spinner spinner = new Spinner(this); + spinner.setAdapter(adapter); + spinner.setOnItemSelectedListener(this); + + mGradientBuffer = getGradientBuffer().get(); + + LinearLayout linearLayout = new LinearLayout(this); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + mImageView = new ImageView(this); + + mSurfaceView = new SurfaceView(this); + mSurfaceView.getHolder().addCallback(this); + + linearLayout.addView(spinner, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + linearLayout.addView(mImageView, new LinearLayout.LayoutParams(WIDTH, HEIGHT)); + linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT)); + + setContentView(linearLayout); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private ColorSpace getFromName(String name) { + if (name.equals("sRGB")) { + return ColorSpace.get(ColorSpace.Named.SRGB); + } else if (name.equals("BT2020_HLG")) { + return ColorSpace.get(ColorSpace.Named.BT2020_HLG); + } else if (name.equals("BT2020_PQ")) { + return ColorSpace.get(ColorSpace.Named.BT2020_PQ); + } + + throw new RuntimeException("Unrecognized Colorspace!"); + } + + private void populateBuffers() { + Bitmap bitmap = Bitmap.wrapHardwareBuffer( + mGradientBuffer, ColorSpace.get(ColorSpace.Named.SRGB)); + Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false); + copy.setColorSpace(mColorSpace); + mImageView.setImageBitmap(copy); + + try (Image image = mImageWriter.dequeueInputImage()) { + authorGradientBuffer(image.getHardwareBuffer()).get(); + image.setDataSpace(mColorSpace.getDataSpace()); + mImageWriter.queueInputImage(image); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + mImageWriter = new ImageWriter.Builder(holder.getSurface()) + .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE + | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT + | HardwareBuffer.USAGE_COMPOSER_OVERLAY) + .build(); + populateBuffers(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + mImageWriter.close(); + mImageWriter = null; + } + + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + mCurrentColorName = mColorNames[position]; + mColorSpace = getFromName(mCurrentColorName); + populateBuffers(); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } +} -- cgit v1.2.3-59-g8ed1b From d21ca252475ec5e1ecb5a3e085a5a13713d27f60 Mon Sep 17 00:00:00 2001 From: Brian Salomon Date: Wed, 1 Feb 2023 19:06:57 +0000 Subject: BaseCanvas drawMesh blendMode identify the src and dst colors in docs. Also note that AA is ignored, like drawVertices. Bug:254700607 Change-Id: I1823220b957c759fd7d8590935c6c7be290c7d6b --- graphics/java/android/graphics/BaseCanvas.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index 00ffd09eb29e..8cd8ddf68832 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -670,8 +670,12 @@ public abstract class BaseCanvas { /** * Draws a mesh object to the screen. * + *

    Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is + * ignored.

    + * * @param mesh {@link Mesh} object that will be drawn to the screen - * @param blendMode {@link BlendMode} used to blend mesh primitives with the Paint color/shader + * @param blendMode {@link BlendMode} used to blend mesh primitives as the destination color + * with the Paint color/shader as the source color. * @param paint {@link Paint} used to provide a color/shader/blend mode. */ public void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) { -- cgit v1.2.3-59-g8ed1b From 4ae9e0cbe78705ccf3b1d828f57b3d42d2c3de9f Mon Sep 17 00:00:00 2001 From: Andrew Solovay Date: Thu, 2 Feb 2023 01:17:54 +0000 Subject: docs: Fixing bad summary fragment in Canvas.isOpaque() The first sentence of the Canvas.isOpaque() comment contained an "i.e.", which the docs engine interprets as the end of the summary fragment (per the Javadoc standard). Replaced "i.e." with "that is" to fix this. Docs-only change, no code affected. NO_TEST , fixes a doc bug. Change-Id: Ib733365cc8654acdaf1d9b5bc891e0aa67210bf1 Test: [go/abtd docs build] Fixes: 267544675 --- graphics/java/android/graphics/Canvas.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 42c892a240b6..e7814cbd67e7 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -241,7 +241,7 @@ public class Canvas extends BaseCanvas { /** * Return true if the device that the current layer draws into is opaque - * (i.e. does not support per-pixel alpha). + * (that is, it does not support per-pixel alpha). * * @return true if the device that the current layer draws into is opaque */ -- cgit v1.2.3-59-g8ed1b From c0d506ec7b6f3c92c35f6c7090445d03293cd68d Mon Sep 17 00:00:00 2001 From: Angel Aguayo Date: Wed, 18 Jan 2023 00:30:45 +0000 Subject: Address drawMesh API feedback Added annotations where appropriate, updated documentation, and made Mesh factory methods into constructors. Bug: b/265855122 Test: atest CtsUiRenderingTestCases:MeshTest Change-Id: Ic06821ea40750d40882f1dc6acd0a21dae76b7ab --- core/api/current.txt | 10 +++---- graphics/java/android/graphics/BaseCanvas.java | 5 ++-- graphics/java/android/graphics/Mesh.java | 32 ++++++++++------------ .../src/com/android/test/hwui/MeshActivity.java | 4 +-- .../com/android/test/hwui/MeshLargeActivity.java | 2 +- 5 files changed, 26 insertions(+), 27 deletions(-) (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index d3de15830ffa..d6be2d2a5bb1 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -15009,7 +15009,7 @@ package android.graphics { method public void drawLine(float, float, float, float, @NonNull android.graphics.Paint); method public void drawLines(@NonNull @Size(multiple=4) float[], int, int, @NonNull android.graphics.Paint); method public void drawLines(@NonNull @Size(multiple=4) float[], @NonNull android.graphics.Paint); - method public void drawMesh(@NonNull android.graphics.Mesh, android.graphics.BlendMode, @NonNull android.graphics.Paint); + method public void drawMesh(@NonNull android.graphics.Mesh, @Nullable android.graphics.BlendMode, @NonNull android.graphics.Paint); method public void drawOval(@NonNull android.graphics.RectF, @NonNull android.graphics.Paint); method public void drawOval(float, float, float, float, @NonNull android.graphics.Paint); method public void drawPaint(@NonNull android.graphics.Paint); @@ -15611,10 +15611,10 @@ package android.graphics { } public class Mesh { - method @NonNull public static android.graphics.Mesh make(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull android.graphics.Rect); - method @NonNull public static android.graphics.Mesh makeIndexed(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull java.nio.ShortBuffer, @NonNull android.graphics.Rect); - method public void setColorUniform(@NonNull String, int); - method public void setColorUniform(@NonNull String, long); + ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull android.graphics.Rect); + ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull java.nio.ShortBuffer, @NonNull android.graphics.Rect); + method public void setColorUniform(@NonNull String, @ColorInt int); + method public void setColorUniform(@NonNull String, @ColorLong long); method public void setColorUniform(@NonNull String, @NonNull android.graphics.Color); method public void setFloatUniform(@NonNull String, float); method public void setFloatUniform(@NonNull String, float, float); diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index 8cd8ddf68832..a7acaf924c15 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -675,10 +675,11 @@ public abstract class BaseCanvas { * * @param mesh {@link Mesh} object that will be drawn to the screen * @param blendMode {@link BlendMode} used to blend mesh primitives as the destination color - * with the Paint color/shader as the source color. + * with the Paint color/shader as the source color. This defaults to + * {@link BlendMode#MODULATE} if null. * @param paint {@link Paint} used to provide a color/shader/blend mode. */ - public void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) { + public void drawMesh(@NonNull Mesh mesh, @Nullable BlendMode blendMode, @NonNull Paint paint) { if (!isHardwareAccelerated() && onHwFeatureInSwMode()) { throw new RuntimeException("software rendering doesn't support meshes"); } diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index a599b2c17604..ce673ce32aa4 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -16,6 +16,8 @@ package android.graphics; +import android.annotation.ColorInt; +import android.annotation.ColorLong; import android.annotation.IntDef; import android.annotation.NonNull; @@ -27,10 +29,8 @@ import java.nio.ShortBuffer; /** * Class representing a mesh object. * - * This class generates Mesh objects via the - * {@link #make(MeshSpecification, int, Buffer, int, Rect)} and - * {@link #makeIndexed(MeshSpecification, int, Buffer, int, ShortBuffer, Rect)} methods, - * where a {@link MeshSpecification} is required along with various attributes for + * This class represents a Mesh object that can optionally be indexed. + * A {@link MeshSpecification} is required along with various attributes for * detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds * for the mesh. Once generated, a mesh object can be drawn through * {@link Canvas#drawMesh(Mesh, BlendMode, Paint)} @@ -62,7 +62,7 @@ public class Mesh { } /** - * Generates a {@link Mesh} object. + * Constructor for a non-indexed Mesh. * * @param meshSpec {@link MeshSpecification} used when generating the mesh. * @param mode Determines what mode to draw the mesh in. Must be one of @@ -74,10 +74,8 @@ public class Mesh { * backed buffer generated. * @param vertexCount the number of vertices represented in the vertexBuffer and mesh. * @param bounds bounds of the mesh object. - * @return a new Mesh object. */ - @NonNull - public static Mesh make(@NonNull MeshSpecification meshSpec, @Mode int mode, + public Mesh(@NonNull MeshSpecification meshSpec, @Mode int mode, @NonNull Buffer vertexBuffer, int vertexCount, @NonNull Rect bounds) { if (mode != TRIANGLES && mode != TRIANGLE_STRIP) { throw new IllegalArgumentException("Invalid value passed in for mode parameter"); @@ -88,11 +86,12 @@ public class Mesh { if (nativeMesh == 0) { throw new IllegalArgumentException("Mesh construction failed."); } - return new Mesh(nativeMesh, false); + + meshSetup(nativeMesh, false); } /** - * Generates a {@link Mesh} object. + * Constructor for an indexed Mesh. * * @param meshSpec {@link MeshSpecification} used when generating the mesh. * @param mode Determines what mode to draw the mesh in. Must be one of @@ -108,10 +107,8 @@ public class Mesh { * currently implementation will have a CPU * backed buffer generated. * @param bounds bounds of the mesh object. - * @return a new Mesh object. */ - @NonNull - public static Mesh makeIndexed(@NonNull MeshSpecification meshSpec, @Mode int mode, + public Mesh(@NonNull MeshSpecification meshSpec, @Mode int mode, @NonNull Buffer vertexBuffer, int vertexCount, @NonNull ShortBuffer indexBuffer, @NonNull Rect bounds) { if (mode != TRIANGLES && mode != TRIANGLE_STRIP) { @@ -124,7 +121,8 @@ public class Mesh { if (nativeMesh == 0) { throw new IllegalArgumentException("Mesh construction failed."); } - return new Mesh(nativeMesh, true); + + meshSetup(nativeMesh, true); } /** @@ -137,7 +135,7 @@ public class Mesh { * @param color the provided sRGB color will be converted into the shader program's output * colorspace and be available as a vec4 uniform in the program. */ - public void setColorUniform(@NonNull String uniformName, int color) { + public void setColorUniform(@NonNull String uniformName, @ColorInt int color) { setUniform(uniformName, Color.valueOf(color).getComponents(), true); } @@ -151,7 +149,7 @@ public class Mesh { * @param color the provided sRGB color will be converted into the shader program's output * colorspace and be available as a vec4 uniform in the program. */ - public void setColorUniform(@NonNull String uniformName, long color) { + public void setColorUniform(@NonNull String uniformName, @ColorLong long color) { Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)); setUniform(uniformName, exSRGB.getComponents(), true); } @@ -357,7 +355,7 @@ public class Mesh { mNativeMeshWrapper, uniformName, value1, value2, value3, value4, count); } - private Mesh(long nativeMeshWrapper, boolean isIndexed) { + private void meshSetup(long nativeMeshWrapper, boolean isIndexed) { mNativeMeshWrapper = nativeMeshWrapper; this.mIsIndexed = isIndexed; MeshHolder.MESH_SPECIFICATION_REGISTRY.registerNativeAllocation(this, mNativeMeshWrapper); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java index d3a5885b324d..0f97bb2d6d0e 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java @@ -64,7 +64,7 @@ public class MeshActivity extends Activity { vertexBuffer.put(4, 0.0f); vertexBuffer.put(5, 400.0f); vertexBuffer.rewind(); - Mesh mesh = Mesh.make( + Mesh mesh = new Mesh( meshSpec, Mesh.TRIANGLES, vertexBuffer, 3, new Rect(0, 0, 1000, 1000)); canvas.drawMesh(mesh, BlendMode.COLOR, new Paint()); @@ -98,7 +98,7 @@ public class MeshActivity extends Activity { } iVertexBuffer.rewind(); indexBuffer.rewind(); - Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.TRIANGLES, iVertexBuffer, 102, indexBuffer, + Mesh mesh2 = new Mesh(meshSpec, Mesh.TRIANGLES, iVertexBuffer, 102, indexBuffer, new Rect(0, 0, 1000, 1000)); Paint paint = new Paint(); paint.setColor(Color.RED); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java index f97d942fa483..bb296ccf2c82 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java @@ -109,7 +109,7 @@ public class MeshLargeActivity extends Activity { } vertexBuffer.rewind(); indexBuffer.rewind(); - Mesh mesh = Mesh.makeIndexed( + Mesh mesh = new Mesh( meshSpec, Mesh.TRIANGLES, vertexBuffer, numTriangles + 2, indexBuffer, new Rect(0, 0, 1000, 1000) ); -- cgit v1.2.3-59-g8ed1b From bfffc36c6ac9710b1feb50bee8a2017d4afa8840 Mon Sep 17 00:00:00 2001 From: John Reck Date: Fri, 3 Feb 2023 15:42:06 -0500 Subject: Mesh Rect -> RectF Test: build & boot; no functionality changes Fixes: 267686303 Change-Id: I1f9edb4e2d07823ffdc4f0074b4ef1358849aa99 --- core/api/current.txt | 4 ++-- graphics/java/android/graphics/Mesh.java | 12 ++++++------ libs/hwui/jni/Mesh.cpp | 10 +++++----- .../src/com/android/test/hwui/MeshActivity.java | 6 +++--- .../src/com/android/test/hwui/MeshLargeActivity.java | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index d6be2d2a5bb1..5be92529be2b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -15611,8 +15611,8 @@ package android.graphics { } public class Mesh { - ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull android.graphics.Rect); - ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull java.nio.ShortBuffer, @NonNull android.graphics.Rect); + ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull android.graphics.RectF); + ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull java.nio.ShortBuffer, @NonNull android.graphics.RectF); method public void setColorUniform(@NonNull String, @ColorInt int); method public void setColorUniform(@NonNull String, @ColorLong long); method public void setColorUniform(@NonNull String, @NonNull android.graphics.Color); diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index ce673ce32aa4..6a1313e16ce5 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -76,7 +76,7 @@ public class Mesh { * @param bounds bounds of the mesh object. */ public Mesh(@NonNull MeshSpecification meshSpec, @Mode int mode, - @NonNull Buffer vertexBuffer, int vertexCount, @NonNull Rect bounds) { + @NonNull Buffer vertexBuffer, int vertexCount, @NonNull RectF bounds) { if (mode != TRIANGLES && mode != TRIANGLE_STRIP) { throw new IllegalArgumentException("Invalid value passed in for mode parameter"); } @@ -110,7 +110,7 @@ public class Mesh { */ public Mesh(@NonNull MeshSpecification meshSpec, @Mode int mode, @NonNull Buffer vertexBuffer, int vertexCount, @NonNull ShortBuffer indexBuffer, - @NonNull Rect bounds) { + @NonNull RectF bounds) { if (mode != TRIANGLES && mode != TRIANGLE_STRIP) { throw new IllegalArgumentException("Invalid value passed in for mode parameter"); } @@ -364,13 +364,13 @@ public class Mesh { private static native long nativeGetFinalizer(); private static native long nativeMake(long meshSpec, int mode, Buffer vertexBuffer, - boolean isDirect, int vertexCount, int vertexOffset, int left, int top, int right, - int bottom); + boolean isDirect, int vertexCount, int vertexOffset, float left, float top, float right, + float bottom); private static native long nativeMakeIndexed(long meshSpec, int mode, Buffer vertexBuffer, boolean isVertexDirect, int vertexCount, int vertexOffset, ShortBuffer indexBuffer, - boolean isIndexDirect, int indexCount, int indexOffset, int left, int top, int right, - int bottom); + boolean isIndexDirect, int indexCount, int indexOffset, float left, float top, + float right, float bottom); private static native void nativeUpdateUniforms(long builder, String uniformName, float value1, float value2, float value3, float value4, int count); diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp index 3aac48dd08b1..b13d9bafb417 100644 --- a/libs/hwui/jni/Mesh.cpp +++ b/libs/hwui/jni/Mesh.cpp @@ -38,8 +38,8 @@ sk_sp genIndexBuffer(JNIEnv* env, jobject buffer, int size, } static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer, - jboolean isDirect, jint vertexCount, jint vertexOffset, jint left, jint top, - jint right, jint bottom) { + jboolean isDirect, jint vertexCount, jint vertexOffset, jfloat left, jfloat top, + jfloat right, jfloat bottom) { auto skMeshSpec = sk_ref_sp(reinterpret_cast(meshSpec)); sk_sp skVertexBuffer = genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect); @@ -60,7 +60,7 @@ static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject verte static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer, jboolean isVertexDirect, jint vertexCount, jint vertexOffset, jobject indexBuffer, jboolean isIndexDirect, jint indexCount, - jint indexOffset, jint left, jint top, jint right, jint bottom) { + jint indexOffset, jfloat left, jfloat top, jfloat right, jfloat bottom) { auto skMeshSpec = sk_ref_sp(reinterpret_cast(meshSpec)); sk_sp skVertexBuffer = genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isVertexDirect); @@ -210,8 +210,8 @@ static jlong getMeshFinalizer(JNIEnv*, jobject) { static const JNINativeMethod gMeshMethods[] = { {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer}, - {"nativeMake", "(JILjava/nio/Buffer;ZIIIIII)J", (void*)make}, - {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIIIII)J", + {"nativeMake", "(JILjava/nio/Buffer;ZIIFFFF)J", (void*)make}, + {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIFFFF)J", (void*)makeIndexed}, {"nativeUpdateMesh", "(JZ)V", (void*)updateMesh}, {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms}, diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java index 0f97bb2d6d0e..413f92c5446b 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java @@ -26,7 +26,7 @@ import android.graphics.MeshSpecification; import android.graphics.MeshSpecification.Attribute; import android.graphics.MeshSpecification.Varying; import android.graphics.Paint; -import android.graphics.Rect; +import android.graphics.RectF; import android.os.Bundle; import android.view.View; @@ -65,7 +65,7 @@ public class MeshActivity extends Activity { vertexBuffer.put(5, 400.0f); vertexBuffer.rewind(); Mesh mesh = new Mesh( - meshSpec, Mesh.TRIANGLES, vertexBuffer, 3, new Rect(0, 0, 1000, 1000)); + meshSpec, Mesh.TRIANGLES, vertexBuffer, 3, new RectF(0, 0, 1000, 1000)); canvas.drawMesh(mesh, BlendMode.COLOR, new Paint()); @@ -99,7 +99,7 @@ public class MeshActivity extends Activity { iVertexBuffer.rewind(); indexBuffer.rewind(); Mesh mesh2 = new Mesh(meshSpec, Mesh.TRIANGLES, iVertexBuffer, 102, indexBuffer, - new Rect(0, 0, 1000, 1000)); + new RectF(0, 0, 1000, 1000)); Paint paint = new Paint(); paint.setColor(Color.RED); canvas.drawMesh(mesh2, BlendMode.COLOR, paint); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java index bb296ccf2c82..e62db6bd6c02 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java @@ -26,7 +26,7 @@ import android.graphics.MeshSpecification; import android.graphics.MeshSpecification.Attribute; import android.graphics.MeshSpecification.Varying; import android.graphics.Paint; -import android.graphics.Rect; +import android.graphics.RectF; import android.os.Bundle; import android.view.View; @@ -111,7 +111,7 @@ public class MeshLargeActivity extends Activity { indexBuffer.rewind(); Mesh mesh = new Mesh( meshSpec, Mesh.TRIANGLES, vertexBuffer, numTriangles + 2, indexBuffer, - new Rect(0, 0, 1000, 1000) + new RectF(0, 0, 1000, 1000) ); mesh.setFloatUniform("test", 1.0f, 2.0f); Paint paint = new Paint(); -- cgit v1.2.3-59-g8ed1b From 93cdf120b52421e9b37a4de2a1b070d453d7ae07 Mon Sep 17 00:00:00 2001 From: Nader Jawad Date: Tue, 7 Feb 2023 17:46:25 -0800 Subject: Address Mesh API feedback Relnote: "Updated constants with appropriate prefixes for TYPE and ALPHA_TYPE. Updated IntDefs to be @hide w/ source retention Updated javadoc formatting for Attibute Added public accessors for Attribute and Varying fields Added toString methods for Attribute and Varying Added size annotations for MeshSpecification.make APIs Added docs to specify the default colorspace and alpha type parameters used. Changed List parameters for Arrays to avoid unnecessary allocations" Fixes: 266626021 Test: Re-ran mesh tests Change-Id: I6954d654a12a99ac7ff6f560c9fd35274a904d06 --- core/api/current.txt | 29 ++-- .../java/android/graphics/MeshSpecification.java | 164 +++++++++++++++------ .../src/com/android/test/hwui/MeshActivity.java | 9 +- .../com/android/test/hwui/MeshLargeActivity.java | 70 ++++----- 4 files changed, 175 insertions(+), 97 deletions(-) (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index eb1bfc8d4253..93377bd9093a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -15644,26 +15644,31 @@ package android.graphics { } public class MeshSpecification { - method @NonNull public static android.graphics.MeshSpecification make(@NonNull java.util.List, int, @NonNull java.util.List, @NonNull String, @NonNull String); - method @NonNull public static android.graphics.MeshSpecification make(@NonNull java.util.List, int, @NonNull java.util.List, @NonNull String, @NonNull String, @NonNull android.graphics.ColorSpace); - method @NonNull public static android.graphics.MeshSpecification make(@NonNull java.util.List, int, @NonNull java.util.List, @NonNull String, @NonNull String, @NonNull android.graphics.ColorSpace, int); - field public static final int FLOAT = 0; // 0x0 - field public static final int FLOAT2 = 1; // 0x1 - field public static final int FLOAT3 = 2; // 0x2 - field public static final int FLOAT4 = 3; // 0x3 - field public static final int OPAQUE = 1; // 0x1 - field public static final int PREMUL = 2; // 0x2 - field public static final int UBYTE4 = 4; // 0x4 - field public static final int UNKNOWN = 0; // 0x0 - field public static final int UNPREMULT = 3; // 0x3 + method @NonNull public static android.graphics.MeshSpecification make(@NonNull @Size(max=8) android.graphics.MeshSpecification.Attribute[], @IntRange(from=1, to=1024) int, @NonNull @Size(max=6) android.graphics.MeshSpecification.Varying[], @NonNull String, @NonNull String); + method @NonNull public static android.graphics.MeshSpecification make(@NonNull @Size(max=8) android.graphics.MeshSpecification.Attribute[], @IntRange(from=1, to=1024) int, @NonNull @Size(max=6) android.graphics.MeshSpecification.Varying[], @NonNull String, @NonNull String, @NonNull android.graphics.ColorSpace); + method @NonNull public static android.graphics.MeshSpecification make(@NonNull @Size(max=8) android.graphics.MeshSpecification.Attribute[], @IntRange(from=1, to=1024) int, @NonNull @Size(max=6) android.graphics.MeshSpecification.Varying[], @NonNull String, @NonNull String, @NonNull android.graphics.ColorSpace, int); + field public static final int ALPHA_TYPE_OPAQUE = 1; // 0x1 + field public static final int ALPHA_TYPE_PREMUL = 2; // 0x2 + field public static final int ALPHA_TYPE_PREMULT = 3; // 0x3 + field public static final int ALPHA_TYPE_UNKNOWN = 0; // 0x0 + field public static final int TYPE_FLOAT = 0; // 0x0 + field public static final int TYPE_FLOAT2 = 1; // 0x1 + field public static final int TYPE_FLOAT3 = 2; // 0x2 + field public static final int TYPE_FLOAT4 = 3; // 0x3 + field public static final int TYPE_UBYTE4 = 4; // 0x4 } public static class MeshSpecification.Attribute { ctor public MeshSpecification.Attribute(int, int, @NonNull String); + method @NonNull public String getName(); + method public int getOffset(); + method public int getType(); } public static class MeshSpecification.Varying { ctor public MeshSpecification.Varying(int, @NonNull String); + method @NonNull public String getName(); + method public int getType(); } @Deprecated public class Movie { diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java index 52b40029a0eb..6b5f1da2663b 100644 --- a/graphics/java/android/graphics/MeshSpecification.java +++ b/graphics/java/android/graphics/MeshSpecification.java @@ -17,11 +17,15 @@ package android.graphics; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Size; +import android.annotation.SuppressLint; import libcore.util.NativeAllocationRegistry; -import java.util.List; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Class responsible for holding specifications for {@link Mesh} creations. This class @@ -43,100 +47,162 @@ public class MeshSpecification { long mNativeMeshSpec; /** - * Constants for {@link #make(List, int, List, String, String)} + * Constants for {@link #make(Attribute[], int, Varying[], String, String)} * to determine alpha type. Describes how to interpret the alpha component of a pixel. + * + * @hide */ - @IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT}) + @IntDef({ALPHA_TYPE_UNKNOWN, ALPHA_TYPE_OPAQUE, ALPHA_TYPE_PREMUL, ALPHA_TYPE_PREMULT}) + @Retention(RetentionPolicy.SOURCE) private @interface AlphaType {} /** * uninitialized. */ - public static final int UNKNOWN = 0; + public static final int ALPHA_TYPE_UNKNOWN = 0; /** * Pixel is opaque. */ - public static final int OPAQUE = 1; + public static final int ALPHA_TYPE_OPAQUE = 1; /** * Pixel components are premultiplied by alpha. */ - public static final int PREMUL = 2; + public static final int ALPHA_TYPE_PREMUL = 2; /** * Pixel components are independent of alpha. */ - public static final int UNPREMULT = 3; + public static final int ALPHA_TYPE_PREMULT = 3; /** * Constants for {@link Attribute} and {@link Varying} for determining the data type. + * + * @hide */ - @IntDef({FLOAT, FLOAT2, FLOAT3, FLOAT4, UBYTE4}) + @IntDef({TYPE_FLOAT, TYPE_FLOAT2, TYPE_FLOAT3, TYPE_FLOAT4, TYPE_UBYTE4}) + @Retention(RetentionPolicy.SOURCE) private @interface Type {} /** * Represents one float. Its equivalent shader type is float. */ - public static final int FLOAT = 0; + public static final int TYPE_FLOAT = 0; /** * Represents two floats. Its equivalent shader type is float2. */ - public static final int FLOAT2 = 1; + public static final int TYPE_FLOAT2 = 1; /** * Represents three floats. Its equivalent shader type is float3. */ - public static final int FLOAT3 = 2; + public static final int TYPE_FLOAT3 = 2; /** * Represents four floats. Its equivalent shader type is float4. */ - public static final int FLOAT4 = 3; + public static final int TYPE_FLOAT4 = 3; /** * Represents four bytes. Its equivalent shader type is half4. */ - public static final int UBYTE4 = 4; + public static final int TYPE_UBYTE4 = 4; /** - * Data class to represent a single attribute in a shader. Note that type parameter must be - * one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}. + * Data class to represent a single attribute in a shader. * * Note that offset is the offset in number of bytes. For example, if we had two attributes * + *
          * Float3 att1
          * Float att2
    +     * 
    * * att1 would have an offset of 0, while att2 would have an offset of 12 bytes. */ public static class Attribute { @Type - private int mType; - private int mOffset; - private String mName; + private final int mType; + private final int mOffset; + private final String mName; public Attribute(@Type int type, int offset, @NonNull String name) { mType = type; mOffset = offset; mName = name; } + + /** + * Return the corresponding data type for this {@link Attribute}. + */ + @Type + public int getType() { + return mType; + } + + /** + * Return the offset of the attribute in bytes + */ + public int getOffset() { + return mOffset; + } + + /** + * Return the name of this {@link Attribute} + */ + @NonNull + public String getName() { + return mName; + } + + @Override + public String toString() { + return "Attribute{" + + "mType=" + mType + + ", mOffset=" + mOffset + + ", mName='" + mName + '\'' + + '}'; + } } /** - * Data class to represent a single varying variable. Note that type parameter must be - * one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}. + * Data class to represent a single varying variable. */ public static class Varying { @Type - private int mType; - private String mName; + private final int mType; + private final String mName; public Varying(@Type int type, @NonNull String name) { mType = type; mName = name; } + + /** + * Return the corresponding data type for this {@link Varying}. + */ + @Type + public int getType() { + return mType; + } + + /** + * Return the name of this {@link Varying} + */ + @NonNull + public String getName() { + return mName; + } + + @Override + public String toString() { + return "Varying{" + + "mType=" + mType + + ", mName='" + mName + '\'' + + '}'; + } } private static class MeshSpecificationHolder { @@ -146,7 +212,9 @@ public class MeshSpecification { } /** - * Creates a {@link MeshSpecification} object for use within {@link Mesh}. + * Creates a {@link MeshSpecification} object for use within {@link Mesh}. This uses a default + * color space of {@link ColorSpace.Named#SRGB} and {@link AlphaType} of + * {@link #ALPHA_TYPE_PREMUL}. * * @param attributes list of attributes represented by {@link Attribute}. Can hold a max of * 8. @@ -162,11 +230,14 @@ public class MeshSpecification { * @return {@link MeshSpecification} object for use when creating {@link Mesh} */ @NonNull - public static MeshSpecification make(@NonNull List attributes, int vertexStride, - @NonNull List varyings, @NonNull String vertexShader, + public static MeshSpecification make( + @SuppressLint("ArrayReturn") @NonNull @Size(max = 8) Attribute[] attributes, + @IntRange(from = 1, to = 1024) int vertexStride, + @SuppressLint("ArrayReturn") @NonNull @Size(max = 6) Varying[] varyings, + @NonNull String vertexShader, @NonNull String fragmentShader) { - long nativeMeshSpec = nativeMake(attributes.toArray(new Attribute[attributes.size()]), - vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader, + long nativeMeshSpec = nativeMake(attributes, + vertexStride, varyings, vertexShader, fragmentShader); if (nativeMeshSpec == 0) { throw new IllegalArgumentException("MeshSpecification construction failed"); @@ -175,7 +246,8 @@ public class MeshSpecification { } /** - * Creates a {@link MeshSpecification} object. + * Creates a {@link MeshSpecification} object. This uses a default {@link AlphaType} of + * {@link #ALPHA_TYPE_PREMUL}. * * @param attributes list of attributes represented by {@link Attribute}. Can hold a max of * 8. @@ -192,11 +264,16 @@ public class MeshSpecification { * @return {@link MeshSpecification} object for use when creating {@link Mesh} */ @NonNull - public static MeshSpecification make(@NonNull List attributes, int vertexStride, - @NonNull List varyings, @NonNull String vertexShader, - @NonNull String fragmentShader, @NonNull ColorSpace colorSpace) { - long nativeMeshSpec = nativeMakeWithCS(attributes.toArray(new Attribute[attributes.size()]), - vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader, + public static MeshSpecification make( + @SuppressLint("ArrayReturn") @NonNull @Size(max = 8) Attribute[] attributes, + @IntRange(from = 1, to = 1024) int vertexStride, + @SuppressLint("ArrayReturn") @NonNull @Size(max = 6) Varying[] varyings, + @NonNull String vertexShader, + @NonNull String fragmentShader, + @NonNull ColorSpace colorSpace + ) { + long nativeMeshSpec = nativeMakeWithCS(attributes, + vertexStride, varyings, vertexShader, fragmentShader, colorSpace.getNativeInstance()); if (nativeMeshSpec == 0) { throw new IllegalArgumentException("MeshSpecification construction failed"); @@ -221,20 +298,23 @@ public class MeshSpecification { * @param colorSpace {@link ColorSpace} to tell what color space to work in. * @param alphaType Describes how to interpret the alpha component for a pixel. Must be * one of - * {@link MeshSpecification#UNKNOWN}, - * {@link MeshSpecification#OPAQUE}, - * {@link MeshSpecification#PREMUL}, or - * {@link MeshSpecification#UNPREMULT} + * {@link MeshSpecification#ALPHA_TYPE_UNKNOWN}, + * {@link MeshSpecification#ALPHA_TYPE_OPAQUE}, + * {@link MeshSpecification#ALPHA_TYPE_PREMUL}, or + * {@link MeshSpecification#ALPHA_TYPE_PREMULT} * @return {@link MeshSpecification} object for use when creating {@link Mesh} */ @NonNull - public static MeshSpecification make(@NonNull List attributes, int vertexStride, - @NonNull List varyings, @NonNull String vertexShader, - @NonNull String fragmentShader, @NonNull ColorSpace colorSpace, + public static MeshSpecification make( + @SuppressLint("ArrayReturn") @NonNull @Size(max = 8) Attribute[] attributes, + @IntRange(from = 1, to = 1024) int vertexStride, + @SuppressLint("ArrayReturn") @NonNull @Size(max = 6) Varying[] varyings, + @NonNull String vertexShader, + @NonNull String fragmentShader, + @NonNull ColorSpace colorSpace, @AlphaType int alphaType) { long nativeMeshSpec = - nativeMakeWithAlpha(attributes.toArray(new Attribute[attributes.size()]), - vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader, + nativeMakeWithAlpha(attributes, vertexStride, varyings, vertexShader, fragmentShader, colorSpace.getNativeInstance(), alphaType); if (nativeMeshSpec == 0) { throw new IllegalArgumentException("MeshSpecification construction failed"); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java index 413f92c5446b..ae3dcb834687 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java @@ -32,7 +32,6 @@ import android.view.View; import java.nio.FloatBuffer; import java.nio.ShortBuffer; -import java.util.ArrayList; public class MeshActivity extends Activity { @Override @@ -115,9 +114,11 @@ public class MeshActivity extends Activity { + " color = vec4(1.0, 0.0, 0.0, 1.0);" + " return varyings.position;\n" + "}"; - ArrayList attList = new ArrayList<>(); - attList.add(new Attribute(MeshSpecification.FLOAT2, 0, "position")); - ArrayList varyList = new ArrayList<>(); + Attribute[] attList = new Attribute[]{ + new Attribute(MeshSpecification.TYPE_FLOAT2, 0, "position"), + + }; + Varying[] varyList = new Varying[0]; return MeshSpecification.make(attList, 8, varyList, vs, fs); } } diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java index e62db6bd6c02..01ca2fcdbb86 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java @@ -32,7 +32,6 @@ import android.view.View; import java.nio.FloatBuffer; import java.nio.ShortBuffer; -import java.util.ArrayList; public class MeshLargeActivity extends Activity { @Override @@ -131,44 +130,37 @@ public class MeshLargeActivity extends Activity { + " color = vec4(1.0, 0.0, 0.0, 1.0);" + " return varyings.position;\n" + "}"; - ArrayList attList = new ArrayList<>(); - attList.add(new Attribute(MeshSpecification.FLOAT2, 0, "position")); - attList.add(new Attribute( - MeshSpecification.FLOAT4, - 8, - "test" - )); - attList.add(new Attribute( - MeshSpecification.FLOAT4, - 24, - "test2" - )); - attList.add(new Attribute( - MeshSpecification.FLOAT4, - 40, - "test3" - )); - attList.add(new Attribute( - MeshSpecification.FLOAT4, - 56, - "test4" - )); - attList.add(new Attribute( - MeshSpecification.FLOAT4, - 72, - "test5" - )); - attList.add(new Attribute( - MeshSpecification.FLOAT4, - 88, - "test6" - )); - attList.add(new Attribute( - MeshSpecification.FLOAT4, - 104, - "test7" - )); - ArrayList varyList = new ArrayList<>(); + Attribute[] attList = new Attribute[]{ + new Attribute(MeshSpecification.TYPE_FLOAT2, 0, "position"), + new Attribute(MeshSpecification.TYPE_FLOAT4, 8, "test"), + new Attribute(MeshSpecification.TYPE_FLOAT4, 24, "test2"), + new Attribute( + MeshSpecification.TYPE_FLOAT4, + 40, + "test3" + ), + new Attribute( + MeshSpecification.TYPE_FLOAT4, + 56, + "test4" + ), + new Attribute( + MeshSpecification.TYPE_FLOAT4, + 72, + "test5" + ), + new Attribute( + MeshSpecification.TYPE_FLOAT4, + 88, + "test6" + ), + new Attribute( + MeshSpecification.TYPE_FLOAT4, + 104, + "test7" + ) + }; + Varying[] varyList = new Varying[0]; return MeshSpecification.make(attList, 120, varyList, vs, fs); } } -- cgit v1.2.3-59-g8ed1b From 2a49934e1dd9a5df9c7dc67e5dc2205c9f8cf523 Mon Sep 17 00:00:00 2001 From: Nader Jawad Date: Wed, 8 Feb 2023 11:56:37 -0800 Subject: Added support for BitmapShader anisotropic filtering Relnote: "Created new BitmapShader constructor that enables support for anisotropic filtering." Bug: 267687306 Test: Added tests to BitmapShaderTest Change-Id: Ie6e5551f3ae6140dc3afb2d65967b5a6bc08523c --- core/api/current.txt | 2 + graphics/java/android/graphics/BitmapShader.java | 49 ++++++++++++++++++++++-- libs/hwui/jni/Shader.cpp | 33 ++++++++++++---- 3 files changed, 74 insertions(+), 10 deletions(-) (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index fcb3a14d3cec..bfdddb52a6e8 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -14908,7 +14908,9 @@ package android.graphics { public class BitmapShader extends android.graphics.Shader { ctor public BitmapShader(@NonNull android.graphics.Bitmap, @NonNull android.graphics.Shader.TileMode, @NonNull android.graphics.Shader.TileMode); method public int getFilterMode(); + method public int getMaxAnisotropy(); method public void setFilterMode(int); + method public void setMaxAnisotropy(@IntRange(from=1) int); field public static final int FILTER_MODE_DEFAULT = 0; // 0x0 field public static final int FILTER_MODE_LINEAR = 2; // 0x2 field public static final int FILTER_MODE_NEAREST = 1; // 0x1 diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java index 43cb5ee8b5c0..2f6dd468511b 100644 --- a/graphics/java/android/graphics/BitmapShader.java +++ b/graphics/java/android/graphics/BitmapShader.java @@ -17,6 +17,7 @@ package android.graphics; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import java.lang.annotation.Retention; @@ -102,6 +103,8 @@ public class BitmapShader extends Shader { private boolean mRequestDirectSampling; + private int mMaxAniso = 0; + /** * Call this to create a new shader that will draw with a bitmap. * @@ -135,15 +138,47 @@ public class BitmapShader extends Shader { } /** - * Set the filter mode to be used when sampling from this shader + * Set the filter mode to be used when sampling from this shader. If this is configured + * then the anisotropic filtering value specified in any previous call to + * {@link #setMaxAnisotropy(int)} is ignored. */ public void setFilterMode(@FilterMode int mode) { if (mode != mFilterMode) { mFilterMode = mode; + mMaxAniso = 0; discardNativeInstance(); } } + /** + * Enables and configures the max anisotropy sampling value. If this value is configured, + * {@link #setFilterMode(int)} is ignored. + * + * Anisotropic filtering can enhance visual quality by removing aliasing effects of images + * that are at oblique viewing angles. This value is typically consumed as a power of 2 and + * anisotropic values of the next power of 2 typically provide twice the quality improvement + * as the previous value. For example, a sampling value of 4 would provide twice the improvement + * of a sampling value of 2. It is important to note that higher sampling values reach + * diminishing returns as the improvements between 8 and 16 can be slight. + * + * @param maxAnisotropy The Anisotropy value to use for filtering. Must be greater than 0. + */ + public void setMaxAnisotropy(@IntRange(from = 1) int maxAnisotropy) { + if (mMaxAniso != maxAnisotropy && maxAnisotropy > 0) { + mMaxAniso = maxAnisotropy; + mFilterMode = FILTER_MODE_DEFAULT; + discardNativeInstance(); + } + } + + /** + * Returns the current max anisotropic filtering value configured by + * {@link #setFilterMode(int)}. If {@link #setFilterMode(int)} is invoked this returns zero. + */ + public int getMaxAnisotropy() { + return mMaxAniso; + } + /** @hide */ /* package */ synchronized long getNativeInstanceWithDirectSampling() { mRequestDirectSampling = true; @@ -162,8 +197,13 @@ public class BitmapShader extends Shader { mIsDirectSampled = mRequestDirectSampling; mRequestDirectSampling = false; - return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, mTileY, - enableLinearFilter, mIsDirectSampled); + if (mMaxAniso > 0) { + return nativeCreateWithMaxAniso(nativeMatrix, mBitmap.getNativeInstance(), mTileX, + mTileY, mMaxAniso, mIsDirectSampled); + } else { + return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, mTileY, + enableLinearFilter, mIsDirectSampled); + } } /** @hide */ @@ -175,5 +215,8 @@ public class BitmapShader extends Shader { private static native long nativeCreate(long nativeMatrix, long bitmapHandle, int shaderTileModeX, int shaderTileModeY, boolean filter, boolean isDirectSampled); + + private static native long nativeCreateWithMaxAniso(long nativeMatrix, long bitmapHandle, + int shaderTileModeX, int shaderTileModeY, int maxAniso, boolean isDirectSampled); } diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index fa8e2e79c831..8a0db1c91d46 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -71,11 +71,9 @@ static jlong Shader_getNativeFinalizer(JNIEnv*, jobject) { return static_cast(reinterpret_cast(&Shader_safeUnref)); } -/////////////////////////////////////////////////////////////////////////////////////////////// - -static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle, - jint tileModeX, jint tileModeY, bool filter, - bool isDirectSampled) { +static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle, + jint tileModeX, jint tileModeY, bool isDirectSampled, + const SkSamplingOptions& sampling) { const SkMatrix* matrix = reinterpret_cast(matrixPtr); sk_sp image; if (bitmapHandle) { @@ -88,8 +86,7 @@ static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, j SkBitmap bitmap; image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode); } - SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest, - SkMipmapMode::kNone); + sk_sp shader; if (isDirectSampled) { shader = image->makeRawShader((SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling); @@ -107,6 +104,26 @@ static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, j /////////////////////////////////////////////////////////////////////////////////////////////// +static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle, + jint tileModeX, jint tileModeY, bool filter, + bool isDirectSampled) { + SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest, + SkMipmapMode::kNone); + return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY, + isDirectSampled, sampling); +} + +static jlong BitmapShader_constructorWithMaxAniso(JNIEnv* env, jobject o, jlong matrixPtr, + jlong bitmapHandle, jint tileModeX, + jint tileModeY, jint maxAniso, + bool isDirectSampled) { + auto sampling = SkSamplingOptions::Aniso(static_cast(maxAniso)); + return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY, + isDirectSampled, sampling); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + static std::vector convertColorLongs(JNIEnv* env, jlongArray colorArray) { const size_t count = env->GetArrayLength(colorArray); const jlong* colorValues = env->GetLongArrayElements(colorArray, nullptr); @@ -408,6 +425,8 @@ static const JNINativeMethod gShaderMethods[] = { static const JNINativeMethod gBitmapShaderMethods[] = { {"nativeCreate", "(JJIIZZ)J", (void*)BitmapShader_constructor}, + {"nativeCreateWithMaxAniso", "(JJIIIZ)J", (void*)BitmapShader_constructorWithMaxAniso}, + }; static const JNINativeMethod gLinearGradientMethods[] = { -- cgit v1.2.3-59-g8ed1b From 3e98bce93d2f46ee99d77711a3af558efacf6dcb Mon Sep 17 00:00:00 2001 From: John Reck Date: Tue, 7 Feb 2023 13:38:28 -0500 Subject: Add Gainmap API Bug: 266628247 Test: CtsGraphicsTestCases:GainmapTest Change-Id: I288516df8ba2bb72525462699d2704bb43a0fd90 --- core/api/current.txt | 23 +++ graphics/java/android/graphics/Bitmap.java | 11 +- graphics/java/android/graphics/Gainmap.java | 277 +++++++++++++++++++++++----- libs/hwui/hwui/ImageDecoder.cpp | 6 + libs/hwui/jni/Bitmap.cpp | 8 + libs/hwui/jni/Gainmap.cpp | 106 ++++++++--- 6 files changed, 360 insertions(+), 71 deletions(-) (limited to 'graphics/java/android') diff --git a/core/api/current.txt b/core/api/current.txt index de540bf93e4b..6c2ba6d4b780 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -14779,6 +14779,7 @@ package android.graphics { method @Nullable public android.graphics.ColorSpace getColorSpace(); method @NonNull public android.graphics.Bitmap.Config getConfig(); method public int getDensity(); + method @Nullable public android.graphics.Gainmap getGainmap(); method public int getGenerationId(); method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer(); method public int getHeight(); @@ -14794,6 +14795,7 @@ package android.graphics { method public int getScaledWidth(int); method public int getWidth(); method public boolean hasAlpha(); + method public boolean hasGainmap(); method public boolean hasMipMap(); method public boolean isMutable(); method public boolean isPremultiplied(); @@ -14805,6 +14807,7 @@ package android.graphics { method public void setColorSpace(@NonNull android.graphics.ColorSpace); method public void setConfig(@NonNull android.graphics.Bitmap.Config); method public void setDensity(int); + method public void setGainmap(@Nullable android.graphics.Gainmap); method public void setHasAlpha(boolean); method public void setHasMipMap(boolean); method public void setHeight(int); @@ -15338,6 +15341,26 @@ package android.graphics { ctor @Deprecated public EmbossMaskFilter(float[], float, float, float); } + public final class Gainmap { + ctor public Gainmap(@NonNull android.graphics.Bitmap); + method @NonNull public float getDisplayRatioForFullHdr(); + method @NonNull public float[] getEpsilonHdr(); + method @NonNull public float[] getEpsilonSdr(); + method @NonNull public android.graphics.Bitmap getGainmapContents(); + method @NonNull public float[] getGamma(); + method @NonNull public float getMinDisplayRatioForHdrTransition(); + method @NonNull public float[] getRatioMax(); + method @NonNull public float[] getRatioMin(); + method @NonNull public void setDisplayRatioForFullHdr(float); + method @NonNull public void setEpsilonHdr(float, float, float); + method @NonNull public void setEpsilonSdr(float, float, float); + method public void setGainmapContents(@NonNull android.graphics.Bitmap); + method @NonNull public void setGamma(float, float, float); + method @NonNull public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float); + method @NonNull public void setRatioMax(float, float, float); + method @NonNull public void setRatioMin(float, float, float); + } + public class HardwareBufferRenderer implements java.lang.AutoCloseable { ctor public HardwareBufferRenderer(@NonNull android.hardware.HardwareBuffer); method public void close(); diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 046373d671af..b1abc2a1a679 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -1899,7 +1899,6 @@ public final class Bitmap implements Parcelable { /** * Returns whether or not this Bitmap contains a Gainmap. - * @hide */ public boolean hasGainmap() { checkRecycled("Bitmap is recycled"); @@ -1908,7 +1907,6 @@ public final class Bitmap implements Parcelable { /** * Returns the gainmap or null if the bitmap doesn't contain a gainmap - * @hide */ public @Nullable Gainmap getGainmap() { checkRecycled("Bitmap is recycled"); @@ -1918,6 +1916,14 @@ public final class Bitmap implements Parcelable { return mGainmap; } + /** + * Sets a gainmap on this bitmap, or removes the gainmap if null + */ + public void setGainmap(@Nullable Gainmap gainmap) { + checkRecycled("Bitmap is recycled"); + nativeSetGainmap(mNativePtr, gainmap == null ? 0 : gainmap.mNativePtr); + } + /** * Fills the bitmap's pixels with the specified {@link Color}. * @@ -2403,6 +2409,7 @@ public final class Bitmap implements Parcelable { private static native void nativeSetImmutable(long nativePtr); private static native Gainmap nativeExtractGainmap(long nativePtr); + private static native void nativeSetGainmap(long bitmapPtr, long gainmapPtr); // ---------------- @CriticalNative ------------------- diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java index a25a60568510..53f23c08dab6 100644 --- a/graphics/java/android/graphics/Gainmap.java +++ b/graphics/java/android/graphics/Gainmap.java @@ -16,6 +16,7 @@ package android.graphics; +import android.annotation.FloatRange; import android.annotation.NonNull; import libcore.util.NativeAllocationRegistry; @@ -24,104 +25,288 @@ import libcore.util.NativeAllocationRegistry; * Gainmap represents a mechanism for augmenting an SDR image to produce an HDR one with variable * display adjustment capability. * - * It is a combination of a set of metadata describing the gainmap, as well as either a 1 or 3 + * It is a combination of a set of metadata describing how to apply the gainmap, as well as either + * a 1 (such as {@link android.graphics.Bitmap.Config#ALPHA_8} or 3 + * (such as {@link android.graphics.Bitmap.Config#ARGB_8888} with the alpha channel ignored) * channel Bitmap that represents the gainmap data itself. * - * @hide + * When rendering to an {@link android.content.pm.ActivityInfo#COLOR_MODE_HDR} activity, the + * hardware accelerated {@link Canvas} will automatically apply the gainmap when sufficient + * HDR headroom is available. + * + *

    Gainmap Structure

    + * + * The logical whole of a gainmap'd image consists of a base Bitmap that represents the original + * image as would be displayed without gainmap support in addition to a gainmap with a second + * enhancement image. In the case of a JPEG, the base image would be the typical 8-bit SDR image + * that the format is commonly associated with. The gainmap image is embedded alongside the base + * image, often at a lower resolution (such as 1/4th), along with some metadata to describe + * how to apply the gainmap. The gainmap image itself is then a greyscale image representing + * the transformation to apply onto the base image to reconstruct an HDR rendition of it. + * + * As such these "gainmap images" consist of 3 parts - a base {@link Bitmap} with a + * {@link Bitmap#getGainmap()} that returns an instance of this class which in turn contains + * the enhancement layer represented as another Bitmap, accessible via {@link #getGainmapContents()} + * + *

    Applying a gainmap manually

    + * + * When doing custom rendering such as to an OpenGL ES or Vulkan context, the gainmap is not + * automatically applied. In such situations, the following steps are appropriate to render the + * gainmap in combination with the base image. + * + * Suppose our display has HDR to SDR ratio of H, and we wish to display an image with gainmap on + * this display. Let B be the pixel value from the base image in a color space that has the + * primaries of the base image and a linear transfer function. Let G be the pixel value from the + * gainmap. Let D be the output pixel in the same color space as B. The value of D is computed + * as follows: + * + * First, let W be a weight parameter determining how much the gainmap will be applied. + * W = clamp((log(H) - log(displayRatioHdr)) / + * (log(displayRatioHdr) - log(displayRatioSdr), 0, 1) + * + * Next, let L be the gainmap value in log space. We compute this from the value G that was + * sampled from the texture as follows: + * L = mix(log(gainmapRatioMin), log(gainmapRatioMax), pow(G, gainmapGamma)) + * + * Finally, apply the gainmap to compute D, the displayed pixel. If the base image is SDR then + * compute: + * D = (B + epsilonSdr) * exp(L * W) - epsilonHdr + * If the base image is HDR then compute: + * D = (B + epsilonHdr) * exp(L * (W - 1)) - epsilonSdr + * + * In the above math, log() is a natural logarithm and exp() is natural exponentiation. */ -public class Gainmap { - private final long mNativePtr; - private final Bitmap mGainmapImage; +public final class Gainmap { - // called from JNI and Bitmap_Delegate. - private Gainmap(Bitmap gainmapImage, long nativeGainmap, int allocationByteCount, - boolean fromMalloc) { + // Use a Holder to allow static initialization of Gainmap in the boot image. + private static class NoImagePreloadHolder { + public static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + Gainmap.class.getClassLoader(), nGetFinalizer()); + } + + final long mNativePtr; + private Bitmap mGainmapContents; + + // called from JNI + private Gainmap(Bitmap gainmapContents, long nativeGainmap) { if (nativeGainmap == 0) { throw new RuntimeException("internal error: native gainmap is 0"); } - mGainmapImage = gainmapImage; + mGainmapContents = gainmapContents; mNativePtr = nativeGainmap; - final NativeAllocationRegistry registry; - if (fromMalloc) { - registry = NativeAllocationRegistry.createMalloced( - Bitmap.class.getClassLoader(), nGetFinalizer(), allocationByteCount); - } else { - registry = NativeAllocationRegistry.createNonmalloced( - Bitmap.class.getClassLoader(), nGetFinalizer(), allocationByteCount); - } - registry.registerNativeAllocation(this, nativeGainmap); + NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, nativeGainmap); + } + + /** + * Creates a gainmap from a given Bitmap. The caller is responsible for setting the various + * fields to the desired values. The defaults are as follows: + *
      + *
    • Ratio min is 1f, 1f, 1f
    • + *
    • Ratio max is 2f, 2f, 2f
    • + *
    • Gamma is 1f, 1f, 1f
    • + *
    • Epsilon SDR is 0f, 0f, 0f
    • + *
    • Epsilon HDR is 0f, 0f, 0f
    • + *
    • Display ratio SDR is 1f
    • + *
    • Display ratio HDR is 2f
    • + *
    + * It is strongly recommended that at least the ratio max and display ratio HDR are adjusted + * to better suit the given gainmap contents. + */ + public Gainmap(@NonNull Bitmap gainmapContents) { + this(gainmapContents, nCreateEmpty()); } /** - * Returns the image data of the gainmap represented as a Bitmap - * @return + * @return Returns the image data of the gainmap represented as a Bitmap. This is represented + * as a Bitmap for broad API compatibility, however certain aspects of the Bitmap are ignored + * such as {@link Bitmap#getColorSpace()} or {@link Bitmap#getGainmap()} as they are not + * relevant to the gainmap's enhancement layer. */ @NonNull - public Bitmap getGainmapImage() { - return mGainmapImage; + public Bitmap getGainmapContents() { + return mGainmapContents; } /** - * Sets the gainmap max metadata. For single-plane gainmaps, r, g, and b should be the same. + * Sets the image data of the gainmap. This is the 1 or 3 channel enhancement layer to apply + * to the base image. This is represented as a Bitmap for broad API compatibility, however + * certain aspects of the Bitmap are ignored such as {@link Bitmap#getColorSpace()} or + * {@link Bitmap#getGainmap()} as they are not relevant to the gainmap's enhancement layer. + * + * @param bitmap The non-null bitmap to set as the gainmap's contents + */ + public void setGainmapContents(@NonNull Bitmap bitmap) { + // TODO: Validate here or leave native-side? + if (bitmap.isRecycled()) throw new IllegalArgumentException("Bitmap is recycled"); + nSetBitmap(mNativePtr, bitmap); + mGainmapContents = bitmap; + } + + /** + * Sets the gainmap ratio min. For single-plane gainmaps, r, g, and b should be the same. + */ + @NonNull + public void setRatioMin(float r, float g, float b) { + nSetRatioMin(mNativePtr, r, g, b); + } + + /** + * Gets the gainmap ratio max. For single-plane gainmaps, all 3 components should be the + * same. The components are in r, g, b order. + */ + @NonNull + public float[] getRatioMin() { + float[] ret = new float[3]; + nGetRatioMin(mNativePtr, ret); + return ret; + } + + /** + * Sets the gainmap ratio max. For single-plane gainmaps, r, g, and b should be the same. */ @NonNull - public void setGainmapMax(float r, float g, float b) { - nSetGainmapMax(mNativePtr, r, g, b); + public void setRatioMax(float r, float g, float b) { + nSetRatioMax(mNativePtr, r, g, b); } /** - * Gets the gainmap max metadata. For single-plane gainmaps, all 3 components should be the + * Gets the gainmap ratio max. For single-plane gainmaps, all 3 components should be the * same. The components are in r, g, b order. */ @NonNull - public float[] getGainmapMax() { + public float[] getRatioMax() { float[] ret = new float[3]; - nGetGainmapMax(mNativePtr, ret); + nGetRatioMax(mNativePtr, ret); return ret; } /** - * Sets the maximum HDR ratio for the gainmap + * Sets the gainmap gamma. For single-plane gainmaps, r, g, and b should be the same. */ @NonNull - public void setHdrRatioMax(float max) { - nSetHdrRatioMax(mNativePtr, max); + public void setGamma(float r, float g, float b) { + nSetGamma(mNativePtr, r, g, b); } /** - * Gets the maximum HDR ratio for the gainmap + * Gets the gainmap gamma. For single-plane gainmaps, all 3 components should be the + * same. The components are in r, g, b order. */ @NonNull - public float getHdrRatioMax() { - return nGetHdrRatioMax(mNativePtr); + public float[] getGamma() { + float[] ret = new float[3]; + nGetGamma(mNativePtr, ret); + return ret; } /** - * Sets the maximum HDR ratio for the gainmap + * Sets the sdr epsilon which is used to avoid numerical instability. + * For single-plane gainmaps, r, g, and b should be the same. */ @NonNull - public void setHdrRatioMin(float min) { - nSetHdrRatioMin(mNativePtr, min); + public void setEpsilonSdr(float r, float g, float b) { + nSetEpsilonSdr(mNativePtr, r, g, b); } /** - * Gets the maximum HDR ratio for the gainmap + * Gets the sdr epsilon. For single-plane gainmaps, all 3 components should be the + * same. The components are in r, g, b order. */ @NonNull - public float getHdrRatioMin() { - return nGetHdrRatioMin(mNativePtr); + public float[] getEpsilonSdr() { + float[] ret = new float[3]; + nGetEpsilonSdr(mNativePtr, ret); + return ret; + } + + /** + * Sets the hdr epsilon which is used to avoid numerical instability. + * For single-plane gainmaps, r, g, and b should be the same. + */ + @NonNull + public void setEpsilonHdr(float r, float g, float b) { + nSetEpsilonHdr(mNativePtr, r, g, b); + } + + /** + * Gets the hdr epsilon. For single-plane gainmaps, all 3 components should be the + * same. The components are in r, g, b order. + */ + @NonNull + public float[] getEpsilonHdr() { + float[] ret = new float[3]; + nGetEpsilonHdr(mNativePtr, ret); + return ret; + } + + /** + * Sets the hdr/sdr ratio at which point the gainmap is fully applied. + * @param max The hdr/sdr ratio at which the gainmap is fully applied. Must be >= 1.0f + */ + @NonNull + public void setDisplayRatioForFullHdr(float max) { + if (!Float.isFinite(max) || max < 1f) { + throw new IllegalArgumentException( + "setDisplayRatioForFullHdr must be >= 1.0f, got = " + max); + } + nSetDisplayRatioHdr(mNativePtr, max); + } + + /** + * Gets the hdr/sdr ratio at which point the gainmap is fully applied. + */ + @NonNull + public float getDisplayRatioForFullHdr() { + return nGetDisplayRatioHdr(mNativePtr); + } + + /** + * Sets the hdr/sdr ratio below which only the SDR image is displayed. + * @param min The minimum hdr/sdr ratio at which to begin applying the gainmap. Must be >= 1.0f + */ + @NonNull + public void setMinDisplayRatioForHdrTransition(@FloatRange(from = 1.0f) float min) { + if (!Float.isFinite(min) || min < 1f) { + throw new IllegalArgumentException( + "setMinDisplayRatioForHdrTransition must be >= 1.0f, got = " + min); + } + nSetDisplayRatioSdr(mNativePtr, min); + } + + /** + * Gets the hdr/sdr ratio below which only the SDR image is displayed. + */ + @NonNull + public float getMinDisplayRatioForHdrTransition() { + return nGetDisplayRatioSdr(mNativePtr); } private static native long nGetFinalizer(); + private static native long nCreateEmpty(); + + private static native void nSetBitmap(long ptr, Bitmap bitmap); + + private static native void nSetRatioMin(long ptr, float r, float g, float b); + private static native void nGetRatioMin(long ptr, float[] components); + + private static native void nSetRatioMax(long ptr, float r, float g, float b); + private static native void nGetRatioMax(long ptr, float[] components); + + private static native void nSetGamma(long ptr, float r, float g, float b); + private static native void nGetGamma(long ptr, float[] components); + + private static native void nSetEpsilonSdr(long ptr, float r, float g, float b); + private static native void nGetEpsilonSdr(long ptr, float[] components); - private static native void nSetGainmapMax(long ptr, float r, float g, float b); - private static native void nGetGainmapMax(long ptr, float[] components); + private static native void nSetEpsilonHdr(long ptr, float r, float g, float b); + private static native void nGetEpsilonHdr(long ptr, float[] components); - private static native void nSetHdrRatioMax(long ptr, float max); - private static native float nGetHdrRatioMax(long ptr); + private static native void nSetDisplayRatioHdr(long ptr, float max); + private static native float nGetDisplayRatioHdr(long ptr); - private static native void nSetHdrRatioMin(long ptr, float min); - private static native float nGetHdrRatioMin(long ptr); + private static native void nSetDisplayRatioSdr(long ptr, float min); + private static native float nGetDisplayRatioSdr(long ptr); } diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index 6d7c72772c7e..5bf53d0c1790 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -506,6 +506,9 @@ SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) { decoder.mOverrideOrigin.emplace(getOrigin()); // Update mDecodeSize / mTargetSize for the overridden origin decoder.setTargetSize(decoder.width(), decoder.height()); + if (decoder.gray()) { + decoder.setOutColorType(kGray_8_SkColorType); + } const bool isScaled = width() != mTargetSize.width() || height() != mTargetSize.height(); @@ -528,6 +531,9 @@ SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) { } SkImageInfo bitmapInfo = decoder.getOutputInfo(); + if (bitmapInfo.colorType() == kGray_8_SkColorType) { + bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType); + } SkBitmap bm; if (!bm.setInfo(bitmapInfo)) { diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp index 10c287d1e07d..aee5242c72d3 100644 --- a/libs/hwui/jni/Bitmap.cpp +++ b/libs/hwui/jni/Bitmap.cpp @@ -1269,6 +1269,13 @@ static jobject Bitmap_extractGainmap(JNIEnv* env, jobject, jlong bitmapHandle) { return Gainmap_extractFromBitmap(env, bitmapHolder->bitmap()); } +static void Bitmap_setGainmap(JNIEnv*, jobject, jlong bitmapHandle, jlong gainmapPtr) { + LocalScopedBitmap bitmapHolder(bitmapHandle); + if (!bitmapHolder.valid()) return; + uirenderer::Gainmap* gainmap = reinterpret_cast(gainmapPtr); + bitmapHolder->bitmap().setGainmap(sp::fromExisting(gainmap)); +} + /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gBitmapMethods[] = { @@ -1320,6 +1327,7 @@ static const JNINativeMethod gBitmapMethods[] = { {"nativeIsSRGBLinear", "(J)Z", (void*)Bitmap_isSRGBLinear}, {"nativeSetImmutable", "(J)V", (void*)Bitmap_setImmutable}, {"nativeExtractGainmap", "(J)Landroid/graphics/Gainmap;", (void*)Bitmap_extractGainmap}, + {"nativeSetGainmap", "(JJ)V", (void*)Bitmap_setGainmap}, // ------------ @CriticalNative ---------------- {"nativeIsImmutable", "(J)Z", (void*)Bitmap_isImmutable}, diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp index f2efbc7b833e..9cd3fb0799d7 100644 --- a/libs/hwui/jni/Gainmap.cpp +++ b/libs/hwui/jni/Gainmap.cpp @@ -45,20 +45,18 @@ static int getCreateFlags(const sk_sp& bitmap) { jobject Gainmap_extractFromBitmap(JNIEnv* env, const Bitmap& bitmap) { auto gainmap = bitmap.gainmap(); jobject jGainmapImage; - size_t allocationSize; { // Scope to guard the release of nativeBitmap auto nativeBitmap = gainmap->bitmap; const int createFlags = getCreateFlags(nativeBitmap); - allocationSize = nativeBitmap->getAllocationByteCount(); jGainmapImage = bitmap::createBitmap(env, nativeBitmap.release(), createFlags); } // Grab a ref for the jobject gainmap->incStrong(0); jobject obj = env->NewObject(gGainmap_class, gGainmap_constructorMethodID, jGainmapImage, - gainmap.get(), allocationSize + sizeof(Gainmap), true); + gainmap.get()); if (env->ExceptionCheck() != 0) { // sadtrombone @@ -77,47 +75,109 @@ static jlong Gainmap_getNativeFinalizer(JNIEnv*, jobject) { return static_cast(reinterpret_cast(&Gainmap_destructor)); } -static void Gainmap_setGainmapMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, +jlong Gainmap_createEmpty(JNIEnv*, jobject) { + Gainmap* gainmap = new Gainmap(); + gainmap->incStrong(0); + return static_cast(reinterpret_cast(gainmap)); +} + +static void Gainmap_setBitmap(JNIEnv* env, jobject, jlong gainmapPtr, jobject jBitmap) { + android::Bitmap* bitmap = GraphicsJNI::getNativeBitmap(env, jBitmap); + fromJava(gainmapPtr)->bitmap = sk_ref_sp(bitmap); +} + +static void Gainmap_setRatioMin(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) { + fromJava(gainmapPtr)->info.fGainmapRatioMin = {r, g, b, 1.f}; +} + +static void Gainmap_getRatioMin(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) { + const auto value = fromJava(gainmapPtr)->info.fGainmapRatioMin; + jfloat buf[3]{value.fR, value.fG, value.fB}; + env->SetFloatArrayRegion(components, 0, 3, buf); +} + +static void Gainmap_setRatioMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) { + fromJava(gainmapPtr)->info.fGainmapRatioMax = {r, g, b, 1.f}; +} + +static void Gainmap_getRatioMax(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) { + const auto value = fromJava(gainmapPtr)->info.fGainmapRatioMax; + jfloat buf[3]{value.fR, value.fG, value.fB}; + env->SetFloatArrayRegion(components, 0, 3, buf); +} + +static void Gainmap_setGamma(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) { + fromJava(gainmapPtr)->info.fGainmapGamma = {r, g, b, 1.f}; +} + +static void Gainmap_getGamma(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) { + const auto value = fromJava(gainmapPtr)->info.fGainmapGamma; + jfloat buf[3]{value.fR, value.fG, value.fB}; + env->SetFloatArrayRegion(components, 0, 3, buf); +} + +static void Gainmap_setEpsilonSdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, + jfloat b) { + fromJava(gainmapPtr)->info.fEpsilonSdr = {r, g, b, 1.f}; +} + +static void Gainmap_getEpsilonSdr(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) { + const auto value = fromJava(gainmapPtr)->info.fEpsilonSdr; + jfloat buf[3]{value.fR, value.fG, value.fB}; + env->SetFloatArrayRegion(components, 0, 3, buf); +} + +static void Gainmap_setEpsilonHdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) { - fromJava(gainmapPtr)->info.fLogRatioMax = {r, g, b, 1.f}; + fromJava(gainmapPtr)->info.fEpsilonHdr = {r, g, b, 1.f}; } -static void Gainmap_getGainmapMax(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) { - const auto ratioMax = fromJava(gainmapPtr)->info.fLogRatioMax; - jfloat buf[3]{ratioMax.fR, ratioMax.fG, ratioMax.fB}; +static void Gainmap_getEpsilonHdr(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) { + const auto value = fromJava(gainmapPtr)->info.fEpsilonHdr; + jfloat buf[3]{value.fR, value.fG, value.fB}; env->SetFloatArrayRegion(components, 0, 3, buf); } -static void Gainmap_setHdrRatioMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat max) { - fromJava(gainmapPtr)->info.fHdrRatioMax = max; +static void Gainmap_setDisplayRatioHdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat max) { + fromJava(gainmapPtr)->info.fDisplayRatioHdr = max; } -static jfloat Gainmap_getHdrRatioMax(JNIEnv*, jobject, jlong gainmapPtr) { - return fromJava(gainmapPtr)->info.fHdrRatioMax; +static jfloat Gainmap_getDisplayRatioHdr(JNIEnv*, jobject, jlong gainmapPtr) { + return fromJava(gainmapPtr)->info.fDisplayRatioHdr; } -static void Gainmap_setHdrRatioMin(JNIEnv*, jobject, jlong gainmapPtr, jfloat min) { - fromJava(gainmapPtr)->info.fHdrRatioMin = min; +static void Gainmap_setDisplayRatioSdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat min) { + fromJava(gainmapPtr)->info.fDisplayRatioSdr = min; } -static jfloat Gainmap_getHdrRatioMin(JNIEnv*, jobject, jlong gainmapPtr) { - return fromJava(gainmapPtr)->info.fHdrRatioMin; +static jfloat Gainmap_getDisplayRatioSdr(JNIEnv*, jobject, jlong gainmapPtr) { + return fromJava(gainmapPtr)->info.fDisplayRatioSdr; } static const JNINativeMethod gGainmapMethods[] = { {"nGetFinalizer", "()J", (void*)Gainmap_getNativeFinalizer}, - {"nSetGainmapMax", "(JFFF)V", (void*)Gainmap_setGainmapMax}, - {"nGetGainmapMax", "(J[F)V", (void*)Gainmap_getGainmapMax}, - {"nSetHdrRatioMax", "(JF)V", (void*)Gainmap_setHdrRatioMax}, - {"nGetHdrRatioMax", "(J)F", (void*)Gainmap_getHdrRatioMax}, - {"nSetHdrRatioMin", "(JF)V", (void*)Gainmap_setHdrRatioMin}, - {"nGetHdrRatioMin", "(J)F", (void*)Gainmap_getHdrRatioMin}, + {"nCreateEmpty", "()J", (void*)Gainmap_createEmpty}, + {"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*)Gainmap_setBitmap}, + {"nSetRatioMin", "(JFFF)V", (void*)Gainmap_setRatioMin}, + {"nGetRatioMin", "(J[F)V", (void*)Gainmap_getRatioMin}, + {"nSetRatioMax", "(JFFF)V", (void*)Gainmap_setRatioMax}, + {"nGetRatioMax", "(J[F)V", (void*)Gainmap_getRatioMax}, + {"nSetGamma", "(JFFF)V", (void*)Gainmap_setGamma}, + {"nGetGamma", "(J[F)V", (void*)Gainmap_getGamma}, + {"nSetEpsilonSdr", "(JFFF)V", (void*)Gainmap_setEpsilonSdr}, + {"nGetEpsilonSdr", "(J[F)V", (void*)Gainmap_getEpsilonSdr}, + {"nSetEpsilonHdr", "(JFFF)V", (void*)Gainmap_setEpsilonHdr}, + {"nGetEpsilonHdr", "(J[F)V", (void*)Gainmap_getEpsilonHdr}, + {"nSetDisplayRatioHdr", "(JF)V", (void*)Gainmap_setDisplayRatioHdr}, + {"nGetDisplayRatioHdr", "(J)F", (void*)Gainmap_getDisplayRatioHdr}, + {"nSetDisplayRatioSdr", "(JF)V", (void*)Gainmap_setDisplayRatioSdr}, + {"nGetDisplayRatioSdr", "(J)F", (void*)Gainmap_getDisplayRatioSdr}, }; int register_android_graphics_Gainmap(JNIEnv* env) { gGainmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Gainmap")); gGainmap_constructorMethodID = - GetMethodIDOrDie(env, gGainmap_class, "", "(Landroid/graphics/Bitmap;JIZ)V"); + GetMethodIDOrDie(env, gGainmap_class, "", "(Landroid/graphics/Bitmap;J)V"); return android::RegisterMethodsOrDie(env, "android/graphics/Gainmap", gGainmapMethods, NELEM(gGainmapMethods)); } -- cgit v1.2.3-59-g8ed1b From 01472127c059b8dc8c7a2cd15b7e515d47a5a758 Mon Sep 17 00:00:00 2001 From: John Reck Date: Wed, 1 Feb 2023 20:10:12 -0500 Subject: Wire up colorMode="hdr" on VRI Test: silkfx Bug: 266628247 Change-Id: I6633436f38d83a0fc5bfa59a29e684a9f1712843 --- core/java/android/content/pm/ActivityInfo.java | 9 +- core/java/android/view/ViewRootImpl.java | 57 +++++++++--- .../java/android/graphics/HardwareRenderer.java | 2 +- .../test/silkfx/common/ColorModeControls.kt | 100 ++++++--------------- 4 files changed, 79 insertions(+), 89 deletions(-) (limited to 'graphics/java/android') diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index db72e29efde1..f8f2663063a6 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -350,7 +350,14 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @see android.R.attr#colorMode */ public static final int COLOR_MODE_HDR = 2; - // 3 Corresponds to android::uirenderer::ColorMode::Hdr10. + + /** + * Comparison point against COLOR_MODE_HDR that uses 1010102 + * Only for internal test usages + * @hide + */ + public static final int COLOR_MODE_HDR10 = 3; + /** * Value of {@link #colorMode} indicating that the activity should use an * 8 bit alpha buffer if the presentation display supports it. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 480abe03b5f3..6b0f8509b8aa 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -683,9 +683,10 @@ public final class ViewRootImpl implements ViewParent, private BLASTBufferQueue mBlastBufferQueue; - private boolean mUpdateSdrHdrRatioInfo = false; - private float mDesiredSdrHdrRatio = 1f; - private float mRenderSdrHdrRatio = 1f; + private boolean mUpdateHdrSdrRatioInfo = false; + private float mDesiredHdrSdrRatio = 1f; + private float mRenderHdrSdrRatio = 1f; + private Consumer mHdrSdrRatioChangedListener = null; /** * Child container layer of {@code mSurface} with the same bounds as its parent, and cropped to @@ -1947,6 +1948,9 @@ public final class ViewRootImpl implements ViewParent, private void updateInternalDisplay(int displayId, Resources resources) { final Display preferredDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId, resources); + if (mHdrSdrRatioChangedListener != null && mDisplay != null) { + mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener); + } if (preferredDisplay == null) { // Fallback to use default display. Slog.w(TAG, "Cannot get desired display with Id: " + displayId); @@ -1955,6 +1959,9 @@ public final class ViewRootImpl implements ViewParent, } else { mDisplay = preferredDisplay; } + if (mHdrSdrRatioChangedListener != null && mDisplay != null) { + mDisplay.registerHdrSdrRatioChangedListener(mExecutor, mHdrSdrRatioChangedListener); + } mContext.updateDisplay(mDisplay.getDisplayId()); } @@ -4838,11 +4845,11 @@ public final class ViewRootImpl implements ViewParent, useAsyncReport = true; - if (mUpdateSdrHdrRatioInfo) { - mUpdateSdrHdrRatioInfo = false; + if (mUpdateHdrSdrRatioInfo) { + mUpdateHdrSdrRatioInfo = false; applyTransactionOnDraw(mTransaction.setExtendedRangeBrightness( - getSurfaceControl(), mRenderSdrHdrRatio, mDesiredSdrHdrRatio)); - mAttachInfo.mThreadedRenderer.setTargetSdrHdrRatio(mRenderSdrHdrRatio); + getSurfaceControl(), mRenderHdrSdrRatio, mDesiredHdrSdrRatio)); + mAttachInfo.mThreadedRenderer.setTargetHdrSdrRatio(mRenderHdrSdrRatio); } if (forceDraw) { @@ -5372,6 +5379,10 @@ public final class ViewRootImpl implements ViewParent, if (mAttachInfo.mThreadedRenderer == null) { return; } + if ((colorMode == ActivityInfo.COLOR_MODE_HDR || colorMode == ActivityInfo.COLOR_MODE_HDR10) + && !mDisplay.isHdrSdrRatioAvailable()) { + colorMode = ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT; + } // TODO: Centralize this sanitization? Why do we let setting bad modes? // Alternatively, can we just let HWUI figure it out? Do we need to care here? if (colorMode != ActivityInfo.COLOR_MODE_A8 @@ -5379,17 +5390,28 @@ public final class ViewRootImpl implements ViewParent, colorMode = ActivityInfo.COLOR_MODE_DEFAULT; } float desiredRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode); - if (desiredRatio != mDesiredSdrHdrRatio) { - mDesiredSdrHdrRatio = desiredRatio; - mUpdateSdrHdrRatioInfo = true; + if (desiredRatio != mDesiredHdrSdrRatio) { + mDesiredHdrSdrRatio = desiredRatio; + mRenderHdrSdrRatio = mDisplay.getHdrSdrRatio(); + mUpdateHdrSdrRatioInfo = true; + + if (mDesiredHdrSdrRatio < 1.01f) { + mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener); + mHdrSdrRatioChangedListener = null; + } else { + mHdrSdrRatioChangedListener = display -> { + setTargetHdrSdrRatio(display.getHdrSdrRatio()); + }; + mDisplay.registerHdrSdrRatioChangedListener(mExecutor, mHdrSdrRatioChangedListener); + } } } /** happylint */ - public void setTargetSdrHdrRatio(float ratio) { - if (mRenderSdrHdrRatio != ratio) { - mRenderSdrHdrRatio = ratio; - mUpdateSdrHdrRatioInfo = true; + public void setTargetHdrSdrRatio(float ratio) { + if (mRenderHdrSdrRatio != ratio) { + mRenderHdrSdrRatio = ratio; + mUpdateHdrSdrRatioInfo = true; invalidate(); } } @@ -5965,6 +5987,9 @@ public final class ViewRootImpl implements ViewParent, } final ViewRootHandler mHandler = new ViewRootHandler(); + private final Executor mExecutor = (Runnable r) -> { + mHandler.post(r); + }; /** * Something in the current window tells us we need to change the touch mode. For @@ -8950,6 +8975,10 @@ public final class ViewRootImpl implements ViewParent, private void destroyHardwareRenderer() { ThreadedRenderer hardwareRenderer = mAttachInfo.mThreadedRenderer; + if (mHdrSdrRatioChangedListener != null) { + mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener); + } + if (hardwareRenderer != null) { if (mHardwareRendererObserver != null) { hardwareRenderer.removeObserver(mHardwareRendererObserver); diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 0488b9d8b201..9ed3d9c3c94b 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -667,7 +667,7 @@ public class HardwareRenderer { } /** @hide */ - public void setTargetSdrHdrRatio(float ratio) { + public void setTargetHdrSdrRatio(float ratio) { if (ratio < 1.f || !Float.isFinite(ratio)) ratio = 1.f; nSetTargetSdrHdrRatio(mNativeProxy, ratio); } diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt index 1bd8f6a68cbd..56ab755af47b 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt @@ -18,10 +18,10 @@ package com.android.test.silkfx.common import android.content.Context import android.content.pm.ActivityInfo -import android.hardware.display.DisplayManager import android.util.AttributeSet import android.util.Log import android.view.Display +import android.view.View import android.view.Window import android.widget.Button import android.widget.LinearLayout @@ -31,22 +31,11 @@ import com.android.test.silkfx.app.WindowObserver import java.util.function.Consumer class ColorModeControls : LinearLayout, WindowObserver { - private val COLOR_MODE_HDR10 = 3 - private val SDR_WHITE_POINTS = floatArrayOf(200f, 250f, 300f, 350f, 400f, 100f, 150f) - constructor(context: Context) : this(context, null) - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { - displayManager = context.getSystemService(DisplayManager::class.java)!! - displayId = context.getDisplayId() - } + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) private var window: Window? = null private var currentModeDisplay: TextView? = null - private val displayManager: DisplayManager - private var targetSdrWhitePointIndex = 0 - private var displayId: Int - - private val whitePoint get() = SDR_WHITE_POINTS[targetSdrWhitePointIndex] override fun onFinishInflate() { super.onFinishInflate() @@ -65,88 +54,53 @@ class ColorModeControls : LinearLayout, WindowObserver { setColorMode(ActivityInfo.COLOR_MODE_HDR) } findViewById
    * * att1 would have an offset of 0, while att2 would have an offset of 12 bytes. + * + * This is consumed as part of + * {@link MeshSpecification#make(Attribute[], int, Varying[], String, String, ColorSpace, int)} + * to create a {@link MeshSpecification} instance. */ public static class Attribute { @Type @@ -175,7 +212,15 @@ public class MeshSpecification { } /** - * Data class to represent a single varying variable. + * Data class to represent a single varying variable. A Varying variable can be altered by the + * vertex shader defined on the mesh but not by the fragment shader defined by AGSL. + * + * See https://developer.android.com/develop/ui/views/graphics/agsl for more information + * regarding Android Graphics Shader Language. + * + * This is consumed as part of + * {@link MeshSpecification#make(Attribute[], int, Varying[], String, String, ColorSpace, int)} + * to create a {@link MeshSpecification} instance. */ public static class Varying { @Type @@ -220,7 +265,7 @@ public class MeshSpecification { /** * Creates a {@link MeshSpecification} object for use within {@link Mesh}. This uses a default - * color space of {@link ColorSpace.Named#SRGB} and {@link AlphaType} of + * color space of {@link ColorSpace.Named#SRGB} and alphaType of * {@link #ALPHA_TYPE_PREMULTIPLIED}. * * @param attributes list of attributes represented by {@link Attribute}. Can hold a max of @@ -233,7 +278,11 @@ public class MeshSpecification { * the 6 varyings allowed. * @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position * varying is set within the shader to get proper results. + * See {@link MeshSpecification} for an example vertex shader + * implementation * @param fragmentShader fragment shader to be supplied to the mesh. + * See {@link MeshSpecification} for an example fragment shader + * implementation * @return {@link MeshSpecification} object for use when creating {@link Mesh} */ @NonNull @@ -253,7 +302,7 @@ public class MeshSpecification { } /** - * Creates a {@link MeshSpecification} object. This uses a default {@link AlphaType} of + * Creates a {@link MeshSpecification} object. This uses a default alphaType of * {@link #ALPHA_TYPE_PREMULTIPLIED}. * * @param attributes list of attributes represented by {@link Attribute}. Can hold a max of @@ -266,7 +315,11 @@ public class MeshSpecification { * the 6 varyings allowed. * @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position * varying is set within the shader to get proper results. + * See {@link MeshSpecification} for an example vertex shader + * implementation * @param fragmentShader fragment shader to be supplied to the mesh. + * See {@link MeshSpecification} for an example fragment shader + * implementation * @param colorSpace {@link ColorSpace} to tell what color space to work in. * @return {@link MeshSpecification} object for use when creating {@link Mesh} */ @@ -301,7 +354,11 @@ public class MeshSpecification { * the 6 varyings allowed. * @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position * varying is set within the shader to get proper results. + * See {@link MeshSpecification} for an example vertex shader + * implementation * @param fragmentShader fragment shader to be supplied to the mesh. + * See {@link MeshSpecification} for an example fragment shader + * implementation * @param colorSpace {@link ColorSpace} to tell what color space to work in. * @param alphaType Describes how to interpret the alpha component for a pixel. Must be * one of -- cgit v1.2.3-59-g8ed1b From 1a15acb484ef18a309b21851fbdd27e6aee9ab68 Mon Sep 17 00:00:00 2001 From: John Reck Date: Mon, 8 May 2023 18:17:55 -0400 Subject: Track input shaders java-side for heap tooling Bug: 280338223 Test: runahat on test app Change-Id: I5a853a8e6865d30e343a5f0cf57045e44798d9ec --- graphics/java/android/graphics/RuntimeShader.java | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java index 9c36fc36474c..3e6457919031 100644 --- a/graphics/java/android/graphics/RuntimeShader.java +++ b/graphics/java/android/graphics/RuntimeShader.java @@ -19,6 +19,7 @@ package android.graphics; import android.annotation.ColorInt; import android.annotation.ColorLong; import android.annotation.NonNull; +import android.util.ArrayMap; import android.view.Window; import libcore.util.NativeAllocationRegistry; @@ -255,6 +256,12 @@ public class RuntimeShader extends Shader { */ private long mNativeInstanceRuntimeShaderBuilder; + /** + * For tracking GC usage. Keep a java-side reference for reachable objects to + * enable better heap tracking & tooling support + */ + private ArrayMap mShaderUniforms = new ArrayMap<>(); + /** * Creates a new RuntimeShader. * @@ -490,6 +497,7 @@ public class RuntimeShader extends Shader { if (shader == null) { throw new NullPointerException("The shader parameter must not be null"); } + mShaderUniforms.put(shaderName, shader); nativeUpdateShader( mNativeInstanceRuntimeShaderBuilder, shaderName, shader.getNativeInstance()); discardNativeInstance(); @@ -511,6 +519,7 @@ public class RuntimeShader extends Shader { throw new NullPointerException("The shader parameter must not be null"); } + mShaderUniforms.put(shaderName, shader); nativeUpdateShader(mNativeInstanceRuntimeShaderBuilder, shaderName, shader.getNativeInstanceWithDirectSampling()); discardNativeInstance(); -- cgit v1.2.3-59-g8ed1b From 65327c05c4040a580edba906d8ac517a664d63fd Mon Sep 17 00:00:00 2001 From: Jernej Virag Date: Wed, 3 May 2023 14:30:45 +0200 Subject: Add ability to decode only image information with ImageDecoder This creates a @hide API which only retrieves ImageInfo object without continuing the decode of full image. It mirrors BitmapOptions.inJustDecodeBounds behaviour and allows checking dimensions and color space configuration of images without proceeding to full decode (if able). Bug: 280572656 Test: atest android.graphics.ImageDecoderTest android.graphics.cts.ImageDecoderTest Change-Id: I704ec6c41d76f655222da7ee322e3b0e4954533d --- .../res/drawable-nodpi/animated_webp.webp | Bin 0 -> 24754 bytes .../src/android/graphics/ImageDecoderTest.java | 80 +++++++++++++++++++++ graphics/java/android/graphics/ImageDecoder.java | 61 ++++++++++++---- 3 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 core/tests/coretests/res/drawable-nodpi/animated_webp.webp create mode 100644 core/tests/coretests/src/android/graphics/ImageDecoderTest.java (limited to 'graphics/java/android') diff --git a/core/tests/coretests/res/drawable-nodpi/animated_webp.webp b/core/tests/coretests/res/drawable-nodpi/animated_webp.webp new file mode 100644 index 000000000000..2d28dbfd380b Binary files /dev/null and b/core/tests/coretests/res/drawable-nodpi/animated_webp.webp differ diff --git a/core/tests/coretests/src/android/graphics/ImageDecoderTest.java b/core/tests/coretests/src/android/graphics/ImageDecoderTest.java new file mode 100644 index 000000000000..8b3e6ba283b2 --- /dev/null +++ b/core/tests/coretests/src/android/graphics/ImageDecoderTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.graphics; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.frameworks.coretests.R; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ImageDecoderTest { + + private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + + @Test + public void onDecodeHeader_png_returnsPopulatedData() throws IOException { + ImageDecoder.Source src = + ImageDecoder.createSource(mContext.getResources(), R.drawable.gettysburg); + ImageDecoder.ImageInfo info = ImageDecoder.decodeHeader(src); + assertThat(info.getSize().getWidth()).isEqualTo(432); + assertThat(info.getSize().getHeight()).isEqualTo(291); + assertThat(info.getMimeType()).isEqualTo("image/png"); + assertThat(info.getColorSpace()).isNotNull(); + assertThat(info.getColorSpace().getModel()).isEqualTo(ColorSpace.Model.RGB); + assertThat(info.getColorSpace().getId()).isEqualTo(0); + assertThat(info.isAnimated()).isFalse(); + } + + @Test + public void onDecodeHeader_animatedWebP_returnsPopulatedData() throws IOException { + ImageDecoder.Source src = + ImageDecoder.createSource(mContext.getResources(), R.drawable.animated_webp); + ImageDecoder.ImageInfo info = ImageDecoder.decodeHeader(src); + assertThat(info.getSize().getWidth()).isEqualTo(278); + assertThat(info.getSize().getHeight()).isEqualTo(183); + assertThat(info.getMimeType()).isEqualTo("image/webp"); + assertThat(info.getColorSpace()).isNotNull(); + assertThat(info.getColorSpace().getModel()).isEqualTo(ColorSpace.Model.RGB); + assertThat(info.getColorSpace().getId()).isEqualTo(0); + assertThat(info.isAnimated()).isTrue(); + } + + @Test(expected = IOException.class) + public void onDecodeHeader_invalidSource_throwsException() throws IOException { + ImageDecoder.Source src = ImageDecoder.createSource(new File("/this/file/does/not/exist")); + ImageDecoder.decodeHeader(src); + } + + @Test(expected = IOException.class) + public void onDecodeHeader_invalidResource_throwsException() throws IOException { + ImageDecoder.Source src = + ImageDecoder.createSource(mContext.getResources(), R.drawable.box); + ImageDecoder.decodeHeader(src); + } +} diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index dd4b58eb83dc..b2da233fc21e 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -627,11 +627,19 @@ public final class ImageDecoder implements AutoCloseable { */ public static class ImageInfo { private final Size mSize; - private ImageDecoder mDecoder; + private final boolean mIsAnimated; + private final String mMimeType; + private final ColorSpace mColorSpace; - private ImageInfo(@NonNull ImageDecoder decoder) { - mSize = new Size(decoder.mWidth, decoder.mHeight); - mDecoder = decoder; + private ImageInfo( + @NonNull Size size, + boolean isAnimated, + @NonNull String mimeType, + @Nullable ColorSpace colorSpace) { + mSize = size; + mIsAnimated = isAnimated; + mMimeType = mimeType; + mColorSpace = colorSpace; } /** @@ -647,7 +655,7 @@ public final class ImageDecoder implements AutoCloseable { */ @NonNull public String getMimeType() { - return mDecoder.getMimeType(); + return mMimeType; } /** @@ -657,7 +665,7 @@ public final class ImageDecoder implements AutoCloseable { * return an {@link AnimatedImageDrawable}.

    */ public boolean isAnimated() { - return mDecoder.mAnimated; + return mIsAnimated; } /** @@ -669,7 +677,7 @@ public final class ImageDecoder implements AutoCloseable { */ @Nullable public ColorSpace getColorSpace() { - return mDecoder.getColorSpace(); + return mColorSpace; } }; @@ -1798,12 +1806,39 @@ public final class ImageDecoder implements AutoCloseable { private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener, @NonNull Source src) { if (listener != null) { - ImageInfo info = new ImageInfo(this); - try { - listener.onHeaderDecoded(this, info, src); - } finally { - info.mDecoder = null; - } + ImageInfo info = + new ImageInfo( + new Size(mWidth, mHeight), mAnimated, getMimeType(), getColorSpace()); + listener.onHeaderDecoded(this, info, src); + } + } + + /** + * Return {@link ImageInfo} from a {@code Source}. + * + *

    Returns the same {@link ImageInfo} object that a usual decoding process would return as + * part of {@link OnHeaderDecodedListener}. + * + * @param src representing the encoded image. + * @return ImageInfo describing the image. + * @throws IOException if {@code src} is not found, is an unsupported format, or cannot be + * decoded for any reason. + * @hide + */ + @WorkerThread + @NonNull + public static ImageInfo decodeHeader(@NonNull Source src) throws IOException { + Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ImageDecoder#decodeHeader"); + try (ImageDecoder decoder = src.createImageDecoder(true /*preferAnimation*/)) { + // We don't want to leak decoder so resolve all properties immediately. + return new ImageInfo( + new Size(decoder.mWidth, decoder.mHeight), + decoder.mAnimated, + decoder.getMimeType(), + decoder.getColorSpace()); + } finally { + // Close the ImageDecoder#decodeHeader trace. + Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } } -- cgit v1.2.3-59-g8ed1b From cab4afeb64bbc6280990b34bd55270b19218ac04 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 9 May 2023 11:25:22 -0700 Subject: hwui: send TextureView hint to SF So that SF could use this hint when choosing the refresh rate. SF would only try to heuristically calculate the frame rate of a layer when TextureView is updating. This fixes a bug where SF tries to heuristically calculate the frame rate for UI animations but fails due to long frames. Bug: 280249265 Test: Playing a video on Facebook and observe refresh rate Test: go/cb-pcmark Change-Id: I0d54d62b97ff48583fbe3cc0da188fe85810fd5e --- core/java/android/view/TextureView.java | 4 ++++ graphics/java/android/graphics/RenderNode.java | 13 +++++++++++++ libs/hwui/RenderNode.cpp | 3 +++ libs/hwui/RenderNode.h | 4 ++++ libs/hwui/TreeInfo.h | 4 ++++ libs/hwui/jni/android_graphics_RenderNode.cpp | 6 ++++++ libs/hwui/renderthread/CanvasContext.cpp | 7 ++++--- libs/hwui/renderthread/CanvasContext.h | 2 +- libs/hwui/renderthread/DrawFrameTask.cpp | 5 ++++- 9 files changed, 43 insertions(+), 5 deletions(-) (limited to 'graphics/java/android') diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 15e0cce9af10..bdd0a9cf653a 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -206,6 +206,7 @@ public class TextureView extends View { */ public TextureView(@NonNull Context context) { super(context); + mRenderNode.setIsTextureView(); } /** @@ -216,6 +217,7 @@ public class TextureView extends View { */ public TextureView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); + mRenderNode.setIsTextureView(); } /** @@ -229,6 +231,7 @@ public class TextureView extends View { */ public TextureView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + mRenderNode.setIsTextureView(); } /** @@ -247,6 +250,7 @@ public class TextureView extends View { public TextureView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mRenderNode.setIsTextureView(); } /** diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index dadbd8d2d1aa..2e91c240d71b 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -1561,6 +1561,16 @@ public final class RenderNode { return nGetUniqueId(mNativeRenderNode); } + /** + * Captures whether this RenderNote represents a TextureView + * TODO(b/281695725): Clean this up once TextureView use setFrameRate API + * + * @hide + */ + public void setIsTextureView() { + nSetIsTextureView(mNativeRenderNode); + } + /////////////////////////////////////////////////////////////////////////// // Animations /////////////////////////////////////////////////////////////////////////// @@ -1891,4 +1901,7 @@ public final class RenderNode { @CriticalNative private static native long nGetUniqueId(long renderNode); + + @CriticalNative + private static native void nSetIsTextureView(long renderNode); } diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index b348a6ecaae4..1c39db3a31bb 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -152,6 +152,9 @@ void RenderNode::damageSelf(TreeInfo& info) { // TODO: Get this from the display list ops or something info.damageAccumulator->dirty(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); } + if (!mIsTextureView) { + info.out.solelyTextureViewUpdates = false; + } } } diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index bdc48e91f6cb..d1e04adcb642 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -221,6 +221,8 @@ public: int64_t uniqueId() const { return mUniqueId; } + void setIsTextureView() { mIsTextureView = true; } + void markDrawStart(SkCanvas& canvas); void markDrawEnd(SkCanvas& canvas); @@ -290,6 +292,8 @@ private: bool mHasHolePunches; StretchMask mStretchMask; + bool mIsTextureView = false; + // METHODS & FIELDS ONLY USED BY THE SKIA RENDERER public: /** diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h index 6b8f43946a74..2bff9cb74fa7 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -123,6 +123,10 @@ public: // This is used to post a message to redraw when it is time to draw the // next frame of an AnimatedImageDrawable. nsecs_t animatedImageDelay = kNoAnimatedImageDelay; + // This is used to determine if there were only TextureView updates in this frame. + // This info is passed to SurfaceFlinger to determine whether it should use vsyncIds + // for refresh rate selection. + bool solelyTextureViewUpdates = true; } out; // This flag helps to disable projection for receiver nodes that do not have any backward diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index 24a785c18711..8c7b9a4b5e94 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -526,6 +526,11 @@ static jlong android_view_RenderNode_getUniqueId(CRITICAL_JNI_PARAMS_COMMA jlong return reinterpret_cast(renderNodePtr)->uniqueId(); } +static void android_view_RenderNode_setIsTextureView( + CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) { + reinterpret_cast(renderNodePtr)->setIsTextureView(); +} + // ---------------------------------------------------------------------------- // RenderProperties - Animations // ---------------------------------------------------------------------------- @@ -844,6 +849,7 @@ static const JNINativeMethod gMethods[] = { {"nSetAllowForceDark", "(JZ)Z", (void*)android_view_RenderNode_setAllowForceDark}, {"nGetAllowForceDark", "(J)Z", (void*)android_view_RenderNode_getAllowForceDark}, {"nGetUniqueId", "(J)J", (void*)android_view_RenderNode_getUniqueId}, + {"nSetIsTextureView", "(J)V", (void*)android_view_RenderNode_setIsTextureView}, }; int register_android_view_RenderNode(JNIEnv* env) { diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index f60c1f3c6ad8..2bd400dba346 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -531,7 +531,7 @@ Frame CanvasContext::getFrame() { } } -void CanvasContext::draw() { +void CanvasContext::draw(bool solelyTextureViewUpdates) { if (auto grContext = getGrContext()) { if (grContext->abandoned()) { LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw"); @@ -604,7 +604,8 @@ void CanvasContext::draw() { static_cast(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId)); native_window_set_frame_timeline_info( mNativeSurface->getNativeWindow(), frameCompleteNr, vsyncId, inputEventId, - mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime)); + mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime), + solelyTextureViewUpdates); } } @@ -885,7 +886,7 @@ void CanvasContext::prepareAndDraw(RenderNode* node) { TreeInfo info(TreeInfo::MODE_RT_ONLY, *this); prepareTree(info, frameInfo, systemTime(SYSTEM_TIME_MONOTONIC), node); if (info.out.canDrawThisFrame) { - draw(); + draw(info.out.solelyTextureViewUpdates); } else { // wait on fences so tasks don't overlap next frame waitOnFences(); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index d7215de92375..08e24245d16c 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -143,7 +143,7 @@ public: bool makeCurrent(); void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued, RenderNode* target); // Returns the DequeueBufferDuration. - void draw(); + void draw(bool solelyTextureViewUpdates); void destroy(); // IFrameCallback, Choreographer-driven frame callback entry point diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index fab2f46e91c3..53b43ba417d0 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -98,12 +98,14 @@ void DrawFrameTask::run() { IRenderPipeline* pipeline = mContext->getRenderPipeline(); bool canUnblockUiThread; bool canDrawThisFrame; + bool solelyTextureViewUpdates; { TreeInfo info(TreeInfo::MODE_FULL, *mContext); info.forceDrawFrame = mForceDrawFrame; mForceDrawFrame = false; canUnblockUiThread = syncFrameState(info); canDrawThisFrame = info.out.canDrawThisFrame; + solelyTextureViewUpdates = info.out.solelyTextureViewUpdates; if (mFrameCommitCallback) { mContext->addFrameCommitListener(std::move(mFrameCommitCallback)); @@ -136,7 +138,7 @@ void DrawFrameTask::run() { } if (CC_LIKELY(canDrawThisFrame)) { - context->draw(); + context->draw(solelyTextureViewUpdates); } else { // Do a flush in case syncFrameState performed any texture uploads. Since we skipped // the draw() call, those uploads (or deletes) will end up sitting in the queue. @@ -179,6 +181,7 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { mLayers[i]->apply(); } } + mLayers.clear(); mContext->setContentDrawBounds(mContentDrawBounds); mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode); -- cgit v1.2.3-59-g8ed1b From 44db040f6c07b63642bc7ae72040d09851d70df7 Mon Sep 17 00:00:00 2001 From: Jernej Virag Date: Tue, 9 May 2023 20:24:48 +0200 Subject: Allow triming of font caches through WindowManager This allows triming of Skia font caches when an app created a lot of transient font allocations and it knows that it won't need it anymore. This is primarily meant for persistent processes like SystemUI to avoid font caches taking up memory after they're not needed anymore. Bug: 275486055 Test: Tested as part of a follow-up commit, ran LockscreenWithSwipeMicrobenchmark which showed a noticable reduction of RSS+anon memory use after unlock. Change-Id: I6d80003d8baab35cb2ca858d4e4d4696b32f3adf --- core/java/android/view/WindowManagerGlobal.java | 6 ++++ .../java/android/graphics/HardwareRenderer.java | 42 ++++++++++++++++++++++ libs/hwui/MemoryPolicy.h | 6 ++++ .../hwui/jni/android_graphics_HardwareRenderer.cpp | 5 +++ libs/hwui/renderthread/CacheManager.cpp | 19 ++++++++++ libs/hwui/renderthread/CacheManager.h | 1 + libs/hwui/renderthread/RenderProxy.cpp | 9 +++++ libs/hwui/renderthread/RenderProxy.h | 1 + libs/hwui/renderthread/RenderThread.cpp | 5 +++ libs/hwui/renderthread/RenderThread.h | 1 + 10 files changed, 95 insertions(+) (limited to 'graphics/java/android') diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 02cd0372ea88..99a4f6b41ef3 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -23,6 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; +import android.graphics.HardwareRenderer; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; @@ -550,6 +551,11 @@ public final class WindowManagerGlobal { ThreadedRenderer.trimMemory(level); } + /** @hide */ + public void trimCaches(@HardwareRenderer.CacheTrimLevel int level) { + ThreadedRenderer.trimCaches(level); + } + public void dumpGfxInfo(FileDescriptor fd, String[] args) { FileOutputStream fout = new FileOutputStream(fd); PrintWriter pw = new FastPrintWriter(fout); diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 9ed3d9c3c94b..9cde1878d9d8 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -144,6 +144,32 @@ public class HardwareRenderer { public @interface DumpFlags { } + + /** + * Trims all Skia caches. + * @hide + */ + public static final int CACHE_TRIM_ALL = 0; + /** + * Trims Skia font caches. + * @hide + */ + public static final int CACHE_TRIM_FONT = 1; + /** + * Trims Skia resource caches. + * @hide + */ + public static final int CACHE_TRIM_RESOURCES = 2; + + /** @hide */ + @IntDef(prefix = {"CACHE_TRIM_"}, value = { + CACHE_TRIM_ALL, + CACHE_TRIM_FONT, + CACHE_TRIM_RESOURCES + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CacheTrimLevel {} + /** * Name of the file that holds the shaders cache. */ @@ -1131,6 +1157,20 @@ public class HardwareRenderer { nTrimMemory(level); } + /** + * Invoke this when all font caches should be flushed. This can cause jank on next render + * commands so use it only after expensive font allocation operations which would + * allocate large amount of temporary memory. + * + * @param level Hint about which caches to trim. See {@link #CACHE_TRIM_ALL}, + * {@link #CACHE_TRIM_FONT}, {@link #CACHE_TRIM_RESOURCES} + * + * @hide + */ + public static void trimCaches(@CacheTrimLevel int level) { + nTrimCaches(level); + } + /** @hide */ public static void overrideProperty(@NonNull String name, @NonNull String value) { if (name == null || value == null) { @@ -1497,6 +1537,8 @@ public class HardwareRenderer { private static native void nTrimMemory(int level); + private static native void nTrimCaches(int level); + private static native void nOverrideProperty(String name, String value); private static native void nFence(long nativeProxy); diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h index 139cdde5c0d2..347daf34f52a 100644 --- a/libs/hwui/MemoryPolicy.h +++ b/libs/hwui/MemoryPolicy.h @@ -31,6 +31,12 @@ enum class TrimLevel { RUNNING_MODERATE = 5, }; +enum class CacheTrimLevel { + ALL_CACHES = 0, + FONT_CACHE = 1, + RESOURCE_CACHE = 2, +}; + struct MemoryPolicy { // The initial scale factor applied to the display resolution. The default is 1, but // lower values may be used to start with a smaller initial cache size. The cache will diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index 6a7411f5d859..d04de37f6961 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -362,6 +362,10 @@ static void android_view_ThreadedRenderer_trimMemory(JNIEnv* env, jobject clazz, RenderProxy::trimMemory(level); } +static void android_view_ThreadedRenderer_trimCaches(JNIEnv* env, jobject clazz, jint level) { + RenderProxy::trimCaches(level); +} + static void android_view_ThreadedRenderer_overrideProperty(JNIEnv* env, jobject clazz, jstring name, jstring value) { const char* nameCharArray = env->GetStringUTFChars(name, NULL); @@ -1018,6 +1022,7 @@ static const JNINativeMethod gMethods[] = { (void*)android_view_ThreadedRenderer_notifyCallbackPending}, {"nNotifyExpensiveFrame", "(J)V", (void*)android_view_ThreadedRenderer_notifyExpensiveFrame}, + {"nTrimCaches", "(I)V", (void*)android_view_ThreadedRenderer_trimCaches}, }; static JavaVM* mJvm = nullptr; diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index c00a2707e0a2..babce88b8e1e 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -139,6 +139,25 @@ void CacheManager::trimMemory(TrimLevel mode) { } } +void CacheManager::trimCaches(CacheTrimLevel mode) { + switch (mode) { + case CacheTrimLevel::FONT_CACHE: + SkGraphics::PurgeFontCache(); + break; + case CacheTrimLevel::RESOURCE_CACHE: + SkGraphics::PurgeResourceCache(); + break; + case CacheTrimLevel::ALL_CACHES: + SkGraphics::PurgeAllCaches(); + if (mGrContext) { + mGrContext->purgeUnlockedResources(false); + } + break; + default: + break; + } +} + void CacheManager::trimStaleResources() { if (!mGrContext) { return; diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index d21ac9badc43..5e43ac209696 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -48,6 +48,7 @@ public: void configureContext(GrContextOptions* context, const void* identity, ssize_t size); #endif void trimMemory(TrimLevel mode); + void trimCaches(CacheTrimLevel mode); void trimStaleResources(); void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr); void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage); diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 31b4b203c670..224c878bf43d 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -231,6 +231,15 @@ void RenderProxy::trimMemory(int level) { } } +void RenderProxy::trimCaches(int level) { + // Avoid creating a RenderThread to do a trimMemory. + if (RenderThread::hasInstance()) { + RenderThread& thread = RenderThread::getInstance(); + const auto trimLevel = static_cast(level); + thread.queue().post([&thread, trimLevel]() { thread.trimCaches(trimLevel); }); + } +} + void RenderProxy::purgeCaches() { if (RenderThread::hasInstance()) { RenderThread& thread = RenderThread::getInstance(); diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 82072a6e2499..47c1b0cd28e5 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -105,6 +105,7 @@ public: void destroyHardwareResources(); static void trimMemory(int level); + static void trimCaches(int level); static void purgeCaches(); static void overrideProperty(const char* name, const char* value); diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 9ba67a2110c1..eb28c080c056 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -521,6 +521,11 @@ void RenderThread::trimMemory(TrimLevel level) { cacheManager().trimMemory(level); } +void RenderThread::trimCaches(CacheTrimLevel level) { + ATRACE_CALL(); + cacheManager().trimCaches(level); +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index c77cd4134d1e..79e57de9d66f 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -174,6 +174,7 @@ public: } void trimMemory(TrimLevel level); + void trimCaches(CacheTrimLevel level); /** * isCurrent provides a way to query, if the caller is running on -- cgit v1.2.3-59-g8ed1b From f0c528ab6ed64b3a551d555bf7cb77f39c571fd7 Mon Sep 17 00:00:00 2001 From: John Reck Date: Thu, 18 May 2023 18:10:26 -0400 Subject: Fix RippleDrawable alpha Alpha was being double-applied by being both handled in the shader and applied by the Paint. So change the Paint to not apply the alpha since the shader does it. Fixes: 272375156 Test: looked at ripples in dark & light theme, verified no visual change Change-Id: I6ca4d32a7a5735ce8ec418014d0318c29dd3c8bf --- core/res/res/values/colors_material.xml | 6 +++--- graphics/java/android/graphics/drawable/RippleDrawable.java | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'graphics/java/android') diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml index a99ba152510e..5b0dd308d05f 100644 --- a/core/res/res/values/colors_material.xml +++ b/core/res/res/values/colors_material.xml @@ -72,9 +72,9 @@ .7 0.60 - 0.5 - 0.5 - 0.10 + 0.20 + 0.20 + 0.20 diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 4d0a05811dbf..641a2ae7b2c3 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -1013,7 +1013,8 @@ public class RippleDrawable extends LayerDrawable { } p.setShader(shader); p.setColorFilter(null); - p.setColor(color); + // Alpha is handled by the shader (and color is a no-op because there's a shader) + p.setColor(0xFF000000); return properties; } -- cgit v1.2.3-59-g8ed1b From 06aaf05f00c32f5421ee3c97587055ab45f36d9b Mon Sep 17 00:00:00 2001 From: Kwangkyu Park Date: Wed, 17 May 2023 14:54:47 +0900 Subject: Camera: Address an issue that the invalid memory is accessed If the ImagePlanes is initiailized the HardwareBuffer and is close()'ed by finalizer then the invalid memory access to the GraphicBufferWrapper and GraphicBuffer could be happen. This patch addressed the issue by properly clearing fields after being destoyed. Bug: 283038375 Test: Test extensions proxy service with advanced extender implementation while maintaining a reference counter so that the ExtensionImage is finalized without invoking close. Change-Id: Iab49da708daf0099d029cda6873cb2e811377fbc --- graphics/java/android/graphics/GraphicBuffer.java | 5 +++-- media/jni/android_media_ImageReader.cpp | 1 + .../com/android/cameraextensions/CameraExtensionsProxyService.java | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java index f9113a21405c..6705b25ab0ec 100644 --- a/graphics/java/android/graphics/GraphicBuffer.java +++ b/graphics/java/android/graphics/GraphicBuffer.java @@ -57,7 +57,7 @@ public class GraphicBuffer implements Parcelable { private final int mUsage; // Note: do not rename, this field is used by native code @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private final long mNativeObject; + private long mNativeObject; // These two fields are only used by lock/unlockCanvas() private Canvas mCanvas; @@ -219,6 +219,7 @@ public class GraphicBuffer implements Parcelable { if (!mDestroyed) { mDestroyed = true; nDestroyGraphicBuffer(mNativeObject); + mNativeObject = 0; } } @@ -239,7 +240,7 @@ public class GraphicBuffer implements Parcelable { @Override protected void finalize() throws Throwable { try { - if (!mDestroyed) nDestroyGraphicBuffer(mNativeObject); + destroy(); } finally { super.finalize(); } diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp index ca1bb3e97815..da2e56f5b6fa 100644 --- a/media/jni/android_media_ImageReader.cpp +++ b/media/jni/android_media_ImageReader.cpp @@ -768,6 +768,7 @@ static void ImageReader_unlockGraphicBuffer(JNIEnv* env, jobject /*thiz*/, android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, buffer); if (graphicBuffer.get() == NULL) { jniThrowRuntimeException(env, "Invalid graphic buffer!"); + return; } status_t res = graphicBuffer->unlock(); diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index f31ca8178eb6..c2ebddf00fb4 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -2057,7 +2057,11 @@ public class CameraExtensionsProxyService extends Service { mIsImageValid = false; if (mGraphicBuffer != null) { - ImageReader.unlockGraphicBuffer(mGraphicBuffer); + try { + ImageReader.unlockGraphicBuffer(mGraphicBuffer); + } catch (RuntimeException e) { + e.printStackTrace(); + } mGraphicBuffer.destroy(); mGraphicBuffer = null; } -- cgit v1.2.3-59-g8ed1b From b1c20eea9e53c2ae1147ce44ed25e524a4e51cc1 Mon Sep 17 00:00:00 2001 From: John Reck Date: Wed, 7 Jun 2023 17:58:16 -0400 Subject: Transform & preserve gainmaps Have Bitmap.createBitmap(sourceBitmap, ...) preserve any gainmaps if present, transforming the gainmaps in the same way. This addresses 2 common usages: 1) Rotating bitmaps to handle EXIF orientations 2) Bitmap.createScaledBitmap() to do "static" scaling Bug: 286131154 Test: SilkFX GainmapTransformsTest Change-Id: I5a62dccbb2c70bc38cca581b161eef792c8b2a78 --- graphics/java/android/graphics/Bitmap.java | 51 +++++++++ graphics/java/android/graphics/Gainmap.java | 11 ++ libs/hwui/jni/Gainmap.cpp | 11 ++ tests/SilkFX/res/layout/gainmap_transform_test.xml | 122 +++++++++++++++++++++ tests/SilkFX/src/com/android/test/silkfx/Main.kt | 4 +- .../android/test/silkfx/hdr/GainmapDecodeTest.kt | 28 ++++- .../test/silkfx/hdr/GainmapTransformsTest.kt | 116 ++++++++++++++++++++ 7 files changed, 340 insertions(+), 3 deletions(-) create mode 100644 tests/SilkFX/res/layout/gainmap_transform_test.xml create mode 100644 tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt (limited to 'graphics/java/android') diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 2307d6080f9f..b9d3756ac6d2 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -997,12 +997,63 @@ public final class Bitmap implements Parcelable { canvas.concat(m); canvas.drawBitmap(source, srcR, dstR, paint); canvas.setBitmap(null); + + // If the source has a gainmap, apply the same set of transformations to the gainmap + // and set it on the output + if (source.hasGainmap()) { + Bitmap newMapContents = transformGainmap(source, m, neww, newh, paint, srcR, dstR, + deviceR); + if (newMapContents != null) { + bitmap.setGainmap(new Gainmap(source.getGainmap(), newMapContents)); + } + } + if (isHardware) { return bitmap.copy(Config.HARDWARE, false); } return bitmap; } + private static Bitmap transformGainmap(Bitmap source, Matrix m, int neww, int newh, Paint paint, + Rect srcR, RectF dstR, RectF deviceR) { + Canvas canvas; + Bitmap sourceGainmap = source.getGainmap().getGainmapContents(); + // Gainmaps can be scaled relative to the base image (eg, 1/4th res) + // Preserve that relative scaling between the base & gainmap in the output + float scaleX = (sourceGainmap.getWidth() / (float) source.getWidth()); + float scaleY = (sourceGainmap.getHeight() / (float) source.getHeight()); + int mapw = Math.round(neww * scaleX); + int maph = Math.round(newh * scaleY); + + if (mapw == 0 || maph == 0) { + // The gainmap has been scaled away entirely, drop it + return null; + } + + // Scale the computed `srcR` used for rendering the source bitmap to the destination + // to be in gainmap dimensions + Rect gSrcR = new Rect((int) (srcR.left * scaleX), + (int) (srcR.top * scaleY), (int) (srcR.right * scaleX), + (int) (srcR.bottom * scaleY)); + + // Note: createBitmap isn't used as that requires a non-null colorspace, however + // gainmaps don't have a colorspace. So use `nativeCreate` directly to bypass + // that colorspace enforcement requirement (#getColorSpace() allows a null return) + Bitmap newMapContents = nativeCreate(null, 0, mapw, mapw, maph, + sourceGainmap.getConfig().nativeInt, true, 0); + newMapContents.eraseColor(0); + canvas = new Canvas(newMapContents); + // Scale the translate & matrix to be in gainmap-relative dimensions + canvas.scale(scaleX, scaleY); + canvas.translate(-deviceR.left, -deviceR.top); + canvas.concat(m); + canvas.drawBitmap(sourceGainmap, gSrcR, dstR, paint); + canvas.setBitmap(null); + // Create a new gainmap using a copy of the metadata information from the source but + // with the transformed bitmap created above + return newMapContents; + } + /** * Returns a mutable bitmap with the specified width and height. Its * initial density is as per {@link #getDensity}. The newly created diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java index 9ac84a6159da..f639521ff250 100644 --- a/graphics/java/android/graphics/Gainmap.java +++ b/graphics/java/android/graphics/Gainmap.java @@ -121,6 +121,16 @@ public final class Gainmap implements Parcelable { this(gainmapContents, nCreateEmpty()); } + /** + * Creates a new gainmap using the provided gainmap as the metadata source and the provided + * bitmap as the replacement for the gainmapContents + * TODO: Make public, it's useful + * @hide + */ + public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) { + this(gainmapContents, nCreateCopy(gainmap.mNativePtr)); + } + /** * @return Returns the image data of the gainmap represented as a Bitmap. This is represented * as a Bitmap for broad API compatibility, however certain aspects of the Bitmap are ignored @@ -325,6 +335,7 @@ public final class Gainmap implements Parcelable { private static native long nGetFinalizer(); private static native long nCreateEmpty(); + private static native long nCreateCopy(long source); private static native void nSetBitmap(long ptr, Bitmap bitmap); diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp index 0f8a85dd9e62..cec0ee7ee247 100644 --- a/libs/hwui/jni/Gainmap.cpp +++ b/libs/hwui/jni/Gainmap.cpp @@ -86,6 +86,16 @@ jlong Gainmap_createEmpty(JNIEnv*, jobject) { return static_cast(reinterpret_cast(gainmap)); } +jlong Gainmap_createCopy(JNIEnv*, jobject, jlong sourcePtr) { + Gainmap* gainmap = new Gainmap(); + gainmap->incStrong(0); + if (sourcePtr) { + Gainmap* src = fromJava(sourcePtr); + gainmap->info = src->info; + } + return static_cast(reinterpret_cast(gainmap)); +} + static void Gainmap_setBitmap(JNIEnv* env, jobject, jlong gainmapPtr, jobject jBitmap) { android::Bitmap* bitmap = GraphicsJNI::getNativeBitmap(env, jBitmap); fromJava(gainmapPtr)->bitmap = sk_ref_sp(bitmap); @@ -237,6 +247,7 @@ static void Gainmap_readFromParcel(JNIEnv* env, jobject, jlong nativeObject, job static const JNINativeMethod gGainmapMethods[] = { {"nGetFinalizer", "()J", (void*)Gainmap_getNativeFinalizer}, {"nCreateEmpty", "()J", (void*)Gainmap_createEmpty}, + {"nCreateCopy", "(J)J", (void*)Gainmap_createCopy}, {"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*)Gainmap_setBitmap}, {"nSetRatioMin", "(JFFF)V", (void*)Gainmap_setRatioMin}, {"nGetRatioMin", "(J[F)V", (void*)Gainmap_getRatioMin}, diff --git a/tests/SilkFX/res/layout/gainmap_transform_test.xml b/tests/SilkFX/res/layout/gainmap_transform_test.xml new file mode 100644 index 000000000000..5aeb53661cbc --- /dev/null +++ b/tests/SilkFX/res/layout/gainmap_transform_test.xml @@ -0,0 +1,122 @@ + + + + + + + +