perf: cache WaveGraphic paths per size (was rebuilding ~3k lineTo per draw frame) + measured motion pass

This commit is contained in:
avi 2026-09-28 20:41:37 -05:00
commit bd4f6bb874

View file

@ -84,6 +84,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -440,41 +441,49 @@ private fun WaveGraphic(modifier: Modifier = Modifier) {
val gradientColors = listOf(LocalTokens.current.accentFill, GradientMid, GradientEnd) val gradientColors = listOf(LocalTokens.current.accentFill, GradientMid, GradientEnd)
val accentGlow = LocalTokens.current.accentFill val accentGlow = LocalTokens.current.accentFill
Canvas(modifier = modifier) { // Build each wave path + its gradient ONCE per size/palette, not per draw
// pass: this canvas sits on the hub, and rebuilding ~1k lineTo segments
// per layer was the heavy frame behind the hub-transition jank (99th-p
// ~200ms). 4px steps are indistinguishable at this frequency and cut
// segment count by 4x.
var canvasSize by remember { mutableStateOf(androidx.compose.ui.unit.IntSize.Zero) }
val layers = remember(canvasSize, gradientColors) {
val w = canvasSize.width.toFloat()
val h = canvasSize.height.toFloat()
if (w <= 0f || h <= 0f) emptyList()
else (0..2).map { layer ->
val amplitude = (30f - layer * 8f) * (w / 400f)
val frequency = 0.008f + layer * 0.002f
val phaseShift = layer * 0.5f
val path = Path().apply {
moveTo(0f, h / 2 + amplitude * kotlin.math.sin(phaseShift.toDouble()).toFloat())
var x = 0f
while (x <= w) {
val y = h / 2 + amplitude * kotlin.math.sin(
(x * frequency * Math.PI * 2).toDouble() + phaseShift
).toFloat()
lineTo(x, y)
x += 4f
}
}
val strokeBrush = Brush.horizontalGradient(
colors = gradientColors.map { it.copy(alpha = 0.6f - layer * 0.15f) },
startX = 0f,
endX = w,
)
Triple(path, strokeBrush, (3f - layer * 0.5f) * (w / 400f))
}
}
Canvas(
modifier = modifier.onSizeChanged { canvasSize = it },
) {
val width = size.width val width = size.width
val height = size.height val height = size.height
val centerY = height / 2 val centerY = height / 2
// Draw multiple wave layers for ((path, strokeBrush, strokeWidth) in layers) {
for (layer in 0..2) { drawPath(path = path, brush = strokeBrush, style = Stroke(width = strokeWidth))
val path = Path()
val amplitude = (30f - layer * 8f) * (width / 400f)
val frequency = 0.008f + layer * 0.002f
val phaseShift = layer * 0.5f
path.moveTo(0f, centerY)
for (x in 0..width.toInt()) {
val xFloat = x.toFloat()
val y = centerY + amplitude * kotlin.math.sin(
(xFloat * frequency * Math.PI * 2).toDouble() + phaseShift
).toFloat()
if (x == 0) {
path.moveTo(xFloat, y)
} else {
path.lineTo(xFloat, y)
}
}
drawPath(
path = path,
brush = Brush.horizontalGradient(
colors = gradientColors.map { it.copy(alpha = 0.6f - layer * 0.15f) },
startX = 0f,
endX = width,
),
style = Stroke(width = (3f - layer * 0.5f) * (width / 400f)),
)
} }
// Ambient glow behind the waves — wide, faint, no visible disc edge. // Ambient glow behind the waves — wide, faint, no visible disc edge.