Skip to content

Commit

Permalink
Proper onLightingInfoChanged tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
MatkovIvan committed Dec 20, 2024
1 parent 2e92dd8 commit 7ab0b32
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,18 @@ package androidx.compose.ui.graphics
import androidx.compose.runtime.snapshots.SnapshotStateObserver
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import org.jetbrains.skia.Point3

@InternalComposeUiApi
class SkiaGraphicsContext(
var containerSize: () -> IntSize,
private val measureDrawBounds: Boolean,
) : GraphicsContext {
private val snapshotObserver = SnapshotStateObserver { command ->
internal val measureDrawBounds: Boolean,
): GraphicsContext {
internal val lightGeometry = LightGeometry()
internal val lightInfo = LightInfo()
internal val snapshotObserver = SnapshotStateObserver { command ->
command()
}

Expand All @@ -39,13 +43,34 @@ class SkiaGraphicsContext(
snapshotObserver.clear()
}

override fun createGraphicsLayer() = GraphicsLayer(
snapshotObserver = snapshotObserver,
containerSize = containerSize,
measureDrawBounds = measureDrawBounds
)
fun setLightingInfo(
centerX: Float = Float.MIN_VALUE,
centerY: Float = Float.MIN_VALUE,
centerZ: Float = Float.MIN_VALUE,
radius: Float = 0f,
ambientShadowAlpha: Float = 0f,
spotShadowAlpha: Float = 0f
) {
lightGeometry.center = Point3(centerX, centerY, centerZ)
lightGeometry.radius = radius
lightInfo.ambientShadowAlpha = ambientShadowAlpha
lightInfo.spotShadowAlpha = spotShadowAlpha
}

override fun createGraphicsLayer() = GraphicsLayer(this)

override fun releaseGraphicsLayer(layer: GraphicsLayer) {
layer.release()
}
}

// Adoption of frameworks/base/libs/hwui/Lighting.h
internal data class LightGeometry(
var center: Point3 = Point3(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE),
var radius: Float = 0f
)

internal data class LightInfo(
var ambientShadowAlpha: Float = 0f,
var spotShadowAlpha: Float = 0f
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package androidx.compose.ui.graphics.layer

import androidx.compose.runtime.SnapshotMutationPolicy
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.SnapshotStateObserver
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
Expand All @@ -37,6 +36,7 @@ import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.RenderEffect
import androidx.compose.ui.graphics.SkiaBackedCanvas
import androidx.compose.ui.graphics.SkiaGraphicsContext
import androidx.compose.ui.graphics.asComposeCanvas
import androidx.compose.ui.graphics.asSkiaColorFilter
import androidx.compose.ui.graphics.asSkiaPath
Expand All @@ -52,9 +52,7 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import kotlin.math.min
import org.jetbrains.skia.Picture
import org.jetbrains.skia.PictureRecorder
import org.jetbrains.skia.Point3
Expand All @@ -63,15 +61,13 @@ import org.jetbrains.skia.Rect as SkRect
import org.jetbrains.skia.ShadowUtils

actual class GraphicsLayer internal constructor(
private val snapshotObserver: SnapshotStateObserver,
private val containerSize: () -> IntSize,
measureDrawBounds: Boolean,
private val context: SkiaGraphicsContext,
) {
private val pictureDrawScope = CanvasDrawScope()
private val pictureRecorder = PictureRecorder()
private var picture: Picture? = null
// Use factory for BBoxHierarchy to track real bounds of drawn content
private val bbhFactory = if (measureDrawBounds) RTreeFactory() else null
private val bbhFactory = if (context.measureDrawBounds) RTreeFactory() else null

// Composable state marker for tracking drawing invalidations.
private val drawState = mutableStateOf(Unit, object : SnapshotMutationPolicy<Unit> {
Expand Down Expand Up @@ -230,7 +226,7 @@ actual class GraphicsLayer internal constructor(
childDependenciesTracker.withTracking(
onDependencyRemoved = { it.onRemovedFromParentLayer() }
) {
snapshotObserver.observeReads(
context.snapshotObserver.observeReads(
scope = this,
onValueChangedForScope = {
// Can be called from another thread
Expand Down Expand Up @@ -483,7 +479,7 @@ actual class GraphicsLayer internal constructor(
it.onRemovedFromParentLayer()
}

snapshotObserver.clear(this)
context.snapshotObserver.clear(this)
}
}

Expand All @@ -509,26 +505,26 @@ actual class GraphicsLayer internal constructor(
offscreenBufferRequested
}

private fun drawShadow(canvas: Canvas) = with(density) {
private fun drawShadow(canvas: Canvas) {
val path = when (val tmpOutline = internalOutline) {
is Outline.Rectangle -> Path().apply { addRect(tmpOutline.rect) }
is Outline.Rounded -> Path().apply { addRoundRect(tmpOutline.roundRect) }
is Outline.Generic -> tmpOutline.path
else -> return
}

val translationZ = 0f
val zParams = Point3(0f, 0f, shadowElevation + translationZ)
val lightPos = getLightCenter(containerSize())
val ambientColor = ambientShadowColor.copy(alpha = AMBIENT_SHADOW_ALPHA * alpha)
val spotColor = spotShadowColor.copy(alpha = SPOT_SHADOW_ALPHA * alpha)
val zParams = Point3(0f, 0f, shadowElevation)
val ambientAlpha = context.lightInfo.ambientShadowAlpha * alpha
val spotAlpha = context.lightInfo.spotShadowAlpha * alpha
val ambientColor = ambientShadowColor.copy(alpha = ambientAlpha)
val spotColor = spotShadowColor.copy(alpha = spotAlpha)

ShadowUtils.drawShadow(
return ShadowUtils.drawShadow(
canvas = canvas.nativeCanvas,
path = path.asSkiaPath(),
zPlaneParams = zParams,
lightPos = lightPos,
lightRadius = LIGHT_RADIUS.toPx(),
lightPos = context.lightGeometry.center,
lightRadius = context.lightGeometry.radius,
ambientColor = ambientColor.toArgb(),
spotColor = spotColor.toArgb(),
transparentOccluder = alpha < 1f,
Expand Down Expand Up @@ -557,24 +553,3 @@ private val PICTURE_BOUNDS = SkRect.makeLTRB(
r = PICTURE_MAX_VALUE,
b = PICTURE_MAX_VALUE
)


// Adoption of android.view.ThreadedRenderer.setLightCenter
private fun Density.getLightCenter(containerSize: IntSize): Point3 {
val lightX = containerSize.width / 2f
val lightY = LIGHT_Y.toPx()
// To prevent shadow distortion on larger screens, scale the z position of the light source
// relative to the smallest screen dimension.
val zRatio = min(containerSize.width, containerSize.height).toFloat() / 450.dp.toPx()
val zWeightedAdjustment = (zRatio + 2) / 3f
val lightZ = LIGHT_Z.toPx() * zWeightedAdjustment

return Point3(lightX,lightY, lightZ)
}

// Values from core/res/res/values/dimens.xml
private val LIGHT_Y = 0.dp
private val LIGHT_Z = 500.dp
private val LIGHT_RADIUS = 800.dp
private const val AMBIENT_SHADOW_ALPHA = 0.039f
private const val SPOT_SHADOW_ALPHA = 0.19f
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.runtime.collection.mutableVectorOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.Modifier
Expand Down Expand Up @@ -65,6 +66,7 @@ import androidx.compose.ui.platform.PlatformClipboardManager
import androidx.compose.ui.platform.PlatformContext
import androidx.compose.ui.platform.PlatformRootForTest
import androidx.compose.ui.platform.PlatformTextInputSessionScope
import androidx.compose.ui.platform.setLightingInfo
import androidx.compose.ui.scene.ComposeScene
import androidx.compose.ui.scene.ComposeSceneInputHandler
import androidx.compose.ui.scene.ComposeScenePointer
Expand All @@ -90,6 +92,10 @@ import androidx.compose.ui.viewinterop.pointerInteropFilter
import kotlin.coroutines.CoroutineContext
import kotlin.math.max
import kotlin.math.min
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch

/**
* Owner of root [LayoutNode].
Expand Down Expand Up @@ -126,10 +132,8 @@ internal class RootNodeOwner(

private val rootSemanticsNode = EmptySemanticsModifier()
private val snapshotObserver = snapshotInvalidationTracker.snapshotObserver()
private val graphicsContext = SkiaGraphicsContext(
containerSize = { platformContext.windowInfo.containerSize },
measureDrawBounds = platformContext.measureDrawLayerBounds,
)
private val graphicsContext = SkiaGraphicsContext(platformContext.measureDrawLayerBounds)
private val coroutineScope = CoroutineScope(coroutineContext + Job(parent = coroutineContext[Job]))

private val rootModifier = EmptySemanticsElement(rootSemanticsNode)
.focusProperties {
Expand Down Expand Up @@ -177,10 +181,18 @@ internal class RootNodeOwner(
owner.root.attach(owner)
platformContext.rootForTestListener?.onRootForTestCreated(rootForTest)
onRootConstrainsChanged(size?.toConstraints())
onLightingInfoChanged()
coroutineScope.launch {
snapshotFlow { platformContext.windowInfo.containerSize }
.collect {
onLightingInfoChanged()
}
}
}

fun dispose() {
check(!isDisposed) { "RootNodeOwner is already disposed" }
coroutineScope.cancel()
platformContext.rootForTestListener?.onRootForTestDisposed(rootForTest)
snapshotObserver.stopObserving()
graphicsContext.dispose()
Expand Down Expand Up @@ -224,6 +236,7 @@ internal class RootNodeOwner(
fun invalidatePositionInWindow() {
owner.root.layoutDelegate.measurePassDelegate.notifyChildrenUsingCoordinatesWhilePlacing()
measureAndLayoutDelegate.dispatchOnPositionedCallbacks(forceDispatch = true)
onLightingInfoChanged()
}

fun draw(canvas: Canvas) = trace("RootNodeOwner:draw") {
Expand All @@ -245,6 +258,14 @@ internal class RootNodeOwner(
}
}

private fun onLightingInfoChanged() {
graphicsContext.setLightingInfo(
canvasOffset = platformContext.convertLocalToWindowPosition(Offset.Zero),
density = density,
containerSize = platformContext.windowInfo.containerSize
)
}

@OptIn(InternalCoreApi::class)
fun onPointerInput(event: PointerInputEvent) {
if (event.button != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.compose.ui.graphics.GraphicsContext
import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
import androidx.compose.ui.graphics.SkiaGraphicsContext
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
Expand All @@ -41,7 +42,8 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection

import androidx.compose.ui.unit.dp
import org.jetbrains.skia.Point3

internal class GraphicsLayerOwnerLayer(
private var graphicsLayer: GraphicsLayer,
Expand Down Expand Up @@ -357,3 +359,34 @@ internal class GraphicsLayerOwnerLayer(
)
}
}

internal fun SkiaGraphicsContext.setLightingInfo(
canvasOffset: Offset,
density: Density,
containerSize: IntSize
) = with(density) {
// Adoption of android.view.ThreadedRenderer.setLightCenter
val lightX = containerSize.width / 2f - canvasOffset.x
val lightY = LIGHT_Y.toPx() - canvasOffset.y
// To prevent shadow distortion on larger screens, scale the z position of the light source
// relative to the smallest screen dimension.
val zRatio = kotlin.math.min(containerSize.width, containerSize.height).toFloat() / 450.dp.toPx()
val zWeightedAdjustment = (zRatio + 2) / 3f
val lightZ = LIGHT_Z.toPx() * zWeightedAdjustment

setLightingInfo(
centerX = lightX,
centerY = lightY,
centerZ = lightZ,
radius = LIGHT_RADIUS.toPx(),
ambientShadowAlpha = AMBIENT_SHADOW_ALPHA,
spotShadowAlpha = SPOT_SHADOW_ALPHA
)
}

// Values from core/res/res/values/dimens.xml
private val LIGHT_Y = 0.dp
private val LIGHT_Z = 500.dp
private val LIGHT_RADIUS = 800.dp
private const val AMBIENT_SHADOW_ALPHA = 0.039f
private const val SPOT_SHADOW_ALPHA = 0.19f

0 comments on commit 7ab0b32

Please sign in to comment.