Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More benchmark tests (and perf improvements) #349

Merged
merged 6 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

309 changes: 145 additions & 164 deletions haze/src/androidMain/generated/baselineProfiles/baseline-prof.txt

Large diffs are not rendered by default.

11 changes: 0 additions & 11 deletions haze/src/commonMain/kotlin/dev/chrisbanes/haze/Canvas.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,8 @@ package dev.chrisbanes.haze

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.isFinite
import androidx.compose.ui.graphics.GraphicsContext
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.layer.GraphicsLayer

internal inline fun GraphicsContext.useGraphicsLayer(block: (GraphicsLayer) -> Unit) {
val layer = createGraphicsLayer()
try {
block(layer)
} finally {
releaseGraphicsLayer(layer)
}
}

internal inline fun DrawScope.translate(
offset: Offset,
Expand Down
97 changes: 51 additions & 46 deletions haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeChildNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@ package dev.chrisbanes.haze

import androidx.compose.animation.core.EaseIn
import androidx.compose.animation.core.Easing
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.isSpecified
import androidx.compose.ui.geometry.takeOrElse
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
Expand All @@ -32,7 +28,6 @@ import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.LayoutAwareModifierNode
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.node.invalidateDraw
Expand All @@ -55,13 +50,10 @@ internal class HazeChildNode(
var block: HazeChildScope.() -> Unit,
) : Modifier.Node(),
CompositionLocalConsumerModifierNode,
LayoutAwareModifierNode,
GlobalPositionAwareModifierNode,
ObserverModifierNode,
DrawModifierNode {

private var positionOnScreen by mutableStateOf(Offset.Unspecified)

private val effect by lazy(::ReusableHazeEffect)

override val shouldAutoInvalidate: Boolean = false
Expand All @@ -80,8 +72,10 @@ internal class HazeChildNode(
}
}

override fun onPlaced(coordinates: LayoutCoordinates) {
effect.positionOnScreen = coordinates.positionInWindow() + calculateWindowOffset()
override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
log(TAG) { "onGloballyPositioned: positionInWindow=${coordinates.positionInWindow()}" }
effect.positionInContent = coordinates.positionInWindow() +
calculateWindowOffset() - state.positionOnScreen
effect.size = coordinates.size.toSize()

val blurRadiusPx = with(currentValueOf(LocalDensity)) { effect.blurRadius.toPx() }
Expand All @@ -90,8 +84,6 @@ internal class HazeChildNode(
updateEffect()
}

override fun onGloballyPositioned(coordinates: LayoutCoordinates) = onPlaced(coordinates)

override fun ContentDrawScope.draw() {
log(TAG) { "-> HazeChild. start draw()" }

Expand Down Expand Up @@ -128,6 +120,7 @@ internal class HazeChildNode(
block(effect)

if (effect.needInvalidation) {
log(TAG) { "invalidateDraw called, due to effect needing invalidation" }
invalidateDraw()
}
}
Expand All @@ -151,7 +144,7 @@ internal class HazeChildNode(
}
drawRect(effect.backgroundColor)

translate(inflatedOffset + state.positionOnScreen - effect.positionOnScreen) {
translate(inflatedOffset - effect.positionInContent) {
// Draw the content into our effect layer
drawLayer(contentLayer)
}
Expand All @@ -169,12 +162,12 @@ internal class HazeChildNode(
clippedContentLayer.renderEffect = effect.renderEffect
clippedContentLayer.alpha = effect.alpha

withPositionAndClip(
effectPositionOnScreen = effect.positionOnScreen,
size = effect.size,
innerDrawOffset = -inflatedOffset,
) {
drawLayer(clippedContentLayer)
clipRect(right = size.width, bottom = size.height) {
translate(-inflatedOffset) {
// Since we included a border around the content, we need to translate so that
// we don't see it (but it still affects the RenderEffect)
drawLayer(clippedContentLayer)
}
}
}

Expand Down Expand Up @@ -243,7 +236,7 @@ internal class HazeChildNode(
add(lerp(min, max, (i + 1f) / steps) to Color.Transparent)
}

log("HazeChildNode") {
log(TAG) {
"drawProgressiveEffect. " +
"step=$i, " +
"fraction=$fraction, " +
Expand All @@ -269,30 +262,15 @@ internal class HazeChildNode(
),
)

withPositionAndClip(
effectPositionOnScreen = effect.positionOnScreen,
size = effect.size,
innerDrawOffset = innerDrawOffset,
block = { drawLayer(layer) },
)

graphicsContext.releaseGraphicsLayer(layer)
}
}

private inline fun DrawScope.withPositionAndClip(
effectPositionOnScreen: Offset,
size: Size,
innerDrawOffset: Offset = Offset.Zero,
block: DrawScope.() -> Unit,
) {
val drawOffset = (effectPositionOnScreen - positionOnScreen).takeOrElse { Offset.Zero }
translate(drawOffset) {
clipRect(right = size.width, bottom = size.height) {
// Since we included a border around the content, we need to translate so that
// we don't see it (but it still affects the RenderEffect)
translate(innerDrawOffset, block)
translate(innerDrawOffset) {
// Since we included a border around the content, we need to translate so that
// we don't see it (but it still affects the RenderEffect)
drawLayer(layer)
}
}

graphicsContext.releaseGraphicsLayer(layer)
}
}

Expand All @@ -314,9 +292,11 @@ internal class HazeChildNode(

private fun ReusableHazeEffect.onPostDraw() {
drawParametersDirty = false
progressiveDirty = false
positionChanged = false
}

private companion object {
internal companion object {
const val TAG = "HazeChild"
}
}
Expand Down Expand Up @@ -443,17 +423,26 @@ internal expect fun HazeChildNode.createRenderEffect(
internal class ReusableHazeEffect : HazeChildScope {
var renderEffect: RenderEffect? = null
var renderEffectDirty: Boolean = true
var positionChanged: Boolean = true
var drawParametersDirty: Boolean = true
var progressiveDirty: Boolean = true

var positionOnScreen: Offset by mutableStateOf(Offset.Unspecified)
var positionInContent: Offset = Offset.Unspecified
set(value) {
if (value != field) {
log("ReusableHazeEffect") { "positionInContent changed. Current: $field. New: $value" }
positionChanged = true
field = value
}
}

val isValid: Boolean
get() = size.isSpecified && layerSize.isSpecified

var size: Size = Size.Unspecified
set(value) {
if (value != field) {
log("ReusableHazeEffect") { "size changed. Current: $field. New: $value" }
// We use the size for crop rects/brush sizing
renderEffectDirty = true
field = value
Expand All @@ -463,6 +452,7 @@ internal class ReusableHazeEffect : HazeChildScope {
var layerSize: Size = Size.Unspecified
set(value) {
if (value != field) {
log("ReusableHazeEffect") { "layerSize changed. Current: $field. New: $value" }
renderEffectDirty = true
field = value
}
Expand All @@ -483,6 +473,7 @@ internal class ReusableHazeEffect : HazeChildScope {
override var blurRadius: Dp = HazeDefaults.blurRadius
set(value) {
if (value != field) {
log("ReusableHazeEffect") { "blurRadius changed. Current: $field. New: $value" }
renderEffectDirty = true
field = value
}
Expand All @@ -491,6 +482,7 @@ internal class ReusableHazeEffect : HazeChildScope {
override var noiseFactor: Float = HazeDefaults.noiseFactor
set(value) {
if (value != field) {
log("ReusableHazeEffect") { "noiseFactor changed. Current: $field. New: $value" }
renderEffectDirty = true
field = value
}
Expand All @@ -499,6 +491,7 @@ internal class ReusableHazeEffect : HazeChildScope {
override var mask: Brush? = null
set(value) {
if (value != field) {
log("ReusableHazeEffect") { "mask changed. Current: $field. New: $value" }
renderEffectDirty = true
field = value
}
Expand All @@ -509,6 +502,7 @@ internal class ReusableHazeEffect : HazeChildScope {
override var tints: List<HazeTint> = emptyList()
set(value) {
if (value != field) {
log("ReusableHazeEffect") { "tints changed. Current: $field. New: $value" }
renderEffectDirty = true
field = value
}
Expand All @@ -517,6 +511,7 @@ internal class ReusableHazeEffect : HazeChildScope {
override var fallbackTint: HazeTint? = null
set(value) {
if (value != field) {
log("ReusableHazeEffect") { "fallbackTint changed. Current: $field. New: $value" }
renderEffectDirty = true
field = value
}
Expand All @@ -525,6 +520,7 @@ internal class ReusableHazeEffect : HazeChildScope {
override var alpha: Float = 1f
set(value) {
if (value != field) {
log("ReusableHazeEffect") { "alpha changed. Current $field. New: $value" }
drawParametersDirty = true
field = value
}
Expand All @@ -533,6 +529,7 @@ internal class ReusableHazeEffect : HazeChildScope {
override var progressive: HazeProgressive? = null
set(value) {
if (value != field) {
log("ReusableHazeEffect") { "progressive changed. Current $field. New: $value" }
progressiveDirty = true
field = value
}
Expand All @@ -551,7 +548,15 @@ internal val ReusableHazeEffect.blurRadiusOrZero: Dp
get() = blurRadius.takeOrElse { 0.dp }

internal val ReusableHazeEffect.needInvalidation: Boolean
get() = renderEffectDirty || drawParametersDirty || progressiveDirty
get() {
log("ReusableHazeEffect") {
"needInvalidation. renderEffectDirty=$renderEffectDirty, " +
"drawParametersDirty=$drawParametersDirty, " +
"progressiveDirty=$progressiveDirty" +
"positionChanged=$positionChanged"
}
return renderEffectDirty || drawParametersDirty || progressiveDirty || positionChanged
}

private fun Size.expand(expansion: Float): Size {
return Size(width = width + expansion, height = height + expansion)
Expand All @@ -568,7 +573,7 @@ private fun DrawScope.drawFallbackEffect(
?.takeIf { it.color.isSpecified }
?: tints.firstOrNull()?.boostForFallback(blurRadius.takeOrElse { 0.dp })

log("HazeChildNode") { "drawEffect. Drawing effect with scrim: tint=$tint, mask=$mask, alpha=$alpha" }
log(HazeChildNode.TAG) { "drawEffect. Drawing effect with scrim: tint=$tint, mask=$mask, alpha=$alpha" }

fun scrim() {
if (tint != null) {
Expand Down
9 changes: 4 additions & 5 deletions haze/src/commonMain/kotlin/dev/chrisbanes/haze/HazeNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.GlobalPositionAwareModifierNode
import androidx.compose.ui.node.LayoutAwareModifierNode
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.platform.LocalGraphicsContext
import androidx.compose.ui.unit.Dp
Expand All @@ -23,13 +22,13 @@ internal class HazeNode(
var state: HazeState,
) : Modifier.Node(),
CompositionLocalConsumerModifierNode,
LayoutAwareModifierNode,
GlobalPositionAwareModifierNode,
DrawModifierNode {

override fun onGloballyPositioned(coordinates: LayoutCoordinates) = onPlaced(coordinates)

override fun onPlaced(coordinates: LayoutCoordinates) {
override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
log("HazeNode") {
"onPlaced: positionInWindow=${coordinates.positionInWindow()}"
}
state.positionOnScreen = coordinates.positionInWindow() + calculateWindowOffset()
}

Expand Down
2 changes: 1 addition & 1 deletion internal/benchmark/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ baselineProfile {
managedDevices += "pixel5Api30"
managedDevices += "pixel5Api34"
useConnectedDevices = false
enableEmulatorDisplay = true
enableEmulatorDisplay = false
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@

package dev.chrisbanes.haze.baselineprofile

import android.graphics.Point
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.uiautomator.By
import dev.chrisbanes.haze.testutils.navigateToImagesList
import dev.chrisbanes.haze.testutils.waitForObject
import dev.chrisbanes.haze.testutils.navigateToScaffold
import dev.chrisbanes.haze.testutils.navigateToScaffoldWithProgressive
import dev.chrisbanes.haze.testutils.scroll
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -25,12 +26,20 @@ class BaselineProfileGenerator {
pressHome()
startActivityAndWait()

// Scroll down several times
device.navigateToImagesList()
device.scroll("lazy_column")

// Scroll down several times
repeat(5) {
val column = device.waitForObject(By.res("lazy_column"))
column.drag(Point(column.visibleCenter.x, column.visibleBounds.top))
}
device.findObject(By.res("back")).click()
device.waitForIdle()

device.navigateToScaffoldWithProgressive()
device.scroll("lazy_grid")

device.findObject(By.res("back")).click()
device.waitForIdle()

device.navigateToScaffold()
device.scroll("lazy_grid")
}
}
Loading