Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b90e08f9 authored by Seigo Nonaka's avatar Seigo Nonaka
Browse files

Reorganize JNI in StaticLayout

This CL reorganize the JNI strategy as follows:
- Remove nNewBuilder/nFreeBuilder.
- Make addStyleRun/addReplacementRun CriticalNative.
- Remove nSetupParagraph and pass necessary arguments to
  nComputeLineBreaks instead.

Here is a performance scores: (w/o patch -> w/ patch)

StaticLayoutPerfTest (median):
createRandom:           3,755,090 -> 3,781,394 (+0.70%)
createRandom Balanced:  3,702,837 -> 3,730,435 (+0.74%)

TextViewOnMeasurePerfTest (median):
measure_AtMost:        39,172,360 -> 35,883,014 (-8.4%)
measure_Exactly:       38,005,066 -> 34,585,052 (-9.0%)
measure_Unspecified:   67,191,780 -> 63,100,545 (-6.1%)

Bug: 65024629
Test: bit CtsTextTestCases:*
Test: bit CtsWidgetTestCases:*
Change-Id: If9db1fdd2b03a1cf240f87322c1e852cf8085fff
parent a5da2cf5
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1910,7 +1910,7 @@ public abstract class Layout {
        MeasuredText mt = MeasuredText.obtain();
        TextLine tl = TextLine.obtain();
        try {
            mt.setPara(text, start, end, textDir, null);
            mt.setPara(text, start, end, textDir);
            Directions directions;
            int dir;
            if (mt.mEasy) {
+24 −19
Original line number Diff line number Diff line
@@ -39,7 +39,6 @@ class MeasuredText {

    private int mPos;
    private TextPaint mWorkPaint;
    private StaticLayout.Builder mBuilder;

    private MeasuredText() {
        mWorkPaint = new TextPaint();
@@ -82,7 +81,6 @@ class MeasuredText {

    void finish() {
        mText = null;
        mBuilder = null;
        if (mLen > 1000) {
            mWidths = null;
            mChars = null;
@@ -93,9 +91,7 @@ class MeasuredText {
    /**
     * Analyzes text for bidirectional runs.  Allocates working buffers.
     */
    void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir,
            StaticLayout.Builder builder) {
        mBuilder = builder;
    void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
        mText = text;
        mTextStart = start;

@@ -159,12 +155,12 @@ class MeasuredText {
    /**
     * Apply the style.
     *
     * If StaticLyaout.Builder is not provided in setPara() method, this method measures the styled
     * text width.
     * If StaticLayout.Builder is provided in setPara() method, this method just passes the style
     * information to native code by calling StaticLayout.Builder.addstyleRun() and returns 0.
     * If nativeStaticLayoutPtr is 0, this method measures the styled text width.
     * If nativeStaticLayoutPtr is not 0, this method just passes the style information to native
     * code by calling StaticLayout.addstyleRun() and returns 0.
     */
    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm,
            long nativeStaticLayoutPtr) {
        if (fm != null) {
            paint.getFontMetricsInt(fm);
        }
@@ -174,10 +170,10 @@ class MeasuredText {

        if (mEasy) {
            final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
            if (mBuilder == null) {
            if (nativeStaticLayoutPtr == 0) {
                return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
            } else {
                mBuilder.addStyleRun(paint, p, p + len, isRtl);
                StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, p, p + len, isRtl);
                return 0.0f;  // Builder.addStyleRun doesn't return the width.
            }
        }
@@ -187,12 +183,12 @@ class MeasuredText {
        for (int q = p, i = p + 1, e = p + len;; ++i) {
            if (i == e || mLevels[i] != level) {
                final boolean isRtl = (level & 0x1) != 0;
                if (mBuilder == null) {
                if (nativeStaticLayoutPtr == 0) {
                    totalAdvance +=
                            paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
                } else {
                    // Builder.addStyleRun doesn't return the width.
                    mBuilder.addStyleRun(paint, q, i, isRtl);
                    StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, q, i, isRtl);
                }
                if (i == e) {
                    break;
@@ -201,11 +197,15 @@ class MeasuredText {
                level = mLevels[i];
            }
        }
        return totalAdvance;  // If mBuilder is null, the result is zero.
        return totalAdvance;  // If nativeStaticLayoutPtr is 0, the result is zero.
    }

    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
        return addStyleRun(paint, len, fm, 0 /* native ptr */);
    }

    float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
            Paint.FontMetricsInt fm) {
            Paint.FontMetricsInt fm, long nativeStaticLayoutPtr) {

        TextPaint workPaint = mWorkPaint;
        workPaint.set(paint);
@@ -224,18 +224,18 @@ class MeasuredText {

        float wid;
        if (replacement == null) {
            wid = addStyleRun(workPaint, len, fm);
            wid = addStyleRun(workPaint, len, fm, nativeStaticLayoutPtr);
        } else {
            // Use original text.  Shouldn't matter.
            wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
                    mTextStart + mPos + len, fm);
            if (mBuilder == null) {
            if (nativeStaticLayoutPtr == 0) {
                float[] w = mWidths;
                w[mPos] = wid;
                for (int i = mPos + 1, e = mPos + len; i < e; i++)
                    w[i] = 0;
            } else {
                mBuilder.addReplacementRun(paint, mPos, mPos + len, wid);
                StaticLayout.addReplacementRun(nativeStaticLayoutPtr, paint, mPos, mPos + len, wid);
            }
            mPos += len;
        }
@@ -253,6 +253,11 @@ class MeasuredText {
        return wid;
    }

    float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
            Paint.FontMetricsInt fm) {
        return addStyleRun(paint, spans, len, fm, 0 /* native ptr */);
    }

    int breakText(int limit, boolean forwards, float width) {
        float[] w = mWidths;
        if (forwards) {
+321 −292
Original line number Diff line number Diff line
@@ -32,6 +32,9 @@ import android.util.Pools.SynchronizedPool;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;

import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;

import java.util.Arrays;

/**
@@ -57,9 +60,7 @@ public class StaticLayout extends Layout {
     * default values.
     */
    public final static class Builder {
        private Builder() {
            mNativePtr = nNewBuilder();
        }
        private Builder() {}

        /**
         * Obtain a builder for constructing StaticLayout objects.
@@ -116,13 +117,11 @@ public class StaticLayout extends Layout {
            b.mRightIndents = null;
            b.mLeftPaddings = null;
            b.mRightPaddings = null;
            nFinishBuilder(b.mNativePtr);
            sPool.release(b);
        }

        // release any expensive state
        /* package */ void finish() {
            nFinishBuilder(mNativePtr);
            mText = null;
            mPaint = null;
            mLeftIndents = null;
@@ -404,32 +403,6 @@ public class StaticLayout extends Layout {
            return this;
        }

        /**
         * Measurement and break iteration is done in native code. The protocol for using
         * the native code is as follows.
         *
         * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
         * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
         * future).
         *
         * Then, for each run within the paragraph:
         *  - one of the following, depending on the type of run:
         *    + addStyleRun (a text run, to be measured in native code)
         *    + addReplacementRun (a replacement run, width is given)
         *
         * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
         *
         * After all paragraphs, call finish() to release expensive buffers.
         */

        /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
            nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
        }

        /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
            nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
        }

        /**
         * Build the {@link StaticLayout} after options have been set.
         *
@@ -446,17 +419,6 @@ public class StaticLayout extends Layout {
            return result;
        }

        @Override
        protected void finalize() throws Throwable {
            try {
                nFreeBuilder(mNativePtr);
            } finally {
                super.finalize();
            }
        }

        /* package */ long mNativePtr;

        private CharSequence mText;
        private int mStart;
        private int mEnd;
@@ -694,13 +656,21 @@ public class StaticLayout extends Layout {
            indents = null;
        }

        final long nativePtr = nInit(
                b.mBreakStrategy, b.mHyphenationFrequency,
                // TODO: Support more justification mode, e.g. letter spacing, stretching.
                b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
                indents, mLeftPaddings, mRightPaddings);

        try {
            int paraEnd;
            for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
                paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
            if (paraEnd < 0)
                if (paraEnd < 0) {
                    paraEnd = bufEnd;
            else
                } else {
                    paraEnd++;
                }

                int firstWidthLineCount = 1;
                int firstWidth = outerWidth;
@@ -730,8 +700,7 @@ public class StaticLayout extends Layout {
                    if (chooseHt.length == 0) {
                        chooseHt = null; // So that out() would not assume it has any contents
                    } else {
                    if (chooseHtv == null ||
                        chooseHtv.length < chooseHt.length) {
                        if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
                            chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
                        }

@@ -752,7 +721,7 @@ public class StaticLayout extends Layout {
                    }
                }

            measured.setPara(source, paraStart, paraEnd, textDir, b);
                measured.setPara(source, paraStart, paraEnd, textDir);
                char[] chs = measured.mChars;
                float[] widths = measured.mWidths;
                byte[] chdirs = measured.mLevels;
@@ -774,15 +743,6 @@ public class StaticLayout extends Layout {
                    }
                }

            nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
                    firstWidth, firstWidthLineCount, restWidth,
                    variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
                    // TODO: Support more justification mode, e.g. letter spacing, stretching.
                    b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
                    // TODO: indents and paddings don't need to get passed to native code for every
                    // paragraph. Pass them to native code just once.
                    indents, mLeftPaddings, mRightPaddings, mLineCount);

                // measurement has to be done before performing line breaking
                // but we don't want to recompute fontmetrics or span ranges the
                // second time, so we cache those and then use those stored values
@@ -804,19 +764,20 @@ public class StaticLayout extends Layout {
                    if (spanned == null) {
                        spanEnd = paraEnd;
                        int spanLen = spanEnd - spanStart;
                    measured.addStyleRun(paint, spanLen, fm);
                        measured.addStyleRun(paint, spanLen, fm, nativePtr);
                    } else {
                        spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
                                MetricAffectingSpan.class);
                        int spanLen = spanEnd - spanStart;
                        MetricAffectingSpan[] spans =
                                spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
                    spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
                    measured.addStyleRun(paint, spans, spanLen, fm);
                        spans = TextUtils.removeEmptySpans(spans, spanned,
                                MetricAffectingSpan.class);
                        measured.addStyleRun(paint, spans, spanLen, fm, nativePtr);
                    }

                // the order of storage here (top, bottom, ascent, descent) has to match the code below
                // where these values are retrieved
                    // the order of storage here (top, bottom, ascent, descent) has to match the
                    // code below where these values are retrieved
                    fmCache[fmCacheCount * 4 + 0] = fm.top;
                    fmCache[fmCacheCount * 4 + 1] = fm.bottom;
                    fmCache[fmCacheCount * 4 + 2] = fm.ascent;
@@ -827,9 +788,28 @@ public class StaticLayout extends Layout {
                    spanEndCacheCount++;
                }

            int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
                    lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
                    lineBreaks.breaks.length, widths);
                int breakCount = nComputeLineBreaks(
                        nativePtr,

                        // Inputs
                        chs,
                        paraEnd - paraStart,
                        firstWidth,
                        firstWidthLineCount,
                        restWidth,
                        variableTabStops,
                        TAB_INCREMENT,
                        mLineCount,

                        // Outputs
                        lineBreaks,
                        lineBreaks.breaks.length,
                        lineBreaks.breaks,
                        lineBreaks.widths,
                        lineBreaks.ascents,
                        lineBreaks.descents,
                        lineBreaks.flags,
                        widths);

                final int[] breaks = lineBreaks.breaks;
                final float[] lineWidths = lineBreaks.widths;
@@ -865,7 +845,8 @@ public class StaticLayout extends Layout {
                    breakCount = remainingLineCount;
                }

            // here is the offset of the starting character of the line we are currently measuring
                // here is the offset of the starting character of the line we are currently
                // measuring
                int here = paraStart;

                int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
@@ -914,10 +895,11 @@ public class StaticLayout extends Layout {
                                : fmDescent;
                        v = out(source, here, endPos,
                                ascent, descent, fmTop, fmBottom,
                            v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
                            needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
                            addLastLineSpacing, chs, widths, paraStart, ellipsize,
                            ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
                                v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
                                flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd,
                                includepad, trackpad, addLastLineSpacing, chs, widths, paraStart,
                                ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint,
                                moreChars);

                        if (endPos < spanEnd) {
                            // preserve metrics for current span
@@ -938,13 +920,14 @@ public class StaticLayout extends Layout {
                    }
                }

            if (paraEnd == bufEnd)
                if (paraEnd == bufEnd) {
                    break;
                }
            }

        if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
                mLineCount < mMaximumVisibleLineCount) {
            measured.setPara(source, bufEnd, bufEnd, textDir, b);
            if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
                    && mLineCount < mMaximumVisibleLineCount) {
                measured.setPara(source, bufEnd, bufEnd, textDir);

                paint.getFontMetricsInt(fm);

@@ -959,6 +942,9 @@ public class StaticLayout extends Layout {
                        null, bufStart, ellipsize,
                        ellipsizedWidth, 0, paint, false);
            }
        } finally {
            nFinish(nativePtr);
        }
    }

    // The parameters that are not changed in the method are marked as final to make the code
@@ -1487,26 +1473,51 @@ public class StaticLayout extends Layout {
                mMaxLineHeight : super.getHeight();
    }

    private static native long nNewBuilder();
    private static native void nFreeBuilder(long nativePtr);
    private static native void nFinishBuilder(long nativePtr);
    /**
     * Measurement and break iteration is done in native code. The protocol for using
     * the native code is as follows.
     *
     * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
     * following:
     *
     *   - Call one of the following methods for each run within the paragraph depending on the type
     *     of run:
     *     + addStyleRun (a text run, to be measured in native code)
     *     + addReplacementRun (a replacement run, width is given)
     *
     *   - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
     *
     * After all paragraphs, call finish() to release expensive buffers.
     */

    /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end,
            boolean isRtl) {
        nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl);
    }

    /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end,
            float width) {
        nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width);
    }

    @FastNative
    private static native long nInit(
            @BreakStrategy int breakStrategy,
            @HyphenationFrequency int hyphenationFrequency,
            boolean isJustified,
            @Nullable int[] indents,
            @Nullable int[] leftPaddings,
            @Nullable int[] rightPaddings);

    // Set up paragraph text and settings; done as one big method to minimize jni crossings
    private static native void nSetupParagraph(
            /* non zero */ long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length,
            @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount,
            @FloatRange(from = 0.0f) float restWidth, @Nullable int[] variableTabStops,
            int defaultTabStop, @BreakStrategy int breakStrategy,
            @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
            @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
            @IntRange(from = 0) int indentsOffset);
    @CriticalNative
    private static native void nFinish(long nativePtr);

    // TODO: Make this method CriticalNative once native code defers doing layouts.
    @CriticalNative
    private static native void nAddStyleRun(
            /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
            @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);

    // TODO: Make this method CriticalNative once native code defers doing layouts.
    @CriticalNative
    private static native void nAddReplacementRun(
            /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
            @IntRange(from = 0) int start, @IntRange(from = 0) int end,
@@ -1519,10 +1530,28 @@ public class StaticLayout extends Layout {
    // arrays do not have to be resized
    // The individual character widths will be returned in charWidths. The length of charWidths must
    // be at least the length of the text.
    private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
            int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
            float[] recycleDescents, int[] recycleFlags, int recycleLength,
            float[] charWidths);
    private static native int nComputeLineBreaks(
            /* non zero */ long nativePtr,

            // Inputs
            @NonNull char[] text,
            @IntRange(from = 0) int length,
            @FloatRange(from = 0.0f) float firstWidth,
            @IntRange(from = 0) int firstWidthLineCount,
            @FloatRange(from = 0.0f) float restWidth,
            @Nullable int[] variableTabStops,
            int defaultTabStop,
            @IntRange(from = 0) int indentsOffset,

            // Outputs
            @NonNull LineBreaks recycle,
            @IntRange(from  = 0) int recycleLength,
            @NonNull int[] recycleBreaks,
            @NonNull float[] recycleWidths,
            @NonNull float[] recycleAscents,
            @NonNull float[] recycleDescents,
            @NonNull int[] recycleFlags,
            @NonNull float[] charWidths);

    private int mLineCount;
    private int mTopPadding, mBottomPadding;
+2 −2
Original line number Diff line number Diff line
@@ -1519,7 +1519,7 @@ public class TextUtils {
                    }

                    // XXX this is probably ok, but need to look at it more
                    tempMt.setPara(format, 0, format.length(), textDir, null);
                    tempMt.setPara(format, 0, format.length(), textDir);
                    float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);

                    if (w + moreWid <= avail) {
@@ -1541,7 +1541,7 @@ public class TextUtils {
    private static float setPara(MeasuredText mt, TextPaint paint,
            CharSequence text, int start, int end, TextDirectionHeuristic textDir) {

        mt.setPara(text, start, end, textDir, null);
        mt.setPara(text, start, end, textDir);

        float width;
        Spanned sp = text instanceof Spanned ? (Spanned) text : null;
+225 −69

File changed.

Preview size limit exceeded, changes collapsed.