app/layer_tree.go

Functions

Functions

func AppendOverlaysToLayerTree

AppendOverlaysToLayerTree adds overlay content boundaries to an existing

Layer Tree. Overlays are appended AFTER main tree children, so they

composite on top (correct Z-order: main content → overlays bottom-to-top).

 

Each overlay content widget that is a RepaintBoundary gets its own

OffsetLayer + PictureLayer in the tree, just like main tree boundaries.

Non-boundary overlay widgets are skipped (they have no scene to composite).

 

The existing parameter is used for persistent tree reuse: overlay layers

from previous frames are matched by BoundaryCacheKey.

func AppendOverlaysToLayerTree(tree *compositor.OffsetLayerImpl, overlayWidgets []widget.Widget, existing *compositor.OffsetLayerImpl) {
	if tree == nil || len(overlayWidgets) == 0 {
		return
	}

	// Record child count before appending so we can fix overlay-specific
	// flags on the newly added layers below.
	preCount := len(tree.Children())

	// Collect existing overlay layers for reuse.
	var index map[uint64]layerIndex
	if existing != nil {
		index = collectLayerIndex(existing)
	}

	for _, w := range overlayWidgets {
		if w == nil {
			continue
		}
		if index != nil {
			updateLayerRecursive(w, tree, index, 0, 0)
		} else {
			buildLayerRecursive(w, tree, 0, 0)
		}
	}

	// Fix IsRoot flag on overlay PictureLayers. Overlay content widgets have
	// Parent() == nil (they're standalone, not part of the main tree), so
	// buildBoundaryLayer/syncPictureLayer sets IsRoot=true. This causes
	// DrawGPUTextureBase (QueueBaseLayer, last-call-wins) to overwrite the
	// actual root texture with the overlay texture → black background.
	// Overlays must use DrawGPUTexture (sublayer blit) instead.
	for _, child := range tree.Children()[preCount:] {
		clearRootOnPictureLayers(child)
	}
}

func BuildLayerTree

BuildLayerTree walks the widget tree and constructs a compositor layer tree.

Each RepaintBoundary widget produces a PictureLayer inside an OffsetLayer.

Non-boundary widgets are skipped (they're drawn inside their parent boundary).

 

ADR-007 Phase D: Layer Tree provides STRUCTURE (which boundaries exist,

their offsets, clip rects, opacity) for the texture rendering/blitting

pipeline. PictureLayerImpl stores BoundaryCacheKey, IsRoot, and Size to

link back to the per-boundary GPU texture cache in renderLoop.

 

Flutter equivalent: Layer tree is built during paint via paintChild.

func BuildLayerTree(root widget.Widget) *compositor.OffsetLayerImpl {
	if root == nil {
		return compositor.NewOffsetLayer(geometry.Point{})
	}

	rootLayer := compositor.NewOffsetLayer(geometry.Point{})
	buildLayerRecursive(root, rootLayer, 0, 0)
	return rootLayer
}

func PaintBoundaryLayers

PaintBoundaryLayers walks the widget tree and re-records dirty boundaries.

This is the Flutter flushPaint equivalent: only dirty boundary PictureLayers

are re-recorded. Clean boundaries keep their cached scenes.

 

After this function, all boundary CachedScene values are fresh.

The compositor can then Compose the layer tree to assemble the final scene.

PaintBoundaryLayers re-records dirty boundaries with nil context.

func PaintBoundaryLayers(root widget.Widget, _ *compositor.OffsetLayerImpl) {
	PaintBoundaryLayersWithContext(root, nil, nil)
}

func PaintBoundaryLayersWithContext

PaintBoundaryLayersWithContext re-records dirty boundaries with a given context.

func PaintBoundaryLayersWithContext(root widget.Widget, _ *compositor.OffsetLayerImpl, ctx widget.Context) {
	if root == nil {
		return
	}
	paintBoundaryRecursiveCtx(root, ctx)
}

func PaintOverlayBoundaries

PaintOverlayBoundaries re-records dirty overlay content boundaries.

Overlay content widgets are already marked as RepaintBoundary by PushOverlay.

This function walks each overlay content widget (same as PaintBoundaryLayers

walks the main tree) so their CachedScene values are fresh for the compositor.

func PaintOverlayBoundaries(overlayWidgets []widget.Widget, ctx widget.Context) {
	for _, w := range overlayWidgets {
		if w == nil {
			continue
		}
		paintBoundaryRecursiveCtx(w, ctx)
	}
}

func UpdateLayerTree

UpdateLayerTree builds or updates a persistent layer tree. On the first call

(existing == nil), it builds from scratch (same as BuildLayerTree). On

subsequent calls, it reuses PictureLayerImpl and OffsetLayerImpl objects

for boundaries that still exist (matched by BoundaryCacheKey), updating

their fields from the current widget state. New boundaries get fresh layers;

removed boundaries are dropped.

 

Flutter equivalent: Layer.addRetained + ContainerLayer.updateSubtreeNeedsAddToScene.

The persistent tree eliminates per-frame layer allocations for stable UIs.

 

Returns the root OffsetLayerImpl (may be the same pointer as existing or new).

func UpdateLayerTree(root widget.Widget, existing *compositor.OffsetLayerImpl) *compositor.OffsetLayerImpl {
	if existing == nil {
		return BuildLayerTree(root)
	}
	if root == nil {
		return compositor.NewOffsetLayer(geometry.Point{})
	}

	// Collect existing layers indexed by BoundaryCacheKey for O(1) lookup.
	index := collectLayerIndex(existing)

	// Build new tree structure, reusing existing layer objects where possible.
	newRoot := compositor.NewOffsetLayer(geometry.Point{})
	updateLayerRecursive(root, newRoot, index, 0, 0)

	return newRoot
}