summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Seigo Nonaka <nona@google.com> 2024-07-26 16:40:41 +0900
committer Seigo Nonaka <nona@google.com> 2024-08-14 23:25:20 +0900
commiteda7fabeaeae5fa050b3c45391ca40eff4fa31bd (patch)
treeb2c5180f36ede5320303a21affda9f10303c7465
parentfda1f4323268706a0d9e11fc96ca237f0bce84c7 (diff)
[Reland] Cache the variation instance of Typeface
This is a reland of the 4cefdc6df295534f7f94b26371dbacdba75db32f The previous attempt breaks robolectric tests of settings but it needs to be fix-forward. Context: b/358347869 Performance numbers on Pixel 8 Pro. Non-Cached: 366,835 ns Cached: 2,427 ns Bug: 355462362 Test: PaintTest Flag: com.android.text.flags.typeface_cache_for_var_settings Change-Id: I0f1e253eae5e2774ef4f10dcf5d0c8e8fbe6e367
-rw-r--r--apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java28
-rw-r--r--core/java/android/text/flags/flags.aconfig10
-rw-r--r--core/tests/coretests/src/android/graphics/PaintFontVariationTest.java74
-rw-r--r--core/tests/coretests/src/android/graphics/PaintTest.java41
-rw-r--r--graphics/java/android/graphics/Typeface.java88
5 files changed, 236 insertions, 5 deletions
diff --git a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
index fbe67a477f5d..c34936f930f9 100644
--- a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
@@ -19,6 +19,7 @@ package android.text;
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.RenderNode;
+import android.graphics.Typeface;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
@@ -120,13 +121,34 @@ public class VariableFontPerfTest {
public void testSetFontVariationSettings() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final Paint paint = new Paint(PAINT);
- final Random random = new Random(0);
while (state.keepRunning()) {
state.pauseTiming();
- int weight = random.nextInt(1000);
+ paint.setTypeface(null);
+ paint.setFontVariationSettings(null);
+ Typeface.clearTypefaceCachesForTestingPurpose();
state.resumeTiming();
- paint.setFontVariationSettings("'wght' " + weight);
+ paint.setFontVariationSettings("'wght' 450");
+ }
+ Typeface.clearTypefaceCachesForTestingPurpose();
+ }
+
+ @Test
+ public void testSetFontVariationSettings_Cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Paint paint = new Paint(PAINT);
+ Typeface.clearTypefaceCachesForTestingPurpose();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ paint.setTypeface(null);
+ paint.setFontVariationSettings(null);
+ state.resumeTiming();
+
+ paint.setFontVariationSettings("'wght' 450");
}
+
+ Typeface.clearTypefaceCachesForTestingPurpose();
}
+
}
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 3d190fe43626..5c5a2f687d3b 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -267,3 +267,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "typeface_cache_for_var_settings"
+ namespace: "text"
+ description: "Cache Typeface instance for font variation settings."
+ bug: "355462362"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
new file mode 100644
index 000000000000..8a54e5b998e7
--- /dev/null
+++ b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 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.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.test.InstrumentationTestCase;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.text.flags.Flags;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * PaintTest tests {@link Paint}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PaintFontVariationTest extends InstrumentationTestCase {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+ @Test
+ public void testDerivedFromSameTypeface() {
+ final Paint p = new Paint();
+
+ p.setTypeface(Typeface.SANS_SERIF);
+ assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+ Typeface first = p.getTypeface();
+
+ p.setTypeface(Typeface.SANS_SERIF);
+ assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+ Typeface second = p.getTypeface();
+
+ assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+ @Test
+ public void testDerivedFromChained() {
+ final Paint p = new Paint();
+
+ p.setTypeface(Typeface.SANS_SERIF);
+ assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+ Typeface first = p.getTypeface();
+
+ assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+ Typeface second = p.getTypeface();
+
+ assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+ }
+}
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index 0dec756d7611..878ba703c8fe 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -16,13 +16,22 @@
package android.graphics;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertNotEquals;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.test.InstrumentationTestCase;
import android.text.TextUtils;
import androidx.test.filters.SmallTest;
+import com.android.text.flags.Flags;
+
+import org.junit.Rule;
+
import java.util.Arrays;
import java.util.HashSet;
@@ -30,6 +39,9 @@ import java.util.HashSet;
* PaintTest tests {@link Paint}.
*/
public class PaintTest extends InstrumentationTestCase {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String FONT_PATH = "fonts/HintedAdvanceWidthTest-Regular.ttf";
static void assertEquals(String message, float[] expected, float[] actual) {
@@ -403,4 +415,33 @@ public class PaintTest extends InstrumentationTestCase {
assertEquals(6, getClusterCount(p, rtlStr + ltrStr));
assertEquals(9, getClusterCount(p, ltrStr + rtlStr + ltrStr));
}
+
+ @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+ public void testDerivedFromSameTypeface() {
+ final Paint p = new Paint();
+
+ p.setTypeface(Typeface.SANS_SERIF);
+ assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+ Typeface first = p.getTypeface();
+
+ p.setTypeface(Typeface.SANS_SERIF);
+ assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+ Typeface second = p.getTypeface();
+
+ assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+ public void testDerivedFromChained() {
+ final Paint p = new Paint();
+
+ p.setTypeface(Typeface.SANS_SERIF);
+ assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+ Typeface first = p.getTypeface();
+
+ assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+ Typeface second = p.getTypeface();
+
+ assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+ }
}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index fd788167a0d8..889a778556b7 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -56,6 +56,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -74,6 +75,7 @@ import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -143,6 +145,23 @@ public class Typeface {
private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
private static final Object sDynamicCacheLock = new Object();
+ private static final LruCache<Long, LruCache<String, Typeface>> sVariableCache =
+ new LruCache<>(16);
+ private static final Object sVariableCacheLock = new Object();
+
+ /** @hide */
+ @VisibleForTesting
+ public static void clearTypefaceCachesForTestingPurpose() {
+ synchronized (sWeightCacheLock) {
+ sWeightTypefaceCache.clear();
+ }
+ synchronized (sDynamicCacheLock) {
+ sDynamicTypefaceCache.evictAll();
+ }
+ synchronized (sVariableCacheLock) {
+ sVariableCache.evictAll();
+ }
+ }
@GuardedBy("SYSTEM_FONT_MAP_LOCK")
static Typeface sDefaultTypeface;
@@ -195,6 +214,8 @@ public class Typeface {
@UnsupportedAppUsage
public final long native_instance;
+ private final Typeface mDerivedFrom;
+
private final String mSystemFontFamilyName;
private final Runnable mCleaner;
@@ -274,6 +295,18 @@ public class Typeface {
}
/**
+ * Returns the Typeface used for creating this Typeface.
+ *
+ * Maybe null if this is not derived from other Typeface.
+ * TODO(b/357707916): Make this public API.
+ * @hide
+ */
+ @VisibleForTesting
+ public final @Nullable Typeface getDerivedFrom() {
+ return mDerivedFrom;
+ }
+
+ /**
* Returns the system font family name if the typeface was created from a system font family,
* otherwise returns null.
*/
@@ -1021,9 +1054,51 @@ public class Typeface {
return typeface;
}
- /** @hide */
+ private static String axesToVarKey(@NonNull List<FontVariationAxis> axes) {
+ // The given list can be mutated because it is allocated in Paint#setFontVariationSettings.
+ // Currently, Paint#setFontVariationSettings is the only code path reaches this method.
+ axes.sort(Comparator.comparingInt(FontVariationAxis::getOpenTypeTagValue));
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < axes.size(); ++i) {
+ final FontVariationAxis fva = axes.get(i);
+ sb.append(fva.getTag());
+ sb.append(fva.getStyleValue());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * TODO(b/357707916): Make this public API.
+ * @hide
+ */
public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
@NonNull List<FontVariationAxis> axes) {
+ if (Flags.typefaceCacheForVarSettings()) {
+ final Typeface target = (family == null) ? Typeface.DEFAULT : family;
+ final Typeface base = (target.mDerivedFrom == null) ? target : target.mDerivedFrom;
+
+ final String key = axesToVarKey(axes);
+
+ synchronized (sVariableCacheLock) {
+ LruCache<String, Typeface> innerCache = sVariableCache.get(base.native_instance);
+ if (innerCache == null) {
+ // Cache up to 16 var instance per root Typeface
+ innerCache = new LruCache<>(16);
+ sVariableCache.put(base.native_instance, innerCache);
+ } else {
+ Typeface cached = innerCache.get(key);
+ if (cached != null) {
+ return cached;
+ }
+ }
+ Typeface typeface = new Typeface(
+ nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
+ base.getSystemFontFamilyName(), base);
+ innerCache.put(key, typeface);
+ return typeface;
+ }
+ }
+
final Typeface base = family == null ? Typeface.DEFAULT : family;
Typeface typeface = new Typeface(
nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
@@ -1184,11 +1259,19 @@ public class Typeface {
// don't allow clients to call this directly
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Typeface(long ni) {
- this(ni, null);
+ this(ni, null, null);
}
+
// don't allow clients to call this directly
+ // This is kept for robolectric.
private Typeface(long ni, @Nullable String systemFontFamilyName) {
+ this(ni, systemFontFamilyName, null);
+ }
+
+ // don't allow clients to call this directly
+ private Typeface(long ni, @Nullable String systemFontFamilyName,
+ @Nullable Typeface derivedFrom) {
if (ni == 0) {
throw new RuntimeException("native typeface cannot be made");
}
@@ -1198,6 +1281,7 @@ public class Typeface {
mStyle = nativeGetStyle(ni);
mWeight = nativeGetWeight(ni);
mSystemFontFamilyName = systemFontFamilyName;
+ mDerivedFrom = derivedFrom;
}
/**