waterui 0.3.0

A modern UI framework for Rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
// Compiled out when the app disables WaterUI's `gpu` feature: the
// `waterui_*` GPU symbols this file binds do not exist in that build.
#if !WATERUI_NO_GPU
import Foundation
import Metal
import OSLog
import QuartzCore

#if canImport(UIKit)
  import UIKit
#elseif canImport(AppKit)
  import AppKit
#endif

typealias WuiMetalViewCaptureCompletion = @MainActor @Sendable (Bool) -> Void

@MainActor
private final class WuiMetalFenceBatch {
  private var remaining: Int
  private let completion: @MainActor @Sendable () -> Void

  init(count: Int, completion: @escaping @MainActor @Sendable () -> Void) {
    precondition(count > 0, "A GPU fence batch must contain at least one submission")
    self.remaining = count
    self.completion = completion
  }

  func completeOne() {
    remaining -= 1
    precondition(remaining >= 0, "A GPU fence batch completed more than once")
    if remaining == 0 {
      completion()
    }
  }
}

/// Captures a native view subtree — and any `GpuSurface` nested inside it — into
/// a single Metal texture for the filter and view-effect pipelines.
///
/// # Orientation and scale contract
///
/// The destination texture is *top-down* (texel row 0 is the visually topmost
/// row) and sized in device pixels, so it can be handed straight to wgpu and
/// presented by a `CAMetalLayer` without any further flip or rescale. Neither
/// property comes for free:
///
/// - `CARenderer` renders a layer tree bottom-up: layer-space y grows with the
///   destination row index, which is the opposite of every other texture in this
///   pipeline.
/// - `CARenderer.bounds` is the destination rectangle in *pixels*, while the
///   layer tree is laid out in *points*. Left alone, a 100×100pt subtree renders
///   into the top-left 100×100 pixels of a 200×200px target on a 2× display.
///
/// Both are corrected in one step by `withCaptureTransform`, which mirrors and
/// scales the captured layer tree for the duration of the `CARenderer` frame.
/// Because the capture texture is then in the same orientation as a
/// wgpu-rendered `GpuSurface` texture, `CaptureComposite.metal` is a plain
/// identity copy for both of its inputs.
///
/// # Concurrency
///
/// View-tree state (`activeGpuSurfaces`, the `CARenderer`, the overlay texture)
/// is main-actor isolated. GPU composition state lives in `Compositor`, which
/// confines every one of its members to a private serial queue. The type is
/// `@unchecked Sendable` because Swift cannot express that split; nothing here
/// is shared without either the main actor or that serial queue mediating it.
final class WuiMetalViewCapture: @unchecked Sendable {
  private struct GpuSurfaceSnapshot: @unchecked Sendable {
    let surface: WuiGpuSurface
    let origin: MTLOrigin
    let size: MTLSize
    let pixelFormat: MTLPixelFormat
  }

  private struct Preparation: @unchecked Sendable {
    let device: MTLDevice
    let targetTexture: MTLTexture
    let overlayTexture: MTLTexture?
    let nativeCaptureFence: MTLCommandBuffer
    let snapshots: [GpuSurfaceSnapshot]
  }

  private struct SurfaceTextureEntry {
    let texture: MTLTexture
    let size: MTLSize
    let pixelFormat: MTLPixelFormat
  }

  private struct RenderedGpuSurface: @unchecked Sendable {
    let snapshot: GpuSurfaceSnapshot
    let texture: MTLTexture
  }

  /// Maps the captured layer tree's point space onto the pixel-sized destination.
  private struct CaptureGeometry {
    let scaleX: CGFloat
    let scaleY: CGFloat
    /// Destination height in pixels; the mirror axis of the capture transform.
    let targetHeight: CGFloat
  }

