Skip to content

Commit 039d0b0

Browse files
Read only the drawn child's mix-blend-mode in Android drawChild
ReactViewGroup.drawChild and ReactRootView.drawChild asked BlendModeHelper.needsIsolatedLayer(this) before reading the drawn child's mix_blend_mode tag. needsIsolatedLayer reads that tag on every child of the group, and dispatchDraw calls drawChild once per child, so recording a group of N children read N^2 tags. With no blend mode set anywhere the scan never short-circuits, so the whole cost lands on views that never use the feature. A child carrying the tag makes needsIsolatedLayer true by definition, and a child without it draws with no layer either way, so the group scan adds nothing to the decision drawChild makes. drawChild now reads the child's own tag; draw() keeps its group-wide check, which runs once per recording.
1 parent 9f5a12c commit 039d0b0

3 files changed

Lines changed: 52 additions & 3 deletions

File tree

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
package com.facebook.react;
99

1010
import static com.facebook.infer.annotation.ThreadConfined.UI;
11-
import static com.facebook.react.uimanager.BlendModeHelper.needsIsolatedLayer;
1211
import static com.facebook.react.uimanager.common.UIManagerType.FABRIC;
1312
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT;
1413

@@ -308,7 +307,7 @@ protected void dispatchDraw(Canvas canvas) {
308307
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
309308

310309
BlendMode mixBlendMode = null;
311-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && needsIsolatedLayer(this)) {
310+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
312311
mixBlendMode = (BlendMode) child.getTag(R.id.mix_blend_mode);
313312
if (mixBlendMode != null) {
314313
Paint p = new Paint();

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,7 @@ public open class ReactViewGroup public constructor(context: Context?) :
10031003
}
10041004

10051005
var mixBlendMode: BlendMode? = null
1006-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && needsIsolatedLayer(this)) {
1006+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
10071007
mixBlendMode = child.getTag(R.id.mix_blend_mode) as? BlendMode
10081008
if (mixBlendMode != null) {
10091009
val p = Paint()

‎packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/view/ReactViewGroupTest.kt‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ package com.facebook.react.views.view
99

1010
import android.app.Activity
1111
import android.content.Context
12+
import android.graphics.BlendMode
13+
import android.graphics.Canvas
14+
import android.graphics.Paint
1215
import android.view.View
1316
import android.view.ViewGroup
1417
import android.widget.FrameLayout
18+
import com.facebook.react.R
1519
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
1620
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults
1721
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
@@ -67,6 +71,32 @@ class ReactViewGroupTest {
6771
assertThat(rvg.childCount).isEqualTo(20)
6872
}
6973

74+
@Test
75+
fun `mix blend mode - drawing a child reads no sibling's blend mode`() {
76+
fun blendModeReadsPerChild(childCount: Int): Int {
77+
val rvg = ReactViewGroup(context)
78+
val children = List(childCount) { BlendModeReadCountingView(context) }
79+
children.forEach { rvg.addView(it) }
80+
rvg.layout(0, 0, 100, 100)
81+
rvg.draw(Canvas())
82+
return children.maxOf { it.blendModeReads }
83+
}
84+
85+
assertThat(blendModeReadsPerChild(20)).isEqualTo(blendModeReadsPerChild(2))
86+
}
87+
88+
@Test
89+
fun `mix blend mode - a child with a blend mode draws into a layer with that blend mode`() {
90+
val rvg = ReactViewGroup(context)
91+
rvg.addView(View(context))
92+
rvg.addView(View(context).apply { setTag(R.id.mix_blend_mode, BlendMode.MULTIPLY) })
93+
rvg.addView(View(context))
94+
rvg.layout(0, 0, 100, 100)
95+
val canvas = LayerRecordingCanvas()
96+
rvg.draw(canvas)
97+
assertThat(canvas.layerBlendModes).containsExactly(null, BlendMode.MULTIPLY)
98+
}
99+
70100
@Test
71101
fun `auto offscreen compositing - true when flag on, alpha lt 1, and elevated descendant`() {
72102
overrideAutoOffscreenFlag(true)
@@ -294,3 +324,23 @@ class TestView(context: Context, yPos: Int) : View(context) {
294324
class TestParent(context: Context) : ViewGroup(context) {
295325
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) = Unit
296326
}
327+
328+
private class BlendModeReadCountingView(context: Context) : View(context) {
329+
var blendModeReads = 0
330+
331+
override fun getTag(key: Int): Any? {
332+
if (key == R.id.mix_blend_mode) {
333+
blendModeReads++
334+
}
335+
return super.getTag(key)
336+
}
337+
}
338+
339+
private class LayerRecordingCanvas : Canvas() {
340+
val layerBlendModes = mutableListOf<BlendMode?>()
341+
342+
override fun saveLayer(left: Float, top: Float, right: Float, bottom: Float, paint: Paint?): Int {
343+
layerBlendModes.add(paint?.blendMode)
344+
return super.saveLayer(left, top, right, bottom, paint)
345+
}
346+
}

0 commit comments

Comments
 (0)