public virtual void Update(ServerCompositionTarget root)
        {
            if (Parent == null && Root == null)
            {
                return;
            }

            var wasVisible = IsVisibleInFrame;

            // Calculate new parent-relative transform
            if (_combinedTransformDirty)
            {
                CombinedTransformMatrix = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint,
                                                                       // HACK: Ignore RenderTransform set by the adorner layer
                                                                       AdornedVisual != null ? Matrix4x4.Identity : TransformMatrix,
                                                                       Scale, RotationAngle, Orientation, Offset);
                _combinedTransformDirty = false;
            }

            var parentTransform = (AdornedVisual ?? Parent)?.GlobalTransformMatrix ?? Matrix4x4.Identity;

            var newTransform = CombinedTransformMatrix * parentTransform;

            // Check if visual was moved and recalculate face orientation
            var positionChanged = false;

            if (GlobalTransformMatrix != newTransform)
            {
                _isBackface = Vector3.Transform(
                    new Vector3(0, 0, float.PositiveInfinity), GlobalTransformMatrix).Z <= 0;
                positionChanged = true;
            }

            var oldTransformedContentBounds      = TransformedOwnContentBounds;
            var oldCombinedTransformedClipBounds = _combinedTransformedClipBounds;

            if (_parent?.IsDirtyComposition == true)
            {
                IsDirtyComposition = true;
                _isDirtyForUpdate  = true;
            }

            var invalidateOldBounds = _isDirtyForUpdate;
            var invalidateNewBounds = _isDirtyForUpdate;

            GlobalTransformMatrix = newTransform;

            var ownBounds = OwnContentBounds;

            if (ownBounds != _oldOwnContentBounds || positionChanged)
            {
                _oldOwnContentBounds = ownBounds;
                if (ownBounds.IsEmpty)
                {
                    TransformedOwnContentBounds = default;
                }
                else
                {
                    TransformedOwnContentBounds =
                        ownBounds.TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix));
                }
            }

            if (_clipSizeDirty || positionChanged)
            {
                _transformedClipBounds = ClipToBounds
                    ? new Rect(new Size(Size.X, Size.Y))
                                         .TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix))
                    : null;

                _clipSizeDirty = false;
            }

            _combinedTransformedClipBounds = Parent?._combinedTransformedClipBounds ?? new Rect(Root !.Size);
            if (_transformedClipBounds != null)
            {
                _combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value);
            }

            EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1);

            IsVisibleInFrame = _parent?.IsVisibleInFrame != false && Visible && EffectiveOpacity > 0.04 && !_isBackface &&
                               !_combinedTransformedClipBounds.IsEmpty;

            if (wasVisible != IsVisibleInFrame || positionChanged)
            {
                invalidateOldBounds |= wasVisible;
                invalidateNewBounds |= IsVisibleInFrame;
            }

            // Invalidate new bounds
            if (invalidateNewBounds)
            {
                AddDirtyRect(TransformedOwnContentBounds.Intersect(_combinedTransformedClipBounds));
            }

            if (invalidateOldBounds)
            {
                AddDirtyRect(oldTransformedContentBounds.Intersect(oldCombinedTransformedClipBounds));
            }


            _isDirtyForUpdate = false;

            // Update readback indices
            var     i        = Root !.Readback;
            ref var readback = ref GetReadback(i.WriteIndex);