  /// GPU composition state, confined to `queue`.
  ///
  /// Every member is touched only from inside `perform`, so the serial queue
  /// supplies the mutual exclusion the type system cannot — hence
  /// `@unchecked Sendable`. Work enqueued during teardown is therefore ordered
  /// after any composition still in flight, and the queue's own strong
  /// reference keeps the resources alive until that work drains.
  private final class Compositor: @unchecked Sendable {
    private let queue = DispatchQueue(
      label: "dev.waterui.graphics.capture-composition",
      qos: .userInteractive
    )
    private var commandQueue: MTLCommandQueue?
    private var surfaceTextures: [ObjectIdentifier: SurfaceTextureEntry] = [:]
    private var pipeline: MTLRenderPipelineState?
    private var pipelineFormat: MTLPixelFormat = .invalid
    private var sampler: MTLSamplerState?

    func perform(_ body: @escaping @Sendable (Compositor) -> Void) {
      queue.async { [self] in
        body(self)
      }
    }

    /// Drops cached per-surface textures once all in-flight composition drains.
    func discardResources() {
      queue.async { [self] in
        surfaceTextures.removeAll()
      }
    }

    func prepareSurfaceTextures(
      snapshots: [GpuSurfaceSnapshot],
      device: MTLDevice
    ) -> [RenderedGpuSurface] {
      let activeSurfaceIds = Set(snapshots.lazy.map { ObjectIdentifier($0.surface) })
      surfaceTextures = surfaceTextures.filter { activeSurfaceIds.contains($0.key) }
      return snapshots.map { snapshot in
        RenderedGpuSurface(
          snapshot: snapshot,
          texture: surfaceTexture(for: snapshot, device: device)
        )
      }
    }

    func makeCommandBuffer(device: MTLDevice) -> MTLCommandBuffer {
      if commandQueue == nil || commandQueue?.device !== device {
        commandQueue = device.makeCommandQueue()
      }
      guard let commandBuffer = commandQueue?.makeCommandBuffer() else {
        fatalError("Failed to create the Metal view composition command buffer")
      }
      return commandBuffer
    }

