mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-06 18:38:34 +00:00
desktop: fix laggy bars blur by blurring a narrower offscreen copy (#7556)
Skia's raster blur runs at full resolution, so each bar blurred width x (bar + 6 sigma) pixels every frame: about 80 ms per frame for the two bars of a chat at the default radius, which cut scrolling to 5-9 fps on the software renderer. Scaling the layer cannot help, because a layer's filter is evaluated in device space and the scale is applied to the sigma too. The bar is now drawn into an offscreen surface up to 8x narrower and blurred there, which measured 5-6x cheaper and within 0.25/255 of the original. Blurring a copy means the bar no longer follows the content on its own, so the scroll containers bump a version on the app bar handler and the bar depends on it. Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
co-authored by
Evgeny Poberezkin
parent
9cd1c46606
commit
ef66e5e520
+6
@@ -0,0 +1,6 @@
|
||||
package chat.simplex.common.platform
|
||||
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
|
||||
// Draws the bars' background blurred. The drawing to blur is given in bar coordinates, whatever size the blur runs at.
|
||||
expect fun DrawScope.drawBarsBlurred(radiusPx: Float, barWidth: Float, barHeight: Float, drawBar: DrawScope.() -> Unit)
|
||||
+32
-27
@@ -12,6 +12,7 @@ import androidx.compose.ui.graphics.layer.GraphicsLayer
|
||||
import androidx.compose.ui.graphics.layer.drawLayer
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.common.platform.appPlatform
|
||||
import chat.simplex.common.platform.drawBarsBlurred
|
||||
import chat.simplex.common.ui.theme.CurrentColors
|
||||
|
||||
fun Modifier.blurredBackgroundModifier(
|
||||
@@ -32,7 +33,7 @@ fun Modifier.blurredBackgroundModifier(
|
||||
return if (appPlatform.isAndroid) {
|
||||
this.androidBlurredModifier(keyboardInset, blurRadius.value, keyboardCoversBar, onTop, graphicsLayer, backgroundGraphicsLayer, backgroundGraphicsLayerSize, density)
|
||||
} else {
|
||||
this.desktopBlurredModifier(keyboardInset, blurRadius, keyboardCoversBar, onTop, graphicsLayer, backgroundGraphicsLayer, backgroundGraphicsLayerSize, density)
|
||||
this.desktopBlurredModifier(keyboardInset, blurRadius, keyboardCoversBar, onTop, handler, graphicsLayer, backgroundGraphicsLayer, backgroundGraphicsLayerSize, density)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,40 +101,44 @@ private fun Modifier.desktopBlurredModifier(
|
||||
blurRadius: State<Int>,
|
||||
keyboardCoversBar: Boolean,
|
||||
onTop: Boolean,
|
||||
handler: AppBarHandler,
|
||||
graphicsLayer: GraphicsLayer,
|
||||
backgroundGraphicsLayer: GraphicsLayer,
|
||||
backgroundGraphicsLayerSize: State<IntSize>,
|
||||
density: Density
|
||||
): Modifier = this
|
||||
.graphicsLayer {
|
||||
renderEffect = if (blurRadius.value > 0) BlurEffect(blurRadius.value.dp.toPx(), blurRadius.value.dp.toPx()) else null
|
||||
clip = blurRadius.value > 0
|
||||
}
|
||||
.drawBehind {
|
||||
drawRect(CurrentColors.value.colors.background)
|
||||
if (onTop) {
|
||||
clipRect {
|
||||
if (backgroundGraphicsLayer.size != IntSize.Zero) {
|
||||
drawLayer(backgroundGraphicsLayer)
|
||||
} else {
|
||||
drawRect(CurrentColors.value.colors.background, size = Size(graphicsLayer.size.width.toFloat(), graphicsLayer.size.height.toFloat()))
|
||||
val barWidth = size.width
|
||||
val barHeight = size.height
|
||||
// The blur is taken from a copy of the scrolled content rather than drawn from that layer, so unlike a layer's own
|
||||
// filter it does not follow the content on its own: the bar is redrawn when the container reports the copy moved.
|
||||
handler.contentVersion.value
|
||||
drawBarsBlurred(blurRadius.value.dp.toPx(), barWidth, barHeight) {
|
||||
drawRect(CurrentColors.value.colors.background, size = Size(barWidth, barHeight))
|
||||
if (onTop) {
|
||||
clipRect(0f, 0f, barWidth, barHeight) {
|
||||
if (backgroundGraphicsLayer.size != IntSize.Zero) {
|
||||
drawLayer(backgroundGraphicsLayer)
|
||||
} else {
|
||||
drawRect(CurrentColors.value.colors.background, size = Size(graphicsLayer.size.width.toFloat(), graphicsLayer.size.height.toFloat()))
|
||||
}
|
||||
drawLayer(graphicsLayer)
|
||||
}
|
||||
drawLayer(graphicsLayer)
|
||||
}
|
||||
} else {
|
||||
val bgSize = when {
|
||||
backgroundGraphicsLayerSize.value.height == 0 && backgroundGraphicsLayer.size.height != 0 -> backgroundGraphicsLayer.size.height
|
||||
backgroundGraphicsLayerSize.value.height == 0 -> graphicsLayer.size.height
|
||||
else -> backgroundGraphicsLayerSize.value.height
|
||||
}
|
||||
val keyboardHeightCovered = if (!keyboardCoversBar) keyboardInset.getBottom(density) else 0
|
||||
translate(top = -bgSize + size.height + keyboardHeightCovered) {
|
||||
if (backgroundGraphicsLayer.size != IntSize.Zero) {
|
||||
drawLayer(backgroundGraphicsLayer)
|
||||
} else {
|
||||
drawRect(CurrentColors.value.colors.background, size = Size(graphicsLayer.size.width.toFloat(), graphicsLayer.size.height.toFloat()))
|
||||
} else {
|
||||
val bgSize = when {
|
||||
backgroundGraphicsLayerSize.value.height == 0 && backgroundGraphicsLayer.size.height != 0 -> backgroundGraphicsLayer.size.height
|
||||
backgroundGraphicsLayerSize.value.height == 0 -> graphicsLayer.size.height
|
||||
else -> backgroundGraphicsLayerSize.value.height
|
||||
}
|
||||
val keyboardHeightCovered = if (!keyboardCoversBar) keyboardInset.getBottom(density) else 0
|
||||
translate(top = -bgSize + barHeight + keyboardHeightCovered) {
|
||||
if (backgroundGraphicsLayer.size != IntSize.Zero) {
|
||||
drawLayer(backgroundGraphicsLayer)
|
||||
} else {
|
||||
drawRect(CurrentColors.value.colors.background, size = Size(graphicsLayer.size.width.toFloat(), graphicsLayer.size.height.toFloat()))
|
||||
}
|
||||
drawLayer(graphicsLayer)
|
||||
}
|
||||
drawLayer(graphicsLayer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -78,6 +78,10 @@ class AppBarHandler(
|
||||
|
||||
val backgroundGraphicsLayerSize: MutableState<IntSize> = mutableStateOf(IntSize.Zero)
|
||||
|
||||
// Bars that blur a copy of the scrolled content depend on this to know the copy moved. It is bumped from a collector
|
||||
// rather than while drawing: a write made during the draw phase invalidates the bars every frame and never settles.
|
||||
val contentVersion: MutableState<Int> = mutableStateOf(0)
|
||||
|
||||
companion object {
|
||||
var appBarMaxHeightPx: Int = 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user