    func encodeComposition(
      surfaces: [RenderedGpuSurface],
      overlayTexture: MTLTexture?,
      targetTexture: MTLTexture,
      commandBuffer: MTLCommandBuffer,
      device: MTLDevice
    ) {
      let descriptor = MTLRenderPassDescriptor()
      descriptor.colorAttachments[0].texture = targetTexture
      descriptor.colorAttachments[0].loadAction = .clear
      descriptor.colorAttachments[0].storeAction = .store
      descriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0)
      guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
        fatalError("Failed to create the Metal capture composition encoder")
      }
      encoder.setRenderPipelineState(
        renderPipeline(device: device, format: targetTexture.pixelFormat)
      )
      encoder.setFragmentSamplerState(compositeSampler(device: device), index: 0)
      // GpuSurface contents first, then the native overlay blended on top: the
      // overlay is transparent wherever a surface's Metal layer was suppressed.
      // Viewport origins are top-down, which matches both the snapshot rects
      // (taken in flipped view coordinates) and the capture texture itself.
      for surface in surfaces {
        let snapshot = surface.snapshot
        encoder.setViewport(
          MTLViewport(
            originX: Double(snapshot.origin.x),
            originY: Double(snapshot.origin.y),
            width: Double(snapshot.size.width),
            height: Double(snapshot.size.height),
            znear: 0,
            zfar: 1
          )
        )
        encoder.setScissorRect(
          MTLScissorRect(
            x: snapshot.origin.x,
            y: snapshot.origin.y,
            width: snapshot.size.width,
            height: snapshot.size.height
          )
        )
        encoder.setFragmentTexture(surface.texture, index: 0)
        encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
      }
      if let overlayTexture {
        encoder.setViewport(
          MTLViewport(
            originX: 0,
            originY: 0,
            width: Double(targetTexture.width),
            height: Double(targetTexture.height),
            znear: 0,
            zfar: 1
          )
        )
        encoder.setScissorRect(
          MTLScissorRect(
            x: 0,
            y: 0,
            width: targetTexture.width,
            height: targetTexture.height
          )
        )
        encoder.setFragmentTexture(overlayTexture, index: 0)
        encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
      }
      encoder.endEncoding()
    }

    private func surfaceTexture(
      for snapshot: GpuSurfaceSnapshot,
      device: MTLDevice
    ) -> MTLTexture {
      let identifier = ObjectIdentifier(snapshot.surface)
      if let entry = surfaceTextures[identifier],
        entry.size.width == snapshot.size.width,
        entry.size.height == snapshot.size.height,
        entry.pixelFormat == snapshot.pixelFormat
      {
        return entry.texture
      }
      let descriptor = MTLTextureDescriptor.texture2DDescriptor(
        pixelFormat: snapshot.pixelFormat,
        width: snapshot.size.width,
        height: snapshot.size.height,
        mipmapped: false
      )
      descriptor.usage = [.shaderRead, .renderTarget]
      descriptor.storageMode = .private
      guard let texture = device.makeTexture(descriptor: descriptor) else {
        fatalError("Failed to create a GpuSurface capture texture")
      }
      surfaceTextures[identifier] = SurfaceTextureEntry(
        texture: texture,
        size: snapshot.size,
        pixelFormat: snapshot.pixelFormat
      )
      return texture
    }

    private func renderPipeline(
      device: MTLDevice,
      format: MTLPixelFormat
    ) -> MTLRenderPipelineState {
      if let pipeline, pipelineFormat == format {
        return pipeline
      }
      let library: MTLLibrary
      do {
        library = try device.makeDefaultLibrary(bundle: Bundle.module)
      } catch {
        fatalError("Failed to load WaterUI's precompiled Metal library: \(error)")
      }
      guard let vertex = library.makeFunction(name: "capture_composite_vertex"),
        let fragment = library.makeFunction(name: "capture_composite_fragment")
      else {
        fatalError("CaptureComposite.metal is missing required entry points")
      }
      let descriptor = MTLRenderPipelineDescriptor()
      descriptor.vertexFunction = vertex
      descriptor.fragmentFunction = fragment
      descriptor.colorAttachments[0].pixelFormat = format
      descriptor.colorAttachments[0].isBlendingEnabled = true
      descriptor.colorAttachments[0].rgbBlendOperation = .add
      descriptor.colorAttachments[0].alphaBlendOperation = .add
      descriptor.colorAttachments[0].sourceRGBBlendFactor = .one
      descriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
      descriptor.colorAttachments[0].sourceAlphaBlendFactor = .one
      descriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha
      do {
        let compiled = try device.makeRenderPipelineState(descriptor: descriptor)
        pipeline = compiled
        pipelineFormat = format
        return compiled
      } catch {
        fatalError("Failed to compile the Metal capture composition pipeline: \(error)")
      }
    }

    private func compositeSampler(device: MTLDevice) -> MTLSamplerState {
      if let sampler { return sampler }
      let descriptor = MTLSamplerDescriptor()
      descriptor.minFilter = .linear
      descriptor.magFilter = .linear
      descriptor.sAddressMode = .clampToEdge
      descriptor.tAddressMode = .clampToEdge
      guard let created = device.makeSamplerState(descriptor: descriptor) else {
        fatalError("Failed to create the Metal capture composition sampler")
      }
      sampler = created
      return created
    }
  }

  private let contentView: PlatformView
  @MainActor var onRedraw: (() -> Void)?

  @MainActor private var activeGpuSurfaces: [ObjectIdentifier: WuiGpuSurface] = [:]
  @MainActor private var captureRenderer: CARenderer?
  @MainActor private var nativeCaptureQueue: MTLCommandQueue?
  @MainActor private var nativeCaptureDevice: MTLDevice?
  @MainActor private var nativeCapturePixelFormat: MTLPixelFormat = .invalid
  @MainActor private var overlayTexture: MTLTexture?

  private let compositor = Compositor()

  @MainActor
  init(contentView: PlatformView) {
    self.contentView = contentView
  }

  @MainActor
  func capture(
    into targetTexture: MTLTexture,
    completion: @escaping WuiMetalViewCaptureCompletion
  ) {
    let preparation = prepareCapture(into: targetTexture)
    Logger.graphics.debug(
      """
      Capture started: \(targetTexture.width, privacy: .public)\
      x\(targetTexture.height, privacy: .public), \
      gpuSurfaces=\(preparation.snapshots.count, privacy: .public)
      """
    )
    let compositor = self.compositor
    preparation.nativeCaptureFence.addCompletedHandler { [weak self] commandBuffer in
      guard commandBuffer.status == .completed else {
        fatalError(
          "Native Metal capture failed: \(String(describing: commandBuffer.error))"
        )
      }
      guard !preparation.snapshots.isEmpty else {
        DispatchQueue.main.async {
          completion(true)
        }
        return
      }
      compositor.perform { compositor in
        let rendered = compositor.prepareSurfaceTextures(
          snapshots: preparation.snapshots,
          device: preparation.device
        )
        DispatchQueue.main.async {
          MainActor.assumeIsolated {
            // The capture pipeline outlives no one: it is owned by the effect
            // view that requested this frame, so if it is gone the frame has
            // nowhere to land and dropping it is the whole of the work left.
            guard let self else {
              Logger.graphics.debug("Native capture completed after teardown; frame dropped")
              return
            }
            self.submitGpuSurfaces(rendered, preparation: preparation, completion: completion)
          }
        }
      }
    }
  }

  @MainActor
  func shutdown() {
    Logger.graphics.debug(
      "Capture pipeline shutting down, externalSurfaces=\(self.activeGpuSurfaces.count, privacy: .public)"
    )
    for surface in activeGpuSurfaces.values {
      surface.endExternalRendering(resumingPresentation: true)
    }
    activeGpuSurfaces.removeAll()
    overlayTexture = nil
    captureRenderer = nil
    nativeCaptureQueue = nil
    nativeCaptureDevice = nil
    nativeCapturePixelFormat = .invalid
    compositor.discardResources()
  }

  @MainActor deinit {
    precondition(
      activeGpuSurfaces.isEmpty,
      "WuiMetalViewCapture must be shut down before deinitialization"
    )
  }

  @MainActor
  private func prepareCapture(into targetTexture: MTLTexture) -> Preparation {
    prepareCaptureView(contentView)
    let layer = resolveCaptureLayer(from: contentView)
    let geometry = captureGeometry(for: targetTexture)
    let snapshots = collectGpuSurfaceSnapshots(
      targetTexture: targetTexture,
      geometry: geometry
    )
    updateExternalGpuSurfaces(snapshots)

    let nativeTarget: MTLTexture
    if snapshots.isEmpty {
      nativeTarget = targetTexture
    } else {
      nativeTarget = ensureOverlayTexture(
        device: targetTexture.device,
        pixelFormat: targetTexture.pixelFormat,
        width: targetTexture.width,
        height: targetTexture.height
      )
    }

    let wasHidden = layer.isHidden
    setLayer(layer, hidden: false)
    defer { setLayer(layer, hidden: wasHidden) }

    let nativeCaptureFence: MTLCommandBuffer
    if snapshots.isEmpty {
      nativeCaptureFence = renderNativeLayer(layer, into: nativeTarget, geometry: geometry)
    } else {
      for snapshot in snapshots {
        snapshot.surface.beginCaptureSuppression()
      }
      defer {
        for snapshot in snapshots.reversed() {
          snapshot.surface.endCaptureSuppression()
        }
      }
      nativeCaptureFence = renderNativeLayer(layer, into: nativeTarget, geometry: geometry)
    }

    return Preparation(
      device: targetTexture.device,
      targetTexture: targetTexture,
      overlayTexture: snapshots.isEmpty ? nil : nativeTarget,
      nativeCaptureFence: nativeCaptureFence,
      snapshots: snapshots
    )
  }

  @MainActor
  private func captureGeometry(for targetTexture: MTLTexture) -> CaptureGeometry {
    precondition(
      contentView.bounds.width > 0 && contentView.bounds.height > 0,
      "WaterUI capture content must have non-zero bounds"
    )
    return CaptureGeometry(
      scaleX: CGFloat(targetTexture.width) / contentView.bounds.width,
      scaleY: CGFloat(targetTexture.height) / contentView.bounds.height,
      targetHeight: CGFloat(targetTexture.height)
    )
  }

  @MainActor
  private func prepareCaptureView(_ view: PlatformView) {
    #if canImport(AppKit)
      ensureLayerBacked(view)
      view.needsLayout = true
      view.layoutSubtreeIfNeeded()
      view.layer?.setNeedsLayout()
      view.layer?.layoutIfNeeded()
      view.needsDisplay = true
      view.displayIfNeeded()
    #elseif canImport(UIKit)
      view.setNeedsLayout()
      view.layoutIfNeeded()
    #endif
  }

  #if canImport(AppKit)
    @MainActor
    private func ensureLayerBacked(_ view: PlatformView) {
      if view.layer == nil {
        view.wantsLayer = true
      }
      for subview in view.subviews {
        ensureLayerBacked(subview)
      }
    }
  #endif

  @MainActor
  private func resolveCaptureLayer(from view: PlatformView) -> CALayer {
    if let component = view as? WuiComponent,
      isMetadataComponent(component),
      let contentSubview = view.subviews.first(where: { $0 is WuiComponent })
    {
      return resolveCaptureLayer(from: contentSubview)
    }

    #if canImport(AppKit)
      ensureLayerBacked(view)
    #endif
    #if canImport(UIKit)
      return view.layer
    #elseif canImport(AppKit)
      guard let layer = view.layer else {
        fatalError("WaterUI capture view is not layer-backed")
      }
      return layer
    #endif
  }

  @MainActor
  private func setLayer(_ layer: CALayer, hidden: Bool) {
    guard layer.isHidden != hidden else { return }
    CATransaction.begin()
    CATransaction.setDisableActions(true)
    layer.isHidden = hidden
    CATransaction.commit()
  }

  /// Runs `body` with `layer` mapped onto the pixel-sized capture destination.
  ///
  /// `CARenderer` takes its destination rectangle in pixels but reads the layer
  /// tree in points, and its destination row index grows with layer-space y —
  /// the opposite of every other texture in this pipeline. One transform fixes
  /// both: scale by the backing factor, mirror vertically, and translate the
  /// mirrored tree back down onto the destination.
  ///
  /// Deriving the position from the layer's own position keeps the mapping
  /// independent of its anchor point (AppKit uses `(0, 0)`, UIKit `(0.5, 0.5)`),
  /// and concatenating onto the existing transform keeps any transform the view
  /// already carries.
  ///
  /// The mutation is restored before this call returns, so no Core Animation
  /// commit ever sees the capture geometry — the same contract the surrounding
  /// `isHidden` dance relies on.
  @MainActor
  private func withCaptureTransform<T>(
    _ layer: CALayer,
    geometry: CaptureGeometry,
    _ body: () -> T
  ) -> T {
    let savedTransform = layer.transform
    let savedPosition = layer.position

    CATransaction.begin()
    CATransaction.setDisableActions(true)
    layer.transform = CATransform3DConcat(
      savedTransform,
      CATransform3DMakeScale(geometry.scaleX, -geometry.scaleY, 1)
    )
    layer.position = CGPoint(
      x: savedPosition.x * geometry.scaleX,
      y: geometry.targetHeight - savedPosition.y * geometry.scaleY
    )
    CATransaction.commit()

    defer {
      CATransaction.begin()
      CATransaction.setDisableActions(true)
      layer.transform = savedTransform
      layer.position = savedPosition
      CATransaction.commit()
    }

    return body()
  }

  @MainActor
  private func renderNativeLayer(
    _ layer: CALayer,
    into texture: MTLTexture,
    geometry: CaptureGeometry
  ) -> MTLCommandBuffer {
    let device = texture.device
    if nativeCaptureQueue == nil || nativeCaptureDevice !== device
      || nativeCapturePixelFormat != texture.pixelFormat
    {
      nativeCaptureDevice = device
      nativeCapturePixelFormat = texture.pixelFormat
      nativeCaptureQueue = device.makeCommandQueue()
      captureRenderer = nil
    }
    guard let queue = nativeCaptureQueue else {
      fatalError("Failed to create the native Metal capture command queue")
    }

    let colorSpace: CGColorSpace? =
      texture.pixelFormat == .rgba16Float
      ? CGColorSpace(name: CGColorSpace.extendedLinearSRGB)
      : CGColorSpace(name: CGColorSpace.sRGB)
    let renderer: CARenderer
    if let existing = captureRenderer {
      renderer = existing
      renderer.setDestination(texture)
    } else {
      renderer = CARenderer(
        mtlTexture: texture,
        options: [
          kCARendererColorSpace as String: colorSpace as Any,
          kCARendererMetalCommandQueue as String: queue,
        ]
      )
      captureRenderer = renderer
    }

    renderer.layer = layer
    // The destination rectangle is in pixels; `withCaptureTransform` brings the
    // point-space layer tree into that same space.
    renderer.bounds = CGRect(x: 0, y: 0, width: texture.width, height: texture.height)
    withCaptureTransform(layer, geometry: geometry) {
      let time = CACurrentMediaTime()
      renderer.beginFrame(atTime: time, timeStamp: nil)
      renderer.addUpdate(renderer.bounds)
      renderer.render()
      renderer.endFrame()
    }

    // `CARenderer` encodes its work onto `queue` during `render()` but hands
    // back no completion signal, so an empty command buffer committed right
    // afterwards stands in as the fence: command buffers in one Metal queue
    // execute in commit order, so this one completes only once the capture has.
    // That is load-bearing, and it holds because `nativeCaptureQueue` is created
    // here, handed to nothing but this `CARenderer`, and only ever driven from
    // the main actor — never share this queue with other work.
    guard let commandBuffer = queue.makeCommandBuffer() else {
      fatalError("Failed to create the native Metal capture command buffer")
    }
    commandBuffer.commit()
    return commandBuffer
  }

  @MainActor
  private func collectGpuSurfaceSnapshots(
    targetTexture: MTLTexture,
    geometry: CaptureGeometry
  ) -> [GpuSurfaceSnapshot] {
    var snapshots: [GpuSurfaceSnapshot] = []
    collectGpuSurfaceSnapshots(
      from: contentView,
      into: &snapshots,
      geometry: geometry,
      targetWidth: targetTexture.width,
      targetHeight: targetTexture.height
    )
    return snapshots
  }

  @MainActor
  private func collectGpuSurfaceSnapshots(
    from view: PlatformView,
    into snapshots: inout [GpuSurfaceSnapshot],
    geometry: CaptureGeometry,
    targetWidth: Int,
    targetHeight: Int
  ) {
    if let surface = view as? WuiGpuSurface {
      let rect = surface.convert(surface.bounds, to: contentView)
      let originX = max(0, Int((rect.minX * geometry.scaleX).rounded(.down)))
      let originY = max(0, Int((rect.minY * geometry.scaleY).rounded(.down)))
      let width = min(
        Int((rect.width * geometry.scaleX).rounded(.up)),
        targetWidth - originX
      )
      let height = min(
        Int((rect.height * geometry.scaleY).rounded(.up)),
        targetHeight - originY
      )
      if width > 0, height > 0 {
        snapshots.append(
          GpuSurfaceSnapshot(
            surface: surface,
            origin: MTLOrigin(x: originX, y: originY, z: 0),
            size: MTLSize(width: width, height: height, depth: 1),
            pixelFormat: surface.capturePixelFormat
          )
        )
      }
      return
    }

    for subview in view.subviews where subview is WuiComponent {
      collectGpuSurfaceSnapshots(
        from: subview,
        into: &snapshots,
        geometry: geometry,
        targetWidth: targetWidth,
        targetHeight: targetHeight
      )
    }
  }

  @MainActor
  private func updateExternalGpuSurfaces(_ snapshots: [GpuSurfaceSnapshot]) {
    guard let onRedraw else {
      fatalError("WuiMetalViewCapture redraw handler was not installed")
    }
    var next: [ObjectIdentifier: WuiGpuSurface] = [:]
    for snapshot in snapshots {
      next[ObjectIdentifier(snapshot.surface)] = snapshot.surface
    }

    for (identifier, surface) in activeGpuSurfaces where next[identifier] == nil {
      Logger.graphics.debug("GpuSurface left the captured subtree; presentation resumed")
      surface.endExternalRendering(resumingPresentation: true)
    }
    for (identifier, surface) in next where activeGpuSurfaces[identifier] == nil {
      Logger.graphics.debug("GpuSurface joined the captured subtree; presentation suspended")
      surface.beginExternalRendering(onRedraw: onRedraw)
    }
    activeGpuSurfaces = next
  }

  @MainActor
  private func ensureOverlayTexture(
    device: MTLDevice,
    pixelFormat: MTLPixelFormat,
    width: Int,
    height: Int
  ) -> MTLTexture {
    if let overlayTexture,
      overlayTexture.width == width,
      overlayTexture.height == height,
      overlayTexture.pixelFormat == pixelFormat
    {
      return overlayTexture
    }
    let descriptor = MTLTextureDescriptor.texture2DDescriptor(
      pixelFormat: pixelFormat,
      width: width,
      height: height,
      mipmapped: false
    )
    descriptor.usage = [.shaderRead, .renderTarget]
    descriptor.storageMode = .private
    guard let texture = device.makeTexture(descriptor: descriptor) else {
      fatalError("Failed to create the native capture overlay texture")
    }
    overlayTexture = texture
    return texture
  }

  @MainActor
  private func submitGpuSurfaces(
    _ rendered: [RenderedGpuSurface],
    preparation: Preparation,
    completion: @escaping WuiMetalViewCaptureCompletion
  ) {
    for item in rendered {
      guard item.snapshot.surface.prepareExternalRender(texture: item.texture) else {
        Logger.graphics.debug("GpuSurface setup still pending; capture frame deferred")
        completion(false)
        return
      }
    }

    let batch = WuiMetalFenceBatch(count: rendered.count) { [compositor = self.compositor] in
      Self.compose(
        compositor,
        preparation: preparation,
        rendered: rendered,
        completion: completion
      )
    }
    for item in rendered {
      item.snapshot.surface.renderPreparedExternalTexture(
        texture: item.texture,
        width: UInt32(item.snapshot.size.width),
        height: UInt32(item.snapshot.size.height)
      ) {
        batch.completeOne()
      }
    }
  }

  /// Encodes the final composition off the main thread.
  ///
  /// Deliberately free of `self`: once the surfaces have rendered, the pass
  /// depends on nothing but the compositor and the prepared textures, so it
  /// completes correctly even if the owning view is torn down meanwhile.
  private static func compose(
    _ compositor: Compositor,
    preparation: Preparation,
    rendered: [RenderedGpuSurface],
    completion: @escaping WuiMetalViewCaptureCompletion
  ) {
    compositor.perform { compositor in
      let commandBuffer = compositor.makeCommandBuffer(device: preparation.device)
      compositor.encodeComposition(
        surfaces: rendered,
        overlayTexture: preparation.overlayTexture,
        targetTexture: preparation.targetTexture,
        commandBuffer: commandBuffer,
        device: preparation.device
      )
      commandBuffer.addCompletedHandler { commandBuffer in
        guard commandBuffer.status == .completed else {
          fatalError(
            "Metal view composition failed: \(String(describing: commandBuffer.error))"
          )
        }
        Logger.graphics.debug("Capture composition complete")
        DispatchQueue.main.async {
          completion(true)
        }
      }
      commandBuffer.commit()
    }
  }
}
#endif  // !WATERUI_NO_GPU