From b4f57c8c05113d9d431af8a0294311277fb2eaa8 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 23 Mar 2026 02:09:13 +0800 Subject: [PATCH 01/19] [NFC] Merge EmptyViewRendererHost into ViewRendererHost file --- .../View/Graph/EmptyViewRendererHost.swift | 41 ------------------- .../View/Graph/ViewRendererHost.swift | 37 +++++++++++++++++ 2 files changed, 37 insertions(+), 41 deletions(-) delete mode 100644 Sources/OpenSwiftUICore/View/Graph/EmptyViewRendererHost.swift diff --git a/Sources/OpenSwiftUICore/View/Graph/EmptyViewRendererHost.swift b/Sources/OpenSwiftUICore/View/Graph/EmptyViewRendererHost.swift deleted file mode 100644 index b21f12920..000000000 --- a/Sources/OpenSwiftUICore/View/Graph/EmptyViewRendererHost.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// EmptyViewRendererHost.swift -// OpenSwiftUICore -// -// Audited for 6.5.4 -// Status: Complete - -final package class EmptyViewRendererHost: ViewRendererHost { - package let viewGraph: ViewGraph - - package var propertiesNeedingUpdate: ViewRendererHostProperties = [] - - package var renderingPhase: ViewRenderingPhase = .none - - package var externalUpdateCount: Int = .zero - - package var currentTimestamp: Time = .zero - - package init(environment: EnvironmentValues = EnvironmentValues()) { - Update.begin() - viewGraph = ViewGraph(rootViewType: EmptyView.self, requestedOutputs: []) - viewGraph.setEnvironment(environment) - viewGraph.setRootView(EmptyView()) - initializeViewGraph() - Update.end() - } - - package func requestUpdate(after delay: Double) {} - - package func updateRootView() {} - - package func updateEnvironment() {} - - package func updateSize() {} - - package func updateSafeArea() {} - - package func updateContainerSize() {} - - package func forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void) {} -} diff --git a/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift b/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift index 285c525e9..994318ddd 100644 --- a/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift +++ b/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift @@ -572,3 +572,40 @@ extension ViewRendererHost { viewGraph.graph.archiveJSON(name: name) } } + +// MARK: - EmptyViewRendererHost [6.5.4] + +final package class EmptyViewRendererHost: ViewRendererHost { + package let viewGraph: ViewGraph + + package var propertiesNeedingUpdate: ViewRendererHostProperties = [] + + package var renderingPhase: ViewRenderingPhase = .none + + package var externalUpdateCount: Int = .zero + + package var currentTimestamp: Time = .zero + + package init(environment: EnvironmentValues = EnvironmentValues()) { + Update.begin() + viewGraph = ViewGraph(rootViewType: EmptyView.self, requestedOutputs: []) + viewGraph.setEnvironment(environment) + viewGraph.setRootView(EmptyView()) + initializeViewGraph() + Update.end() + } + + package func requestUpdate(after delay: Double) {} + + package func updateRootView() {} + + package func updateEnvironment() {} + + package func updateSize() {} + + package func updateSafeArea() {} + + package func updateContainerSize() {} + + package func forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void) {} +} From 7956c2e45dfab635b2361f24e6ed856792a49b63 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 23 Mar 2026 02:54:28 +0800 Subject: [PATCH 02/19] Update DisplayListViewRenderer --- .../DisplayList/DisplayListViewRenderer.swift | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift index a8cc2c6ea..0f2fd99ff 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift @@ -2,31 +2,49 @@ // DisplayListViewRenderer.swift // OpenSwiftUICore // -// Audited for 6.0.87 -// Status: Blocked by ViewUpdater and ViewRasterizer +// Audited for 6.5.4 +// Status: Blocked by ViewRasterizer // ID: 21FFA3C7D88AC65BB559906758271BFC (SwiftUICore) package import Foundation protocol ViewRendererBase: AnyObject { var platform: DisplayList.ViewUpdater.Platform { get } + var exportedObject: AnyObject? { get } - func render(rootView: AnyObject, from list: DisplayList, time: Time, version: DisplayList.Version, maxVersion: DisplayList.Version, environment: DisplayList.ViewRenderer.Environment) -> Time - func renderAsync(to list: DisplayList, time: Time, targetTimestamp: Time?, version: DisplayList.Version, maxVersion: DisplayList.Version) -> Time? + + func render( + rootView: AnyObject, + from list: DisplayList, + time: Time, + version: DisplayList.Version, + maxVersion: DisplayList.Version, + environment: DisplayList.ViewRenderer.Environment + ) -> Time + + func renderAsync( + to list: DisplayList, + time: Time, + targetTimestamp: Time?, + version: DisplayList.Version, + maxVersion: DisplayList.Version + ) -> Time? + func destroy(rootView: AnyObject) + var viewCacheIsEmpty: Bool { get } } @_spi(ForOpenSwiftUIOnly) +@available(OpenSwiftUI_v6_0, *) extension DisplayList { final public class ViewRenderer { package struct Environment: Equatable { package var contentsScale: CGFloat - #if os(macOS) package var opaqueBackground: Bool = false #endif - + package static let invalid = Environment(contentsScale: .zero) package init(contentsScale: CGFloat) { @@ -59,7 +77,7 @@ extension DisplayList { private var state: State = .none - private var renderer: (any ViewRendererBase)? = nil + private var renderer: (any ViewRendererBase)? private var configChanged: Bool = true @@ -72,11 +90,11 @@ extension DisplayList { return renderer! } configChanged = false - let renderStateMatchCheck = switch configuration.renderer { + let isValid = switch configuration.renderer { case .default: state == .updating case .rasterized: state == .rasterizing } - if !renderStateMatchCheck { + if !isValid { if let renderer { renderer.destroy(rootView: rootView) } @@ -95,12 +113,16 @@ extension DisplayList { } else { switch configuration.renderer { case .default: - let updater = ViewUpdater() - // TODO: ViewUpdater + let updater = ViewUpdater(platform: platform, host: host) renderer = updater state = .updating case let .rasterized(options): - let rasterizer = ViewRasterizer(platform: platform, host: host, rootView: rootView, options: options) + let rasterizer = ViewRasterizer( + platform: platform, + host: host, + rootView: rootView, + options: options + ) renderer = rasterizer state = .rasterizing } From 5b9e7a1d4acd25180277ac1c8b0703f868f79f51 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 23 Mar 2026 03:35:45 +0800 Subject: [PATCH 03/19] Optimize ViewRenderer code --- .../DisplayList/DisplayListViewRenderer.swift | 77 +++++++++++++------ .../View/Graph/ViewRendererHost.swift | 23 ------ 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift index 0f2fd99ff..b736bcd2b 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift @@ -135,9 +135,9 @@ extension DisplayList { return renderer.exportedObject } - #if canImport(Darwin) && _OPENSWIFTUI_SWIFTUI_RENDER + #if canImport(SwiftUI, _underlyingVersion: 6.5.4) && _OPENSWIFTUI_SWIFTUI_RENDER @_silgen_name("OpenSwiftUITestStub_DisplayListViewRendererRenderRootView") - package func swiftUI_render( + private func swiftUI_render( rootView: AnyObject, from list: DisplayList, time: Time, @@ -146,16 +146,6 @@ extension DisplayList { maxVersion: DisplayList.Version, environment: DisplayList.ViewRenderer.Environment ) -> Time - - @_silgen_name("OpenSwiftUITestStub_DisplayListViewRendererRenderAsync") - package func swiftUI_renderAsync( - to list: DisplayList, - time: Time, - nextTime: Time, - targetTimestamp: Time?, - version: DisplayList.Version, - maxVersion: DisplayList.Version - ) -> Time? #endif package func render( @@ -167,13 +157,43 @@ extension DisplayList { maxVersion: DisplayList.Version, environment: DisplayList.ViewRenderer.Environment ) -> Time { + #if canImport(SwiftUI, _underlyingVersion: 6.5.4) && _OPENSWIFTUI_SWIFTUI_RENDER + swiftUI_render( + rootView: rootView, + from: list, + time: time, + nextTime: nextTime, + version: version, + maxVersion: maxVersion, + environment: environment + ) + #else let renderer = updateRenderer(rootView: rootView) - let result = renderer.render(rootView: rootView, from: list, time: time, version: version, maxVersion: maxVersion, environment: environment) - let interval = min(nextTime, result) - time - let maxInterval = max(interval, configuration.minFrameInterval) - return time + maxInterval + let nextUpdate = renderer.render( + rootView: rootView, + from: list, + time: time, + version: version, + maxVersion: maxVersion, + environment: environment + ) + let interval = max(min(nextTime, nextUpdate) - time, configuration.minFrameInterval) + return time + interval + #endif } - + + #if canImport(SwiftUI, _underlyingVersion: 6.5.4) && _OPENSWIFTUI_SWIFTUI_RENDER + @_silgen_name("OpenSwiftUITestStub_DisplayListViewRendererRenderAsync") + private func swiftUI_renderAsync( + to list: DisplayList, + time: Time, + nextTime: Time, + targetTimestamp: Time?, + version: DisplayList.Version, + maxVersion: DisplayList.Version + ) -> Time? + #endif + package func renderAsync( to list: DisplayList, time: Time, @@ -182,17 +202,26 @@ extension DisplayList { version: DisplayList.Version, maxVersion: DisplayList.Version ) -> Time? { + #if canImport(SwiftUI, _underlyingVersion: 6.5.4) && _OPENSWIFTUI_SWIFTUI_RENDER + swiftUI_renderAsync( + to: list, + time: time, + nextTime: nextTime, + targetTimestamp: targetTimestamp, + version: version, + maxVersion: maxVersion + ) + #else guard !configChanged, let renderer else { return nil } - let result = renderer.renderAsync(to: list, time: time, targetTimestamp: targetTimestamp, version: version, maxVersion: maxVersion) - if let result { - let interval = min(nextTime, result) - time - let maxInterval = max(interval, configuration.minFrameInterval) - return time + maxInterval - } else { - return nil + let nextUpdate = renderer.renderAsync(to: list, time: time, targetTimestamp: targetTimestamp, version: version, maxVersion: maxVersion) + guard let nextUpdate else { + return nextUpdate } + let interval = max(min(nextTime, result) - time, configuration.minFrameInterval) + return time + interval + #endif } package var viewCacheIsEmpty: Bool { diff --git a/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift b/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift index 994318ddd..91049321c 100644 --- a/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift +++ b/Sources/OpenSwiftUICore/View/Graph/ViewRendererHost.swift @@ -312,17 +312,6 @@ extension ViewRendererHost { let rootView = delegate.renderingRootView // TODO: CustomEventTrace return delegate.withMainThreadRender(wasAsync: false) { - #if canImport(SwiftUI, _underlyingVersion: 6.0.87) && _OPENSWIFTUI_SWIFTUI_RENDER - renderer.swiftUI_render( - rootView: self, - from: list, - time: time, - nextTime: nextTime, - version: version, - maxVersion: maxVersion, - environment: environment - ) - #else renderer.render( rootView: self, from: list, @@ -332,21 +321,10 @@ extension ViewRendererHost { maxVersion: maxVersion, environment: environment ) - #endif } } if asynchronously { // TODO: CustomEventTrace - #if canImport(SwiftUI, _underlyingVersion: 6.0.87) && _OPENSWIFTUI_SWIFTUI_RENDER - let renderedTime = renderer.swiftUI_renderAsync( - to: list, - time: time, - nextTime: nextTime, - targetTimestamp: targetTimestamp, - version: version, - maxVersion: maxVersion - ) - #else let renderedTime = renderer.renderAsync( to: list, time: time, @@ -355,7 +333,6 @@ extension ViewRendererHost { version: version, maxVersion: maxVersion ) - #endif if let renderedTime { return renderedTime } else { From 4d1929500d323b6f835aa3da9d0b1e903aedca0e Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 24 Mar 2026 02:15:20 +0800 Subject: [PATCH 04/19] Implement ViewRasterizer --- .../DisplayList/DisplayListViewPlatform.swift | 23 +++ .../DisplayList/DisplayListViewRenderer.swift | 165 +++++++++++++++--- 2 files changed, 164 insertions(+), 24 deletions(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift index cc2c53913..0f13b0591 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift @@ -109,6 +109,29 @@ extension DisplayList.ViewUpdater.Platform { CoreViewLayer(system: viewSystem, view: view) } #endif + + func updateDrawingView( + _ drawingView: inout AnyObject, + options: RasterizationOptions, + contentsScale: CGFloat + ) -> any PlatformDrawable { + var drawable = (drawingView as? PlatformDrawable) ?? definition.makeDrawingView(options: .init(base: options)) + let oldOption = drawable.options.base + if options != oldOption { + if oldOption.flags.symmetricDifference(options.flags).contains(.isAccelerated) { + drawable = definition.makeDrawingView(options: .init(base: options)) + } else { + drawable.options.base = options + } + } + drawable.setContentsScale(contentsScale) + drawingView = drawable + return drawable + } + + // TODO: + // private func updateDrawingView + // private func updateDrawingViewAsync } extension DisplayList.GraphicsRenderer { diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift index b736bcd2b..5afc77834 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift @@ -3,9 +3,10 @@ // OpenSwiftUICore // // Audited for 6.5.4 -// Status: Blocked by ViewRasterizer +// Status: Complete // ID: 21FFA3C7D88AC65BB559906758271BFC (SwiftUICore) +import OpenSwiftUI_SPI package import Foundation protocol ViewRendererBase: AnyObject { @@ -50,7 +51,7 @@ extension DisplayList { package init(contentsScale: CGFloat) { self.contentsScale = contentsScale } - + #if os(macOS) package init(contentsScale: CGFloat, opaqueBackground: Bool) { self.contentsScale = contentsScale @@ -58,7 +59,7 @@ extension DisplayList { } #endif } - + let platform: DisplayList.ViewUpdater.Platform package var configuration: _RendererConfiguration = .init() @@ -84,7 +85,7 @@ extension DisplayList { package init(platform: DisplayList.ViewUpdater.Platform) { self.platform = platform } - + private func updateRenderer(rootView: AnyObject) -> any ViewRendererBase { guard configChanged else { return renderer! @@ -129,7 +130,7 @@ extension DisplayList { } return renderer! } - + package func exportedObject(rootView: AnyObject) -> AnyObject? { let renderer = updateRenderer(rootView: rootView) return renderer.exportedObject @@ -223,7 +224,7 @@ extension DisplayList { return time + interval #endif } - + package var viewCacheIsEmpty: Bool { renderer?.viewCacheIsEmpty ?? true } @@ -237,42 +238,158 @@ private var printTree: Bool? extension DisplayList { private final class ViewRasterizer: ViewRendererBase { let platform: DisplayList.ViewUpdater.Platform - weak var host: ViewRendererHost? - var drawingView: AnyObject? + weak var host: (any ViewRendererHost)? = nil + var drawingView: AnyObject? = nil var options: _RendererConfiguration.RasterizationOptions let renderer: DisplayList.GraphicsRenderer - var seed: DisplayList.Seed - var lastContentsScale: CGFloat + var seed: DisplayList.Seed = .init() + var lastContentsScale: CGFloat = .zero - init(platform: DisplayList.ViewUpdater.Platform, host: ViewRendererHost?, rootView: AnyObject, options: _RendererConfiguration.RasterizationOptions) { - _openSwiftUIBaseClassAbstractMethod() + init( + platform: DisplayList.ViewUpdater.Platform, + host: (any ViewRendererHost)?, + rootView: AnyObject, + options: _RendererConfiguration.RasterizationOptions + ) { + self.platform = platform + self.host = host + self.options = options + self.renderer = DisplayList.GraphicsRenderer(platformViewMode: options.drawsPlatformViews ? .rendered(update: true) : .unsupported) + self.drawingView = platform.definition.makeDrawingView(options: .init(base: .init(options))) + #if canImport(Darwin) + CoreViewAddSubview( + system: platform.viewSystem, + parent: rootView, + child: drawingView!, + index: 0 + ) + #else + _openSwiftUIPlatformUnimplementedWarning() + #endif } var exportedObject: AnyObject? { - platform.definition.getRBLayer(drawingView: drawingView!) + let drawingView = drawingView! + let rbLayer = platform.definition + .getRBLayer(drawingView: drawingView) + return rbLayer } - func render(rootView: AnyObject, from list: DisplayList, time: Time, version: DisplayList.Version, maxVersion: DisplayList.Version, environment: DisplayList.ViewRenderer.Environment) -> Time { - // _openSwiftUIUnimplementedFailure() - if printTree == nil { - printTree = ProcessEnvironment.bool(forKey: "OPENSWIFTUI_PRINT_TREE") + func render( + rootView: AnyObject, + from list: DisplayList, + time: Time, + version: DisplayList.Version, + maxVersion: DisplayList.Version, + environment: DisplayList.ViewRenderer.Environment + ) -> Time { + let contentsScale = environment.contentsScale + if contentsScale != lastContentsScale { + lastContentsScale = contentsScale + seed = .init() } - if let printTree, printTree { - print("View \(Unmanaged.passUnretained(rootView).toOpaque()) at \(time):\n\(list.description)") + #if canImport(Darwin) + let drawingViewFrame = drawingView!.frame + if let rootViewBounds = rootView.bounds, drawingViewFrame != rootViewBounds { + CoreViewSetFrame( + system: platform.viewSystem, + view: drawingView!, + frame: rootView.bounds! + ) + seed = .init() } - return .zero + #endif + let newSeed = DisplayList.Seed(version) + if newSeed == seed, renderer.nextTime >= time { + return renderer.nextTime + } + let drawable = platform.updateDrawingView( + &drawingView!, + options: .init(options), + contentsScale: lastContentsScale + ) + let content = drawingContent(list: list, time: time) + var result = time + let updated = drawable.update(content: content, required: false) + if updated { + result = .infinity + } + if let host, let observer = host.as(ViewGraphRenderObserver.self) { + observer.didRender() + } + return result } - func renderAsync(to list: DisplayList, time: Time, targetTimestamp: Time?, version: DisplayList.Version, maxVersion: DisplayList.Version) -> Time? { - _openSwiftUIUnimplementedFailure() + private func drawingContent(list: DisplayList, time: Time) -> PlatformDrawableContent { + var content = PlatformDrawableContent() + content.storage = .graphicsCallback { [weak host, renderer] ctx, size in + if printTree == nil { + printTree = ProcessEnvironment.bool(forKey: "OPENSWIFTUI_PRINT_TREE") + } + if let printTree, printTree { + print("View \(Unmanaged.passUnretained(self).toOpaque()) at \(time):\n\(list.description)") + } + renderer.renderDisplayList(list, at: time, in: &ctx) + let duration = renderer.nextTime - time + let delay = max(duration, 1e-6) + if delay != .infinity { + DispatchQueue.main.async { [weak host] in + host?.requestUpdate(after: delay) + } + } + } + return content + } + + func renderAsync( + to list: DisplayList, + time: Time, + targetTimestamp: Time?, + version: DisplayList.Version, + maxVersion: DisplayList.Version + ) -> Time? { + nil } func destroy(rootView: AnyObject) { - _openSwiftUIUnimplementedFailure() + #if canImport(Darwin) + CoreViewRemoveFromSuperview( + system: platform.viewSystem, + view: drawingView! + ) + #else + _openSwiftUIPlatformUnimplementedWarning() + #endif } var viewCacheIsEmpty: Bool { - _openSwiftUIUnimplementedFailure() + true + } + } +} + +// MARK: - RasterizationOptions + _RendererConfiguration.RasterizationOptions + +@available(OpenSwiftUI_v6_0, *) +extension RasterizationOptions { + /// Convert from the public `_RendererConfiguration.RasterizationOptions` + /// to the internal `RasterizationOptions`. + package init(_ options: _RendererConfiguration.RasterizationOptions) { + var flags: RasterizationOptions.Flags = .defaultFlags + if options.isOpaque { + flags.insert(.isOpaque) + } + if options.rendersAsynchronously { + flags.insert(.rendersAsynchronously) + } + if options.prefersDisplayCompositing { + flags.insert(.prefersDisplayCompositing) } + self.init( + colorMode: options.colorMode, + rbColorMode: options.rbColorMode, + flags: flags, + maxDrawableCount: Int8(clamping: options.maxDrawableCount) + ) } } From 3afd6642bb51500ea3edea03de744b1eb3942308 Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 24 Mar 2026 03:27:36 +0800 Subject: [PATCH 05/19] Update RendererConfiguration --- .../DisplayList/DisplayListViewRenderer.swift | 26 -------------- .../Render/RendererConfiguration.swift | 34 +++++++++++++++++++ 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift index 5afc77834..dfda16404 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift @@ -367,29 +367,3 @@ extension DisplayList { } } } - -// MARK: - RasterizationOptions + _RendererConfiguration.RasterizationOptions - -@available(OpenSwiftUI_v6_0, *) -extension RasterizationOptions { - /// Convert from the public `_RendererConfiguration.RasterizationOptions` - /// to the internal `RasterizationOptions`. - package init(_ options: _RendererConfiguration.RasterizationOptions) { - var flags: RasterizationOptions.Flags = .defaultFlags - if options.isOpaque { - flags.insert(.isOpaque) - } - if options.rendersAsynchronously { - flags.insert(.rendersAsynchronously) - } - if options.prefersDisplayCompositing { - flags.insert(.prefersDisplayCompositing) - } - self.init( - colorMode: options.colorMode, - rbColorMode: options.rbColorMode, - flags: flags, - maxDrawableCount: Int8(clamping: options.maxDrawableCount) - ) - } -} diff --git a/Sources/OpenSwiftUICore/Render/RendererConfiguration.swift b/Sources/OpenSwiftUICore/Render/RendererConfiguration.swift index c837130ff..88f55097d 100644 --- a/Sources/OpenSwiftUICore/Render/RendererConfiguration.swift +++ b/Sources/OpenSwiftUICore/Render/RendererConfiguration.swift @@ -5,7 +5,10 @@ // Audited for 6.5.4 // Status: Complete +// MARK: - _RendererConfiguration + /// Renderer configuration for a hosting view. +@available(OpenSwiftUI_v2_0, *) public struct _RendererConfiguration { /// The available renderer kind and their configuration. @@ -37,6 +40,8 @@ public struct _RendererConfiguration { _RendererConfiguration(renderer: .rasterized(options)) } + // MARK: - _RendererConfiguration.RasterizationOptions + /// Options for the `rasterized` renderer. public struct RasterizationOptions { @@ -84,3 +89,32 @@ extension _RendererConfiguration.Renderer: Sendable {} @available(*, unavailable) extension _RendererConfiguration.RasterizationOptions: Sendable {} + +// MARK: - RasterizationOptions + _RendererConfiguration.RasterizationOptions + +extension RasterizationOptions { + + /// Convert from the public `_RendererConfiguration.RasterizationOptions` + /// to the internal `RasterizationOptions`. + package init(_ options: _RendererConfiguration.RasterizationOptions) { + var flags: RasterizationOptions.Flags = .defaultFlags + flags.formUnion(.isAccelerated) + if options.isOpaque { + flags.formUnion(.isOpaque) + } else { + flags.subtract([.isOpaque, .rendersAsynchronously, .prefersDisplayCompositing]) + } + if options.rendersAsynchronously { + flags.formUnion(.rendersAsynchronously) + } + if options.prefersDisplayCompositing { + flags.formUnion(.prefersDisplayCompositing) + } + self.init( + colorMode: options.colorMode, + rbColorMode: options.rbColorMode, + flags: flags, + maxDrawableCount: Int8(truncatingIfNeeded: options.maxDrawableCount) + ) + } +} From dd48a73e1ef9cb1628360ad4816519cac09f978b Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 27 Mar 2026 23:48:33 +0800 Subject: [PATCH 06/19] Add CAPresentationModifier.h --- .../Util/CoreGraphicsShims.swift | 15 ++ .../Shims/QuartzCore/CAPresentationModifier.h | 141 ++++++++++++++++++ .../Shims/QuartzCore/CoreAnimation_Private.h | 10 ++ 3 files changed, 166 insertions(+) create mode 100644 Sources/OpenSwiftUICore/Util/CoreGraphicsShims.swift create mode 100644 Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CAPresentationModifier.h diff --git a/Sources/OpenSwiftUICore/Util/CoreGraphicsShims.swift b/Sources/OpenSwiftUICore/Util/CoreGraphicsShims.swift new file mode 100644 index 000000000..b1c2958da --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/CoreGraphicsShims.swift @@ -0,0 +1,15 @@ +// +// CoreGraphicsShims.swift +// OpenSwiftUICore + +#if !canImport(CoreGraphics) + +// MARK: - CAPresentationModifierGroup + +open class CAPresentationModifierGroup: NSObject {} + +// MARK: - CAPresentationModifier + +open class CAPresentationModifier: NSObject {} + +#endif diff --git a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CAPresentationModifier.h b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CAPresentationModifier.h new file mode 100644 index 000000000..bb6d068fe --- /dev/null +++ b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CAPresentationModifier.h @@ -0,0 +1,141 @@ +// +// CAPresentationModifier.h +// OpenSwiftUI_SPI +// +// Status: Complete +// Audited for 6.5.4 + +#ifndef CAPresentationModifier_h +#define CAPresentationModifier_h + +#include "OpenSwiftUIBase.h" + +#if __has_include() + +#import + +OPENSWIFTUI_ASSUME_NONNULL_BEGIN + +// MARK: - CAPresentationModifierGroup + +/// Private QuartzCore class that manages a group of presentation modifiers +/// sharing a single shared-memory region for efficient batch updates. +/// +/// Available since iOS 12.4. Used by SwiftUI for async rendering and by +/// WebKit for off-main-thread animation updates. +/// +/// Source: WebKit QuartzCoreSPI.h +@interface CAPresentationModifierGroup : NSObject + +/// Create a group with a fixed capacity for modifier slots. ++ (instancetype)groupWithCapacity:(NSUInteger)capacity; + +/// Flush all pending modifier values to the Render Server via atomic signal. +/// Safe to call from any thread. +- (void)flush; + +/// Flush with a target timestamp for frame pacing. +- (void)flushWithTargetTime:(double)targetTime; + +/// Flush locally (copy pending → current buffer) without notifying Render Server. +- (void)flushLocally; + +/// Flush locally with target timestamp. +- (void)flushLocallyWithTargetTime:(double)targetTime; + +/// Flush via CA::Transaction path. Must be called on the main thread. +- (void)flushWithTransaction; + +/// Flush via CA::Transaction with target timestamp. +- (void)flushWithTransactionAndTargetTime:(double)targetTime; + +/// Whether the group updates asynchronously (controls shmem header bit 30). +@property (nonatomic) BOOL updatesAsynchronously; + +/// Number of modifiers currently in this group. +@property (nonatomic, readonly) NSUInteger count; + +/// Maximum number of modifiers this group can hold. +@property (nonatomic, readonly) NSUInteger capacity; + +@end + +// MARK: - CAPresentationModifier + +/// Private QuartzCore class that allows direct modification of CALayer +/// properties via shared memory, bypassing CATransaction. +/// +/// Values written via `setValue:` are picked up by the Render Server on +/// the next frame without requiring a main-thread round-trip. +/// +/// Available since iOS 12.4. +/// +/// Source: WebKit QuartzCoreSPI.h +@interface CAPresentationModifier : NSObject + +/// The target CALayer property keyPath (e.g. "opacity", "transform"). +@property (nonatomic, copy, readonly) NSString *keyPath; + +/// Whether this modifier is currently active. +@property (nonatomic, getter=isEnabled) BOOL enabled; + +/// Whether the modifier value is additive (added to model value vs. replacing it). +@property (nonatomic, readonly) BOOL additive; + +/// The group this modifier belongs to (nil for standalone modifiers). +@property (nonatomic, readonly, nullable) CAPresentationModifierGroup *group; + +/// The current value. +@property (nonatomic, strong) id value; + +/// Convenience initializer without a group (creates standalone shared memory). +- (instancetype)initWithKeyPath:(NSString *)keyPath + initialValue:(id)initialValue + additive:(BOOL)additive; + +/// Convenience initializer with a group (allocates a slot in the group's shared memory). +- (instancetype)initWithKeyPath:(NSString *)keyPath + initialValue:(id)initialValue + additive:(BOOL)additive + group:(nullable CAPresentationModifierGroup *)group; + +/// Designated initializer with full parameters. +- (instancetype)initWithKeyPath:(NSString *)keyPath + initialValue:(id)initialValue + initialVelocity:(nullable id)initialVelocity + additive:(BOOL)additive + preferredFrameRateRangeMaximum:(NSInteger)preferredFrameRateRangeMaximum + group:(nullable CAPresentationModifierGroup *)group; + +/// Set a new value (thread-safe, writes to shared memory). +- (void)setValue:(id)value; + +/// Set a new value with velocity for interpolation. +- (void)setValue:(id)value velocity:(nullable id)velocity; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +// MARK: - CALayer (PresentationModifiers) + +@interface CALayer (OpenSwiftUI_PresentationModifiers) + +/// The presentation modifiers currently attached to this layer. +@property (nonatomic, copy, nullable) NSArray *presentationModifiers; + +/// Bind a presentation modifier to this layer. +/// Triggers a CA::Transaction; must be called on the main thread. +- (void)addPresentationModifier:(CAPresentationModifier *)modifier; + +/// Remove a presentation modifier from this layer. +/// Triggers a CA::Transaction; must be called on the main thread. +- (void)removePresentationModifier:(CAPresentationModifier *)modifier; + +@end + +OPENSWIFTUI_ASSUME_NONNULL_END + +#endif /* */ + +#endif /* CAPresentationModifier_h */ diff --git a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CoreAnimation_Private.h b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CoreAnimation_Private.h index a3bf51e2e..12b1bd35a 100644 --- a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CoreAnimation_Private.h +++ b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CoreAnimation_Private.h @@ -39,6 +39,16 @@ typedef struct CAColorMatrix CAColorMatrix; - (void)setHighFrameRateReasons_openswiftui_safe_wrapper:(const uint32_t *)reasons count:(NSInteger)count OPENSWIFTUI_SWIFT_NAME(setHighFrameRateReasons(_:count:)); @end +// MARK: - CATransaction (Private) + +@interface CATransaction (OpenSwiftUI_Private) + +/// Activate a background Core Animation context for off-main-thread rendering. +/// Must be called before any CA operations on a background thread. ++ (void)activateBackground:(BOOL)activate; + +@end + OPENSWIFTUI_ASSUME_NONNULL_END #endif /* CoreAnimation.h */ From 1e81addb038b7ea4d56ae93aba89f57ae35f0ce1 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 28 Mar 2026 00:31:20 +0800 Subject: [PATCH 07/19] Add DisplayListViewCache Stub --- .../Render/DisplayList/DisplayList.swift | 9 +- .../DisplayList/DisplayListViewCache.swift | 142 +++++++++++++++--- .../DisplayList/DisplayListViewUpdater.swift | 9 ++ 3 files changed, 140 insertions(+), 20 deletions(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift index c6eb2c883..f9854bf7b 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift @@ -218,9 +218,10 @@ extension DisplayList { case rotation3D(_Rotation3DEffect.Data) } -// package typealias AnyEffectAnimation = _DisplayList_AnyEffectAnimation -// package typealias AnyEffectAnimator = _DisplayList_AnyEffectAnimator - + package typealias AnyEffectAnimation = _DisplayList_AnyEffectAnimation + + package typealias AnyEffectAnimator = _DisplayList_AnyEffectAnimator + package struct ArchiveIDs { package var uuid: UUID package var stableIDs: StableIdentityMap @@ -553,6 +554,8 @@ package struct AccessibilityNodeAttachment {} package protocol _DisplayList_AnyEffectAnimation {} +package protocol _DisplayList_AnyEffectAnimator: AnyObject {} + extension DisplayList.Item { func addDrawingGroup(contentSeed: DisplayList.Seed) { _openSwiftUIUnimplementedWarning() diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift index 239d6c458..062cbeeb0 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift @@ -2,52 +2,160 @@ // DisplayListViewCache.swift // OpenSwiftUICore // -// Audited for 6.0.87 +// Audited for 6.5.4 // Status: WIP // ID: A9949015C771FF99F7528BB7239FD006 (SwiftUICore) import Foundation +import OpenQuartzCoreShims +import QuartzCore_Private extension DisplayList.ViewUpdater { + + // MARK: - ViewCache [WIP] + struct ViewCache { - enum Tag { + + // MARK: - Tag + + enum Tag: UInt8 { case item case inherited } - struct Key { + // MARK: - Key + + struct Key: Hashable { var id: DisplayList.Index.ID var tag: Tag } + let platform: Platform + + var map: [Key: ViewInfo] + + var reverseMap: [OpaquePointer: Key] + + var removed: Set + + private struct AnimatorInfo { + enum State { + case idle + case active(DisplayList.AnyEffectAnimator) + case finished(DisplayList.Effect, DisplayList.Version) + } + + var state: State + var deadline: Time + } + + private var animators: [Key: AnimatorInfo] + private struct AsyncValues { var animations: Set - var modifiers: [String: Void /*CAPresentationModifier*/] + var modifiers: [String: CAPresentationModifier] } + private var asyncValues: [ObjectIdentifier: AsyncValues] + private struct PendingAsyncValue { var keyPath: String var value: NSObject var usesPresentationModifier: Bool } - private struct AnimatorInfo { - enum State { - // case active(_DisplayList_AnyEffectAnimator) - case finished(DisplayList.Effect, DisplayList.Version) - case idle - } + private var pendingAsyncValues: [ObjectIdentifier: [PendingAsyncValue]] - var state: State - var deadline: Time - } + var asyncModifierGroup: CAPresentationModifierGroup? + var pendingAsyncUpdates: [() -> Void] - let platform: Platform - // TODO - var pendingAsyncUpdates: [() -> ()] var index: DisplayList.Index - var cacheSeed: Swift.UInt32 + + var cacheSeed: UInt32 + var currentList: DisplayList + + // MARK: - Init + + init(platform: Platform) { + self.platform = platform + self.map = [:] + self.reverseMap = [:] + self.removed = [] + self.animators = [:] + self.asyncValues = [:] + self.pendingAsyncValues = [:] + self.pendingAsyncUpdates = [] + self.index = DisplayList.Index() + self.cacheSeed = 0 + self.currentList = DisplayList() + } + + mutating func clearAsyncValues() { + _openSwiftUIUnimplementedFailure() + } + + mutating func reclaim(time: Time) { + _openSwiftUIUnimplementedFailure() + } + + mutating func commitAsyncValues(targetTimestamp: Time?) { + _openSwiftUIUnimplementedFailure() + } + + mutating func prepare( + item: inout DisplayList.Item, + parentState: UnsafePointer + ) -> Time { + _openSwiftUIUnimplementedFailure() + } + + struct Result {} + + mutating func update( + item: DisplayList.Item, + state: UnsafePointer, + tag: Tag, + in parentID: ViewInfo.ID, + makeView: (DisplayList.Index, DisplayList.Item, UnsafePointer) -> DisplayList.ViewUpdater.ViewInfo, + updateView: (inout ViewInfo, DisplayList.Index, DisplayList.Item, UnsafePointer) -> Void + ) -> Result { + _openSwiftUIUnimplementedFailure() + } + + mutating func setNextUpdate( + _ time: Time, + in result: inout Result + ) { + _openSwiftUIUnimplementedFailure() + } + + struct AsyncResult {} + + mutating func setNextUpdate( + _ time: Time, + in result: inout AsyncResult + ) { + _openSwiftUIUnimplementedFailure() + } + + mutating func setAsyncValue( + _ value: NSObject, + for key: String, + in layer: CALayer, + usingPresentationModifier: Bool + ) { + _openSwiftUIUnimplementedFailure() + } + + private mutating func prepareAnimation( + _ animation: any DisplayList.AnyEffectAnimation, + displayList: DisplayList, + item: inout DisplayList.Item, + parentState: UnsafePointer + ) -> Time { + _openSwiftUIUnimplementedFailure() + } } } diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewUpdater.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewUpdater.swift index 9bb8c50b1..6c604eceb 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewUpdater.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewUpdater.swift @@ -99,3 +99,12 @@ extension DisplayList.ViewUpdater { var isInvalid: Bool } } + +// MARK: - DisplayList.ViewUpdater.Model + +extension DisplayList.ViewUpdater { + struct Model { + struct State { + } + } +} From b3071c625257f47c0280bbf457ecaf7cd07973e7 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 29 Mar 2026 14:57:45 +0800 Subject: [PATCH 08/19] Implement ViewCache.setAsyncValue --- .../Render/DisplayList/DisplayListViewCache.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift index 062cbeeb0..eb8cb81fc 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift @@ -146,7 +146,14 @@ extension DisplayList.ViewUpdater { in layer: CALayer, usingPresentationModifier: Bool ) { - _openSwiftUIUnimplementedFailure() + let layerID = ObjectIdentifier(layer) + pendingAsyncValues[layerID, default: []].append( + PendingAsyncValue( + keyPath: key, + value: value, + usesPresentationModifier: usingPresentationModifier + ) + ) } private mutating func prepareAnimation( From e5b749572bd7907792a903c0dbcd75480e3f1c3b Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 29 Mar 2026 17:26:38 +0800 Subject: [PATCH 09/19] Update DisplayListPlatformEffect --- .../DisplayListPlatformEffect.swift | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListPlatformEffect.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListPlatformEffect.swift index aeccc9564..8cf43185c 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListPlatformEffect.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListPlatformEffect.swift @@ -2,21 +2,40 @@ // DisplayListPlatformEffect.swift // OpenSwiftUICore // -// Audited for 6.0.87 +// Audited for 6.5.4 // Status: Complete +// MARK: - DisplayList.PlatformEffect + extension DisplayList { package enum PlatformEffect { case identity - package var features: DisplayList.Features { [] } - package func encode(to encoder: any Encoder) throws {} - package init(from decoder: any Decoder) throws { self = .identity } - package func print(into sexp: inout SExpPrinter) {} + + package var features: DisplayList.Features { + [] + } + + package func encode(to encoder: any Encoder) throws { + _openSwiftUIEmptyStub() + } + + package init(from decoder: any Decoder) throws { + self = .identity + } + + package func print(into sexp: inout SExpPrinter) { + _openSwiftUIEmptyStub() + } } } extension DisplayList.PlatformEffect: ProtobufMessage { - package func encode(to encoder: inout ProtobufEncoder) throws {} - package init(from decoder: inout ProtobufDecoder) throws { self = .identity } + package func encode(to encoder: inout ProtobufEncoder) throws { + _openSwiftUIEmptyStub() + } + + package init(from decoder: inout ProtobufDecoder) throws { + self = .identity + } } extension DisplayList.ViewUpdater { @@ -29,3 +48,8 @@ extension DisplayList.ViewUpdater.Platform { package struct PlatformState { } } + +extension DisplayList.ViewUpdater.Model { + package struct PlatformState { + } +} From b5b9515a7a36554f17558b44cd2987f323b803cc Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 29 Mar 2026 21:58:35 +0800 Subject: [PATCH 10/19] Update CALayer API --- .../DisplayList/DisplayListViewCache.swift | 12 ++++ Sources/OpenSwiftUICore/Test/Test.swift | 1 + .../QuartzCore/CALayer+OpenSwiftUIAdditions.h | 31 ++++++++++ .../QuartzCore/CALayer+OpenSwiftUIAdditions.m | 61 +++++++++++++++++++ .../Shims/QuartzCore/CALayerPrivate.h | 2 - .../Shims/QuartzCore/CALayerPrivate.m | 8 --- 6 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 Sources/OpenSwiftUI_SPI/Overlay/QuartzCore/CALayer+OpenSwiftUIAdditions.h create mode 100644 Sources/OpenSwiftUI_SPI/Overlay/QuartzCore/CALayer+OpenSwiftUIAdditions.m diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift index eb8cb81fc..52c82b41b 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift @@ -166,3 +166,15 @@ extension DisplayList.ViewUpdater { } } } + +#if canImport(QuartzCore) +import OpenSwiftUI_SPI +package import QuartzCore + +extension CALayer { + package var displayListID: DisplayList.Identity { + get { DisplayList.Identity(value: .init(openSwiftUI_displayListID)) } + set { openSwiftUI_displayListID = .init(newValue.value) } + } +} +#endif diff --git a/Sources/OpenSwiftUICore/Test/Test.swift b/Sources/OpenSwiftUICore/Test/Test.swift index 2d54ecf8b..c9439b14f 100644 --- a/Sources/OpenSwiftUICore/Test/Test.swift +++ b/Sources/OpenSwiftUICore/Test/Test.swift @@ -163,6 +163,7 @@ package struct PlatformViewTestProperties: OptionSet { } #if canImport(QuartzCore) +import OpenSwiftUI_SPI package import QuartzCore extension CALayer { diff --git a/Sources/OpenSwiftUI_SPI/Overlay/QuartzCore/CALayer+OpenSwiftUIAdditions.h b/Sources/OpenSwiftUI_SPI/Overlay/QuartzCore/CALayer+OpenSwiftUIAdditions.h new file mode 100644 index 000000000..86e580618 --- /dev/null +++ b/Sources/OpenSwiftUI_SPI/Overlay/QuartzCore/CALayer+OpenSwiftUIAdditions.h @@ -0,0 +1,31 @@ +// +// CALayer+OpenSwiftUIAddition.h +// OpenSwiftUI_SPI +// +// Audited for 6.5.4 +// Status: Complete + +#ifndef CALayer_OpenSwiftUIAdditions_h +#define CALayer_OpenSwiftUIAdditions_h + +#include "OpenSwiftUIBase.h" + +#if __has_include() + +#import + +// MARK: - CALayer (OpenSwiftUIAdditions) + +@interface CALayer (OpenSwiftUIAdditions) + +@property (nonatomic, assign) uint64_t openSwiftUI_viewTestProperties; + +@property (nonatomic, assign) int64_t openSwiftUI_displayListID; + +- (void)openSwiftUI_setNoAnimationDelegate; + +@end + +#endif /* __has_include() */ + +#endif /* CALayer_OpenSwiftUIAdditions_h */ diff --git a/Sources/OpenSwiftUI_SPI/Overlay/QuartzCore/CALayer+OpenSwiftUIAdditions.m b/Sources/OpenSwiftUI_SPI/Overlay/QuartzCore/CALayer+OpenSwiftUIAdditions.m new file mode 100644 index 000000000..e9b25dd6d --- /dev/null +++ b/Sources/OpenSwiftUI_SPI/Overlay/QuartzCore/CALayer+OpenSwiftUIAdditions.m @@ -0,0 +1,61 @@ +// +// CALayer+OpenSwiftUIAdditions.m +// OpenSwiftUI_SPI +// +// Audited for 6.5.4 +// Status: Complete + +#import "CALayer+OpenSwiftUIAdditions.h" +#import "CANullAction.h" + +#if __has_include() + +// MARK: - _OpenSwiftUI_NoAnimationDelegate + +/// A class whose sole purpose is to act as a CALayer delegate that +/// suppresses all implicit animations by returning kCFNull. +/// Used as a class object (not an instance) — the class method +/// +actionForLayer:forKey: is dispatched via the metaclass. +@interface _OpenSwiftUI_NoAnimationDelegate : NSObject +@end + +@implementation _OpenSwiftUI_NoAnimationDelegate + ++ (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event { + return _CANullAction(); +} + +@end + +// MARK: - CALayer (OpenSwiftUIAdditions) + +@implementation CALayer (OpenSwiftUIAdditions) + +- (uint64_t)openSwiftUI_viewTestProperties { + NSNumber *properties = [self valueForKey:@"_openSwiftUI_viewTestProperties"]; + return properties.integerValue; +} + +- (void)setOpenSwiftUI_viewTestProperties:(uint64_t)properties { + [self setValue:[NSNumber numberWithUnsignedLongLong:properties] forKey:@"_openSwiftUI_viewTestProperties"]; +} + +- (int64_t)openSwiftUI_displayListID { + NSNumber *value = [self valueForKey:@"_openSwiftUI_displayListID"]; + if (value == nil) { + return INT64_MAX; + } + return value.integerValue; +} + +- (void)setOpenSwiftUI_displayListID:(int64_t)displayListID { + [self setValue:[NSNumber numberWithInteger:displayListID] forKey:@"_openSwiftUI_displayListID"]; +} + +- (void)openSwiftUI_setNoAnimationDelegate { + self.delegate = (id)[_OpenSwiftUI_NoAnimationDelegate class]; +} + +@end + +#endif /* __has_include() */ diff --git a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CALayerPrivate.h b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CALayerPrivate.h index db17afa08..1116dcb57 100644 --- a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CALayerPrivate.h +++ b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CALayerPrivate.h @@ -23,8 +23,6 @@ typedef NSString *CALayerContentsScaling NS_TYPED_ENUM; @property (nonatomic, assign) BOOL allowsGroupBlending_openswiftui_safe_wrapper OPENSWIFTUI_SWIFT_NAME(allowsGroupBlending); @property (nonatomic, assign) BOOL allowsHitTesting_openswiftui_safe_wrapper OPENSWIFTUI_SWIFT_NAME(allowsHitTesting); -@property (nonatomic, assign) uint64_t openSwiftUI_viewTestProperties; - /// Private property to control contents alpha channel swizzling. /// When set to kCALayerContentsSwizzleAAAA, the alpha channel is replicated to all channels. /// When set to kCALayerContentsSwizzleRGBA, normal RGBA behavior is used. diff --git a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CALayerPrivate.m b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CALayerPrivate.m index c56cb2179..95599e258 100644 --- a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CALayerPrivate.m +++ b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CALayerPrivate.m @@ -51,14 +51,6 @@ - (void)setAllowsHitTesting_openswiftui_safe_wrapper:(BOOL)allows { func(self, selector, allows); } -- (uint64_t)openSwiftUI_viewTestProperties { - NSNumber *properties = [self valueForKey:@"_viewTestProperties"]; - return properties.integerValue; -} - -- (void)setOpenSwiftUI_viewTestProperties:(uint64_t)properties { - [self setValue:[NSNumber numberWithUnsignedLongLong:properties] forKey:@"_viewTestProperties"]; -} @end void _CALayerSetSplatsContentsAlpha(CALayer * _Nonnull layer, BOOL splatAlpha) { From d5a8ffddc5131ab9af7f194e3ff821b531b1e457 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 30 Mar 2026 01:57:12 +0800 Subject: [PATCH 11/19] Update DisplayListViewCache.update and Result --- .../Render/DisplayList/DisplayList.swift | 31 +- .../DisplayList/DisplayListViewCache.swift | 249 ++++++++++++++- .../DisplayList/DisplayListViewUpdater.swift | 286 ++++++++++++++++-- 3 files changed, 530 insertions(+), 36 deletions(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift index f9854bf7b..3a030c77f 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift @@ -482,7 +482,22 @@ extension DisplayList.Item { // TODO eg. .opacity(1.0) -> .identity } - // package func matchesTopLevelStructure(of other: DisplayList.Item) -> Bool + // TBA + package func matchesTopLevelStructure(of other: DisplayList.Item) -> Bool { + guard identity == other.identity else { return false } + switch (value, other.value) { + case (.empty, .empty): + return true + case (.content, .content): + return true + case (.effect, .effect): + return true + case (.states, .states): + return true + default: + return false + } + } package var features: DisplayList.Features { // TODO @@ -552,9 +567,19 @@ extension DisplayList { package struct AccessibilityNodeAttachment {} -package protocol _DisplayList_AnyEffectAnimation {} +package protocol _DisplayList_AnyEffectAnimation: ProtobufMessage { + // FIXME: CodableEffectAnimation + static var leafProtobufTag: CodableAnimation.Tag? { get } + func makeAnimator() -> any _DisplayList_AnyEffectAnimator +} -package protocol _DisplayList_AnyEffectAnimator: AnyObject {} +package protocol _DisplayList_AnyEffectAnimator { + func evaluate( + _ animation: any DisplayList.AnyEffectAnimation, + at time: Time, + size: CGSize + ) -> (DisplayList.Effect, finished: Bool) +} extension DisplayList.Item { func addDrawingGroup(contentSeed: DisplayList.Seed) { diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift index 52c82b41b..bc4553c8f 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift @@ -7,6 +7,7 @@ // ID: A9949015C771FF99F7528BB7239FD006 (SwiftUICore) import Foundation +import OpenSwiftUI_SPI import OpenQuartzCoreShims import QuartzCore_Private @@ -76,8 +77,7 @@ extension DisplayList.ViewUpdater { var currentList: DisplayList - // MARK: - Init - + // TBA init(platform: Platform) { self.platform = platform self.map = [:] @@ -92,16 +92,135 @@ extension DisplayList.ViewUpdater { self.currentList = DisplayList() } + // TBA mutating func clearAsyncValues() { - _openSwiftUIUnimplementedFailure() + for (layerID, values) in asyncValues { + // Recover CALayer from ObjectIdentifier — the layer must still be alive + // since ViewCache holds strong refs to views containing these layers. + let layer = unsafeBitCast(layerID, to: CALayer.self) + for animationKey in values.animations { + layer.removeAnimation(forKey: animationKey) + } + for modifier in values.modifiers.values { + layer.remove(modifier) + } + } + asyncValues.removeAll() + asyncModifierGroup = nil } + // TBA mutating func reclaim(time: Time) { - _openSwiftUIUnimplementedFailure() + for key in removed { + if let info = map.removeValue(forKey: key) { + let pointer = unsafeBitCast(info.view, to: OpaquePointer.self) + reverseMap.removeValue(forKey: pointer) + // TODO: _CoreViewRemoveFromSuperview for managed view kinds + } + } + removed.removeAll() + animators = animators.filter { $0.value.deadline >= time } + cacheSeed &+= 1 } + // TBA mutating func commitAsyncValues(targetTimestamp: Time?) { - _openSwiftUIUnimplementedFailure() + #if canImport(QuartzCore) + guard !pendingAsyncValues.isEmpty || !pendingAsyncUpdates.isEmpty else { + return + } + + // Activate background CA context if not on main thread + if !Thread.isMainThread { + CATransaction.activateBackground(true) + } + + // Suppress implicit animations during commit + let savedDisableActions = CATransaction.disableActions() + if !savedDisableActions { + CATransaction.setDisableActions(true) + } + + // Track which modifier groups need flushing + var modifiedGroups: Set = [] + + // Apply each pending async value to its layer + for (layerID, values) in pendingAsyncValues { + let layer = unsafeBitCast(layerID, to: CALayer.self) + + // Ensure asyncValues entry exists for this layer + if asyncValues[layerID] == nil { + asyncValues[layerID] = AsyncValues( + animations: [], + modifiers: [:] + ) + } + + for pending in values { + if !pending.usesPresentationModifier { + // CABasicAnimation path + let animation = CABasicAnimation(keyPath: pending.keyPath) + animation.beginTime = -1 + animation.duration = 1 + animation.fillMode = .forwards + animation.toValue = pending.value + animation.isRemovedOnCompletion = false + layer.add(animation, forKey: pending.keyPath) + asyncValues[layerID]!.animations.insert(pending.keyPath) + } else { + // CAPresentationModifier path + if let existing = asyncValues[layerID]!.modifiers[pending.keyPath] { + existing.value = pending.value + if let group = existing.group { + modifiedGroups.insert(ObjectIdentifier(group)) + } + } else { + // Reuse existing group if it has capacity, otherwise create new + let group: CAPresentationModifierGroup + if let existingGroup = asyncModifierGroup, + existingGroup.count < existingGroup.capacity { + group = existingGroup + } else { + group = CAPresentationModifierGroup(capacity: 100) + group.updatesAsynchronously = false + asyncModifierGroup = group + } + let modifier = CAPresentationModifier( + keyPath: pending.keyPath, + initialValue: pending.value, + additive: false, + group: group + ) + layer.add(modifier) + asyncValues[layerID]!.modifiers[pending.keyPath] = modifier + if let group = modifier.group { + modifiedGroups.insert(ObjectIdentifier(group)) + } + } + } + } + } + + // Restore disableActions + if !savedDisableActions { + CATransaction.setDisableActions(false) + } + + // Flush modified presentation modifier groups + for groupID in modifiedGroups { + let group = unsafeBitCast(groupID, to: CAPresentationModifierGroup.self) + group.flushWithTransaction() + } + #endif + + // Execute all completion closures + for update in pendingAsyncUpdates { + update() + } + + // Reset state + pendingAsyncValues = [:] + pendingAsyncUpdates = [] } mutating func prepare( @@ -111,33 +230,139 @@ extension DisplayList.ViewUpdater { _openSwiftUIUnimplementedFailure() } - struct Result {} - mutating func update( item: DisplayList.Item, state: UnsafePointer, tag: Tag, in parentID: ViewInfo.ID, - makeView: (DisplayList.Index, DisplayList.Item, UnsafePointer) -> DisplayList.ViewUpdater.ViewInfo, + makeView: (DisplayList.Index, DisplayList.Item, UnsafePointer) -> ViewInfo, updateView: (inout ViewInfo, DisplayList.Index, DisplayList.Item, UnsafePointer) -> Void ) -> Result { - _openSwiftUIUnimplementedFailure() + let key = Key(id: index.id, tag: tag) + let version = item.version + if let existingInfo = map[key] { + var info = existingInfo + guard info.cacheSeed != cacheSeed else { + let description = currentList.description + Log.internalError( + "repeated view: %u, %u, %u, %u, %s, %s", + key.id.identity.value, + key.id.serial, + key.id.archiveIdentity.value, + key.id.archiveSerial, + String(describing: info.state.kind), + description + ) + preconditionFailure("repeated view: #\(key.id.identity.value), \(key.id.serial), \(key.id.archiveIdentity.value), \(key.id.archiveSerial), \(info.state.kind), \(description)") + } + defer { map[key] = info } + // Update isRemoved + if info.isRemoved { + info.isRemoved = false + removed.remove(key) + } + // Update cacheSeed + info.cacheSeed = cacheSeed + // Update nextUpdate + let newSeed = DisplayList.Seed(version) + var isInserted = info.seeds.item != newSeed || state.pointee.globals.pointee.time >= info.nextUpdate + info.nextUpdate = .infinity + // Update parentID + let oldParentID = info.parentID + if oldParentID != parentID { + info.parentID = parentID + info.seeds.invalidate() + } + // Update view + let oldView = info.view + updateView(&info, index, item, state) + if !info.isInvalid { + info.seeds.item = DisplayList.Seed(version) + } + if info.view !== oldView { + reverseMap.removeValue(forKey: unsafeBitCast(oldView, to: OpaquePointer.self)) + #if canImport(Darwin) + CoreViewRemoveFromSuperview(system: platform.viewSystem, view: oldView) + #endif + reverseMap[unsafeBitCast(info.view, to: OpaquePointer.self)] = key + #if canImport(QuartzCore) + if index.archiveIdentity == .none, item.identity != .none { + info.layer.displayListID = item.identity + } + #endif + isInserted = true + } + return Result( + view: info.view, + container: info.container, + id: info.id, + key: key, + isInserted: isInserted, + isValid: !info.isInvalid, + nextUpdate: info.nextUpdate + ) + } else { + var info = makeView(index, item, state) + info.parentID = parentID + info.cacheSeed = cacheSeed + info.seeds.item = DisplayList.Seed(version) + map[key] = info + // If this view was previously cached under a different key, + // remove the stale map entry for that old key. + let viewPointer = unsafeBitCast(info.view, to: OpaquePointer.self) + if let oldKey = reverseMap[viewPointer] { + map.removeValue(forKey: oldKey) + } + reverseMap[viewPointer] = key + #if canImport(QuartzCore) + if index.archiveIdentity == .none, item.identity != .none { + info.layer.displayListID = item.identity + } + #endif + return Result( + view: info.view, + container: info.container, + id: info.id, + key: key, + isInserted: true, + isValid: !info.isInvalid, + nextUpdate: info.nextUpdate + ) + } + } + + struct Result { + var view: AnyObject + var container: AnyObject + var id: ViewInfo.ID + var key: Key + var isInserted: Bool + var isValid: Bool + var nextUpdate: Time } mutating func setNextUpdate( _ time: Time, in result: inout Result ) { - _openSwiftUIUnimplementedFailure() + guard time < result.nextUpdate else { return } + result.nextUpdate = time + map[result.key]!.nextUpdate = time } - struct AsyncResult {} + struct AsyncResult { + var unknown: AnyObject // FIXME + var key: Key + var nextUpdate: Time + } mutating func setNextUpdate( _ time: Time, in result: inout AsyncResult ) { - _openSwiftUIUnimplementedFailure() + guard time < result.nextUpdate else { return } + result.nextUpdate = time + map[result.key]!.nextUpdate = time } mutating func setAsyncValue( diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewUpdater.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewUpdater.swift index 6c604eceb..7ec92cae6 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewUpdater.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewUpdater.swift @@ -1,5 +1,5 @@ // -// DisplayList.ViewUpdater.swift +// DisplayListViewUpdater.swift // OpenSwiftUICore // // Audited for 6.0.87 @@ -9,14 +9,12 @@ private var printTree: Bool? import Foundation -#if canImport(Darwin) -import QuartzCore -#endif +import OpenQuartzCoreShims extension DisplayList { // FIXME final package class ViewUpdater: ViewRendererBase { - weak var host: ViewRendererHost? + weak var host: (any ViewRendererHost)? var viewCache: DisplayList.ViewUpdater.ViewCache var seed: DisplayList.Seed var asyncSeed: DisplayList.Seed @@ -27,36 +25,172 @@ extension DisplayList { var isValid: Bool var wasValid: Bool - init() { - _openSwiftUIUnimplementedFailure() + init(platform: Platform, host: (any ViewRendererHost)?){ + self.host = host + self.viewCache = ViewCache(platform: platform) + self.seed = DisplayList.Seed() + self.asyncSeed = DisplayList.Seed() + self.nextUpdate = .infinity + self.lastEnv = .invalid + self.lastList = DisplayList() + self.lastTime = .zero + self.isValid = false + self.wasValid = false } - func render(rootView: AnyObject, from list: DisplayList, time: Time, version: DisplayList.Version, maxVersion: DisplayList.Version, environment: DisplayList.ViewRenderer.Environment) -> Time { - // TODO + func render( + rootView: AnyObject, + from list: DisplayList, + time: Time, + version: DisplayList.Version, + maxVersion: DisplayList.Version, + environment: DisplayList.ViewRenderer.Environment + ) -> Time { + viewCache.clearAsyncValues() if printTree == nil { printTree = ProcessEnvironment.bool(forKey: "OPENSWIFTUI_PRINT_TREE") } if let printTree, printTree { print("View \(Unmanaged.passUnretained(rootView).toOpaque()) at \(time):\n\(list.description)") } - return .zero + + let newSeed = DisplayList.Seed(version) + let seedChanged = newSeed != seed + let envChanged = environment != lastEnv + + wasValid = isValid + + if seedChanged || envChanged || !isValid { + // TODO: Walk display list items and create/update platform views + viewCache.currentList = list + seed = newSeed + lastEnv = environment + isValid = true + } + + lastList = list + lastTime = time + nextUpdate = .infinity + + return nextUpdate } - func renderAsync(to list: DisplayList, time: Time, targetTimestamp: Time?, version: DisplayList.Version, maxVersion: DisplayList.Version) -> Time? { - nil + func renderAsync( + to list: DisplayList, + time: Time, + targetTimestamp: Time?, + version: DisplayList.Version, + maxVersion: DisplayList.Version + ) -> Time? { + // Phase 1: Version hash early return + let newAsyncSeed = DisplayList.Seed(version) + if newAsyncSeed == asyncSeed, lastTime >= time { + return nextUpdate + } + + // Phase 2: Debug print + if printTree == nil { + printTree = ProcessEnvironment.bool(forKey: "OPENSWIFTUI_PRINT_TREE") + } + if let printTree, printTree { + print("Async view at \(time):\n\(list.description)") + } + + // Phase 3: Save state and call updateAsync + wasValid = isValid + let oldList = viewCache.currentList + + guard let resultTime = updateAsync(oldList: oldList, newList: list) else { + // Cancelled: rollback + isValid = wasValid + return nil + } + + // Phase 4: Commit + viewCache.commitAsyncValues(targetTimestamp: targetTimestamp) + viewCache.currentList = list + asyncSeed = newAsyncSeed + lastTime = time + nextUpdate = resultTime + isValid = true + + return resultTime + } + + private func updateAsync(oldList: DisplayList, newList: DisplayList) -> Time? { + let oldItems = oldList.items + let newItems = newList.items + guard oldItems.count == newItems.count else { + return nil + } + + var nextTime: Time = .infinity + + for i in 0 ..< oldItems.count { + let oldItem = oldItems[i] + let newItem = newItems[i] + + guard oldItem.matchesTopLevelStructure(of: newItem) else { + return nil + } + + switch (oldItem.value, newItem.value) { + case let (.effect(_, oldChild), .effect(_, newChild)): + guard let childTime = updateAsync(oldList: oldChild, newList: newChild) else { + return nil + } + nextTime = min(nextTime, childTime) + case let (.states(oldStates), .states(newStates)): + guard oldStates.count == newStates.count else { + return nil + } + for j in 0 ..< oldStates.count { + let (oldHash, oldChild) = oldStates[j] + let (newHash, newChild) = newStates[j] + guard oldHash == newHash else { + return nil + } + guard let stateTime = updateAsync( + oldList: oldChild, + newList: newChild + ) else { + return nil + } + nextTime = min(nextTime, stateTime) + } + case (.content, .content): + // TODO: updateItemViewAsync — leaf platform view property update + if oldItem.version != newItem.version { + // Content changed but structure same — would update platform view here + } + case (.empty, .empty): + break + default: + break + } + } + + return nextTime } func destroy(rootView: AnyObject) { + isValid = false + wasValid = false + lastList = DisplayList() + lastEnv = .invalid + seed = DisplayList.Seed() + asyncSeed = DisplayList.Seed() + nextUpdate = .infinity + viewCache.currentList = DisplayList() + viewCache.pendingAsyncUpdates.removeAll() } var viewCacheIsEmpty: Bool { - // TODO - false + viewCache.map.isEmpty } var platform: Platform { - // TODO - _openSwiftUIUnimplementedFailure() + viewCache.platform } var exportedObject: AnyObject? { @@ -79,16 +213,26 @@ extension DisplayList.ViewUpdater { var shadow: DisplayList.Seed var properties: DisplayList.Seed var platformSeeds: DisplayList.ViewUpdater.PlatformViewInfo.Seeds + + mutating func invalidate() { + item.invalidate() + content.invalidate() + opacity.invalidate() + blend.invalidate() + transform.invalidate() + clips.invalidate() + filters.invalidate() + shadow.invalidate() + properties.invalidate() + } } - struct ID { + struct ID: Equatable { var value: Int } var view: AnyObject - #if canImport(Darwin) var layer: CALayer - #endif var container: AnyObject var state: DisplayList.ViewUpdater.Platform.State var id: ID @@ -97,14 +241,114 @@ extension DisplayList.ViewUpdater { var cacheSeed: UInt32 var isRemoved: Bool var isInvalid: Bool + var nextUpdate: Time + + init( + view: AnyObject, + layer: CALayer, + container: AnyObject, + state: Platform.State + ) { + self.view = view + self.layer = layer + self.container = container + self.state = state + self.id = ID(value: 0) + self.parentID = ID(value: 0) + self.seeds = Seeds( + item: .init(), content: .init(), opacity: .init(), + blend: .init(), transform: .init(), clips: .init(), + filters: .init(), shadow: .init(), properties: .init(), + platformSeeds: .init() + ) + self.cacheSeed = 0 + self.isRemoved = false + self.isInvalid = false + self.nextUpdate = .infinity + } + + init( + platform: Platform, + kind: PlatformViewDefinition.ViewKind + ) { + _openSwiftUIUnimplementedFailure() + } + + func reset() { + _openSwiftUIUnimplementedFailure() + } } } -// MARK: - DisplayList.ViewUpdater.Model +// MARK: - DisplayList.ViewUpdater.Model [WIP] extension DisplayList.ViewUpdater { - struct Model { + enum Model { + struct Clip { + var path: Path + var transform: CGAffineTransform? + var style: FillStyle + + var isEmpty: Bool { + // TODO + false + } + } + struct State { + struct Versions { + var opacity: DisplayList.Version + var blend: DisplayList.Version + var transform: DisplayList.Version + var clips: DisplayList.Version + var filters: DisplayList.Version + var shadow: DisplayList.Version + var properties: DisplayList.Version + } + + struct Globals { + var updater: DisplayList.ViewUpdater + var time: Time + var maxVersion: DisplayList.Version + var environment: DisplayList.ViewRenderer.Environment + } + + var globals: UnsafePointer + var opacity: Float + var blend: GraphicsBlendMode + var transform: CGAffineTransform + var clips: [Clip] + var filters: [GraphicsFilter] + var shadow: Indirect + var properties: DisplayList.Properties + var rewriteVibrantColorMatrix: Bool + var compositingGroup: Bool + var backdropGroupID: UInt32 + var stateHashes: [StrongHash] + var platformState: PlatformState + var versions: Versions + + var hasDODEffects: Bool { + // TODO + false + } + + func reset() { + // TODO + } + + func clipRect() -> FixedRoundedRect? { + // TODO + nil + } + + func adjust(for transform: CGAffineTransform) { + // TODO + } + + mutating func addClip(_ path: Path, style: FillStyle) { + // TODO + } } } } From b20244872f93b5822487bd4479ff7429c79155d8 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 30 Mar 2026 02:32:51 +0800 Subject: [PATCH 12/19] Update ViewCache.prepare --- .../Graphic/GraphicsFilter.swift | 6 +- .../DisplayList/DisplayListViewCache.swift | 71 ++++++++++++++++++- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift b/Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift index 2b3845ade..1e3a3dc6f 100644 --- a/Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift +++ b/Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift @@ -24,7 +24,7 @@ package enum GraphicsFilter { case vibrantColorMatrix(_ColorMatrix) case luminanceCurve(GraphicsFilter.LuminanceCurve) case colorCurves(GraphicsFilter.ColorCurves) - // case shader(GraphicsFilter.ShaderFilter) + case shader(GraphicsFilter.ShaderFilter) package struct ColorMonochrome: Equatable { package var color: Color.Resolved @@ -73,7 +73,7 @@ package enum GraphicsFilter { } } -// package struct ShaderFilter { + package struct ShaderFilter { // package var shader: Shader.ResolvedShader // package var size: CGSize // @@ -81,7 +81,7 @@ package enum GraphicsFilter { // self.shader = shader // self.size = size // } -// } + } } package enum GraphicsBlendMode: Equatable { diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift index bc4553c8f..ca6c9b4d5 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift @@ -42,7 +42,7 @@ extension DisplayList.ViewUpdater { private struct AnimatorInfo { enum State { case idle - case active(DisplayList.AnyEffectAnimator) + case active(any DisplayList.AnyEffectAnimator) case finished(DisplayList.Effect, DisplayList.Version) } @@ -227,7 +227,34 @@ extension DisplayList.ViewUpdater { item: inout DisplayList.Item, parentState: UnsafePointer ) -> Time { - _openSwiftUIUnimplementedFailure() + switch item.value { + case let .content(content): + if case let .shape(_, paint, _) = content.value, !paint.isCALayerCompatible { + item.addDrawingGroup(contentSeed: .init(item.version)) + } + return .infinity + case let .effect(effect, displayList): + switch effect { + case let .archive(archiveIDs): + index.updateArchive(entering: archiveIDs != nil) + return .infinity + case let .filter(filter): + if case .shader = filter { + item.addDrawingGroup(contentSeed: .init(item.version)) + } + case let .animation(animation): + return prepareAnimation( + animation, + displayList: displayList, + item: &item, + parentState: parentState, + ) + default: + return .infinity + } + default: + return .infinity + } } mutating func update( @@ -387,7 +414,45 @@ extension DisplayList.ViewUpdater { item: inout DisplayList.Item, parentState: UnsafePointer ) -> Time { - _openSwiftUIUnimplementedFailure() + let key = Key(id: index.id, tag: .item) + let time = parentState.pointee.globals.pointee.time + var animatorInfo = animators[key, default: .init(state: .idle, deadline: .zero)] + if case .idle = animatorInfo.state { + // If idle, initialize by creating an animator from the animation + animatorInfo.state = .active(animation.makeAnimator()) + } + switch animatorInfo.state { + case let .active(animator): + // Reset to idle before evaluation + animatorInfo.state = .idle + // Evaluate the animation effect + let (effect, finished) = animator.evaluate( + animation, + at: time, + size: item.size, + ) + // Swap item value with the animation effect + item.value = .effect(effect, displayList) + let maxVersion = parentState.pointee.globals.pointee.maxVersion + item.version = maxVersion + if finished { + animatorInfo.state = .finished(effect, maxVersion) + } else { + animatorInfo.state = .active(animator) + } + animatorInfo.deadline = time + animators[key] = animatorInfo + return finished ? .infinity : time + case let .finished(effect, version): + // Re-apply the stored final effect + item.value = .effect(effect, displayList) + item.version = version + animatorInfo.deadline = time + animators[key] = animatorInfo + return .infinity + case .idle: + _openSwiftUIUnreachableCode() + } } } } From 2d51240896ffbfe64ded8dd20df093017660ddc6 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 30 Mar 2026 03:22:07 +0800 Subject: [PATCH 13/19] Update commitAsyncValues --- .../DisplayList/DisplayListViewCache.swift | 63 +++++++------------ 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift index ca6c9b4d5..137bf7799 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift @@ -123,59 +123,32 @@ extension DisplayList.ViewUpdater { cacheSeed &+= 1 } - // TBA mutating func commitAsyncValues(targetTimestamp: Time?) { #if canImport(QuartzCore) guard !pendingAsyncValues.isEmpty || !pendingAsyncUpdates.isEmpty else { return } - // Activate background CA context if not on main thread if !Thread.isMainThread { CATransaction.activateBackground(true) } - // Suppress implicit animations during commit let savedDisableActions = CATransaction.disableActions() if !savedDisableActions { CATransaction.setDisableActions(true) } - // Track which modifier groups need flushing var modifiedGroups: Set = [] - // Apply each pending async value to its layer - for (layerID, values) in pendingAsyncValues { + for (layerID, pendingAsyncValueArray) in pendingAsyncValues { let layer = unsafeBitCast(layerID, to: CALayer.self) - - // Ensure asyncValues entry exists for this layer - if asyncValues[layerID] == nil { - asyncValues[layerID] = AsyncValues( - animations: [], - modifiers: [:] - ) - } - - for pending in values { - if !pending.usesPresentationModifier { - // CABasicAnimation path - let animation = CABasicAnimation(keyPath: pending.keyPath) - animation.beginTime = -1 - animation.duration = 1 - animation.fillMode = .forwards - animation.toValue = pending.value - animation.isRemovedOnCompletion = false - layer.add(animation, forKey: pending.keyPath) - asyncValues[layerID]!.animations.insert(pending.keyPath) - } else { - // CAPresentationModifier path - if let existing = asyncValues[layerID]!.modifiers[pending.keyPath] { + var asyncValueArray = asyncValues[layerID, default: .init(animations: [], modifiers: [:])] + for pending in pendingAsyncValueArray { + if pending.usesPresentationModifier { + if let existing = asyncValueArray.modifiers[pending.keyPath] { existing.value = pending.value - if let group = existing.group { - modifiedGroups.insert(ObjectIdentifier(group)) - } + modifiedGroups.insert(ObjectIdentifier(existing.group!)) } else { - // Reuse existing group if it has capacity, otherwise create new let group: CAPresentationModifierGroup if let existingGroup = asyncModifierGroup, existingGroup.count < existingGroup.capacity { @@ -192,35 +165,41 @@ extension DisplayList.ViewUpdater { group: group ) layer.add(modifier) - asyncValues[layerID]!.modifiers[pending.keyPath] = modifier - if let group = modifier.group { - modifiedGroups.insert(ObjectIdentifier(group)) - } + asyncValueArray.modifiers[pending.keyPath] = modifier + modifiedGroups.insert(ObjectIdentifier(group)) } + } else { + let animation = CABasicAnimation(keyPath: pending.keyPath) + animation.beginTime = -1 + animation.duration = 1 + animation.fillMode = .forwards + animation.toValue = pending.value + animation.isRemovedOnCompletion = false + layer.add(animation, forKey: pending.keyPath) + asyncValueArray.animations.insert(pending.keyPath) } } + asyncValues[layerID] = asyncValueArray } - // Restore disableActions if !savedDisableActions { CATransaction.setDisableActions(false) } - // Flush modified presentation modifier groups for groupID in modifiedGroups { let group = unsafeBitCast(groupID, to: CAPresentationModifierGroup.self) group.flushWithTransaction() } - #endif - // Execute all completion closures for update in pendingAsyncUpdates { update() } - // Reset state pendingAsyncValues = [:] pendingAsyncUpdates = [] + #else + _openSwiftUIPlatformUnimplementedWarning() + #endif } mutating func prepare( From 50f954a6f730da56ed3944ae4c600bc2c5425570 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 30 Mar 2026 03:26:07 +0800 Subject: [PATCH 14/19] Update clearAsyncValues --- .../Render/DisplayList/DisplayListViewCache.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift index 137bf7799..8a8937fe6 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift @@ -3,7 +3,7 @@ // OpenSwiftUICore // // Audited for 6.5.4 -// Status: WIP +// Status: TBA init // ID: A9949015C771FF99F7528BB7239FD006 (SwiftUICore) import Foundation @@ -13,7 +13,7 @@ import QuartzCore_Private extension DisplayList.ViewUpdater { - // MARK: - ViewCache [WIP] + // MARK: - ViewCache struct ViewCache { @@ -92,20 +92,19 @@ extension DisplayList.ViewUpdater { self.currentList = DisplayList() } - // TBA mutating func clearAsyncValues() { - for (layerID, values) in asyncValues { + for (layerID, asyncValueArray) in asyncValues { // Recover CALayer from ObjectIdentifier — the layer must still be alive // since ViewCache holds strong refs to views containing these layers. let layer = unsafeBitCast(layerID, to: CALayer.self) - for animationKey in values.animations { - layer.removeAnimation(forKey: animationKey) + for animation in asyncValueArray.animations { + layer.removeAnimation(forKey: animation) } - for modifier in values.modifiers.values { + for modifier in asyncValueArray.modifiers.values { layer.remove(modifier) } } - asyncValues.removeAll() + asyncValues = [:] asyncModifierGroup = nil } From 8275376e92f85b37700d5a701e7749fc3576bc17 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 30 Mar 2026 04:32:17 +0800 Subject: [PATCH 15/19] Update ViewCache.reclaim --- .../DisplayList/DisplayListViewCache.swift | 26 ++++++++++++++----- .../DisplayList/DisplayListViewPlatform.swift | 20 ++++++++++++++ 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift index 8a8937fe6..6197287aa 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift @@ -108,20 +108,32 @@ extension DisplayList.ViewUpdater { asyncModifierGroup = nil } - // TBA mutating func reclaim(time: Time) { - for key in removed { - if let info = map.removeValue(forKey: key) { - let pointer = unsafeBitCast(info.view, to: OpaquePointer.self) - reverseMap.removeValue(forKey: pointer) - // TODO: _CoreViewRemoveFromSuperview for managed view kinds - } + removed.forEach { key in + guard let info = map[key], info.isRemoved else { return } + removeRecursively(info as AnyObject) } removed.removeAll() animators = animators.filter { $0.value.deadline >= time } cacheSeed &+= 1 } + /// Removes a managed subview from the cache and recursively + /// cleans up its container children from the view hierarchy. + private mutating func removeRecursively(_ object: AnyObject) { + let info = object as! ViewInfo + platform.forEachChild(of: info) { view in + let pointer = unsafeBitCast(view, to: OpaquePointer.self) + if let key = reverseMap.removeValue(forKey: pointer), + let newInfo = map.removeValue(forKey: key) { + removeRecursively(newInfo as AnyObject) + } + #if canImport(Darwin) + CoreViewRemoveFromSuperview(system: platform.viewSystem, view: view) + #endif + } + } + mutating func commitAsyncValues(targetTimestamp: Time?) { #if canImport(QuartzCore) guard !pendingAsyncValues.isEmpty || !pendingAsyncUpdates.isEmpty else { diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift index 0f13b0591..f272e3403 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift @@ -132,6 +132,26 @@ extension DisplayList.ViewUpdater.Platform { // TODO: // private func updateDrawingView // private func updateDrawingViewAsync + + func forEachChild( + of viewInfo: DisplayList.ViewUpdater.ViewInfo, + do body: (AnyObject) -> Void + ) { + #if canImport(Darwin) + let kind = viewInfo.state.kind + if kind.isContainer { + for subview in CoreViewSubviews(system: viewSystem, view: viewInfo.container) { + body(subview as AnyObject) + } + } + if kind == .mask, + let maskView = CoreViewMaskView(system: viewSystem, view: viewInfo.view) { + for subview in CoreViewSubviews(system: viewSystem, view: maskView) { + body(subview as AnyObject) + } + } + #endif + } } extension DisplayList.GraphicsRenderer { From 6e7cb70783db683d5622ca501b3006447a1c96fb Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 30 Mar 2026 04:38:30 +0800 Subject: [PATCH 16/19] Complete DisplayListViewCache --- .../DisplayList/DisplayListViewCache.swift | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift index 6197287aa..2ae40aa54 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift @@ -3,7 +3,7 @@ // OpenSwiftUICore // // Audited for 6.5.4 -// Status: TBA init +// Status: Complete // ID: A9949015C771FF99F7528BB7239FD006 (SwiftUICore) import Foundation @@ -33,11 +33,11 @@ extension DisplayList.ViewUpdater { let platform: Platform - var map: [Key: ViewInfo] + var map: [Key: ViewInfo] = [:] - var reverseMap: [OpaquePointer: Key] + var reverseMap: [OpaquePointer: Key] = [:] - var removed: Set + var removed: Set = [] private struct AnimatorInfo { enum State { @@ -50,14 +50,14 @@ extension DisplayList.ViewUpdater { var deadline: Time } - private var animators: [Key: AnimatorInfo] + private var animators: [Key: AnimatorInfo] = [:] private struct AsyncValues { var animations: Set var modifiers: [String: CAPresentationModifier] } - private var asyncValues: [ObjectIdentifier: AsyncValues] + private var asyncValues: [ObjectIdentifier: AsyncValues] = [:] private struct PendingAsyncValue { var keyPath: String @@ -65,31 +65,20 @@ extension DisplayList.ViewUpdater { var usesPresentationModifier: Bool } - private var pendingAsyncValues: [ObjectIdentifier: [PendingAsyncValue]] + private var pendingAsyncValues: [ObjectIdentifier: [PendingAsyncValue]] = [:] var asyncModifierGroup: CAPresentationModifierGroup? - var pendingAsyncUpdates: [() -> Void] + var pendingAsyncUpdates: [() -> Void] = [] - var index: DisplayList.Index + var index: DisplayList.Index = .init() - var cacheSeed: UInt32 + var cacheSeed: UInt32 = .zero - var currentList: DisplayList + var currentList: DisplayList = .init() - // TBA init(platform: Platform) { self.platform = platform - self.map = [:] - self.reverseMap = [:] - self.removed = [] - self.animators = [:] - self.asyncValues = [:] - self.pendingAsyncValues = [:] - self.pendingAsyncUpdates = [] - self.index = DisplayList.Index() - self.cacheSeed = 0 - self.currentList = DisplayList() } mutating func clearAsyncValues() { From 7aeb2dcb2bf24a8afad6cf844e0f6655d78bf38a Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 30 Mar 2026 04:56:45 +0800 Subject: [PATCH 17/19] Update DisplayListViewPlatform --- .../Graphic/GraphicsContext.swift | 18 +++++++- .../DisplayList/DisplayListViewPlatform.swift | 44 ++++++++++++------- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/Sources/OpenSwiftUICore/Graphic/GraphicsContext.swift b/Sources/OpenSwiftUICore/Graphic/GraphicsContext.swift index e9e8fc340..ae1bcf36a 100644 --- a/Sources/OpenSwiftUICore/Graphic/GraphicsContext.swift +++ b/Sources/OpenSwiftUICore/Graphic/GraphicsContext.swift @@ -536,13 +536,28 @@ extension GraphicsContext { #endif } + // FIXME + struct ORBLayerFlags: OptionSet { + let rawValue: UInt32 + + init(rawValue: UInt32) { + self.rawValue = rawValue + } + } + + func drawLayer( + flags: ORBLayerFlags, + content: (inout GraphicsContext) throws -> Void + ) throws { + _openSwiftUIUnimplementedFailure() + } + package mutating func translateBy(x: CGFloat, y: CGFloat) { guard x != 0 || y != 0 else { return } _openSwiftUIUnimplementedFailure() } // FIXME - #if canImport(CoreGraphics) static func renderingTo( cgContext: CGContext, environment: EnvironmentValues, @@ -551,5 +566,4 @@ extension GraphicsContext { ) { _openSwiftUIUnimplementedFailure() } - #endif } diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift index f272e3403..6a6a35f22 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift @@ -2,16 +2,14 @@ // DisplayListViewPlatform.swift // OpenSwiftUICore // -// Audited for 6.0.87 -// Status: Blocked by PlatformDrawable and GraphicsContext +// Audited for 6.5.4 +// Status: Blocked by GraphicsContext and Platform // ID: 8BBC66CBE42B8A65F8A2F3799C81A349 (SwiftUICore) +public import OpenQuartzCoreShims import OpenSwiftUI_SPI -#if canImport(QuartzCore) -public import QuartzCore -#else -import Foundation -#endif + +// MARK: - PlatformViewDefinition @_spi(DisplayList_ViewSystem) @available(OpenSwiftUI_v6_0, *) @@ -71,6 +69,8 @@ open class PlatformViewDefinition: @unchecked Sendable { open class func setHitTestsAsOpaque(_ value: Bool, for view: AnyObject) { _openSwiftUIBaseClassAbstractMethod() } } +// MARK: - DisplayList.ViewUpdater.Platform Definition + extension DisplayList.ViewUpdater { package struct Platform { let rawValue: UInt @@ -89,6 +89,8 @@ extension DisplayList.ViewUpdater { } } +// MARK: - DisplayList.ViewUpdater.Platform API [WIP] + extension DisplayList.ViewUpdater.Platform { package init(definition: PlatformViewDefinition.Type) { self.init(rawValue: UInt(bitPattern: ObjectIdentifier(definition)) | UInt(definition.system.base.rawValue)) @@ -104,11 +106,13 @@ extension DisplayList.ViewUpdater.Platform { return unsafeBitCast(UInt8(rawValue & 3), to: ViewSystem.self) } - #if canImport(QuartzCore) package func viewLayer(_ view: AnyObject) -> CALayer { + #if canImport(QuartzCore) CoreViewLayer(system: viewSystem, view: view) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif } - #endif func updateDrawingView( _ drawingView: inout AnyObject, @@ -154,16 +158,26 @@ extension DisplayList.ViewUpdater.Platform { } } +// MARK: - DisplayList.GraphicsRenderer + Platform [WIP] + extension DisplayList.GraphicsRenderer { - #if canImport(Darwin) - final package func drawPlatformLayer(_ layer: CALayer, in ctx: GraphicsContext, size: CGSize, update: Bool) { + package func drawPlatformLayer( + _ layer: CALayer, + in ctx: GraphicsContext, + size: CGSize, + update: Bool + ) { + #if canImport(Darwin) if update { layer.bounds = CGRect(origin: .zero, size: size) layer.layoutIfNeeded() } - // TODO: Blocked by GraphicsContext - _openSwiftUIUnimplementedFailure() - // ctx.drawLayer + try? ctx.drawLayer(flags: []) { _ in + // TODO: Blocked by GraphicsContext + _openSwiftUIUnimplementedFailure() + } + #else + _openSwiftUIPlatformUnimplementedWarning() + #endif } - #endif } From 100e83296b74b01cae423b9d45aa5119188ff402 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 30 Mar 2026 04:58:57 +0800 Subject: [PATCH 18/19] Fix compile error --- .../Render/DisplayList/DisplayListViewCache.swift | 3 +++ .../Render/DisplayList/DisplayListViewRenderer.swift | 2 +- Sources/OpenSwiftUICore/Util/CoreGraphicsShims.swift | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift index 2ae40aa54..99a3d3181 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewCache.swift @@ -82,6 +82,7 @@ extension DisplayList.ViewUpdater { } mutating func clearAsyncValues() { + #if canImport(QuartzCore) for (layerID, asyncValueArray) in asyncValues { // Recover CALayer from ObjectIdentifier — the layer must still be alive // since ViewCache holds strong refs to views containing these layers. @@ -93,6 +94,7 @@ extension DisplayList.ViewUpdater { layer.remove(modifier) } } + #endif asyncValues = [:] asyncModifierGroup = nil } @@ -221,6 +223,7 @@ extension DisplayList.ViewUpdater { if case .shader = filter { item.addDrawingGroup(contentSeed: .init(item.version)) } + return .infinity case let .animation(animation): return prepareAnimation( animation, diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift index dfda16404..b3ebcace6 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewRenderer.swift @@ -220,7 +220,7 @@ extension DisplayList { guard let nextUpdate else { return nextUpdate } - let interval = max(min(nextTime, result) - time, configuration.minFrameInterval) + let interval = max(min(nextTime, nextUpdate) - time, configuration.minFrameInterval) return time + interval #endif } diff --git a/Sources/OpenSwiftUICore/Util/CoreGraphicsShims.swift b/Sources/OpenSwiftUICore/Util/CoreGraphicsShims.swift index b1c2958da..fbe830e86 100644 --- a/Sources/OpenSwiftUICore/Util/CoreGraphicsShims.swift +++ b/Sources/OpenSwiftUICore/Util/CoreGraphicsShims.swift @@ -3,6 +3,7 @@ // OpenSwiftUICore #if !canImport(CoreGraphics) +public import Foundation // MARK: - CAPresentationModifierGroup From 23432a77fb1719f90a554cbae96ad46ba85909d3 Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 31 Mar 2026 00:27:42 +0800 Subject: [PATCH 19/19] Rename CoreGraphicsShims to QuartzCoreShims and fix compile guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CAPresentationModifier and CAPresentationModifierGroup are QuartzCore types, not CoreGraphics — fix the compile guard from !canImport(CoreGraphics) to !canImport(QuartzCore) and rename the file accordingly. --- .../Util/{CoreGraphicsShims.swift => QuartzCoreShims.swift} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename Sources/OpenSwiftUICore/Util/{CoreGraphicsShims.swift => QuartzCoreShims.swift} (80%) diff --git a/Sources/OpenSwiftUICore/Util/CoreGraphicsShims.swift b/Sources/OpenSwiftUICore/Util/QuartzCoreShims.swift similarity index 80% rename from Sources/OpenSwiftUICore/Util/CoreGraphicsShims.swift rename to Sources/OpenSwiftUICore/Util/QuartzCoreShims.swift index fbe830e86..2d52013c3 100644 --- a/Sources/OpenSwiftUICore/Util/CoreGraphicsShims.swift +++ b/Sources/OpenSwiftUICore/Util/QuartzCoreShims.swift @@ -1,8 +1,8 @@ // -// CoreGraphicsShims.swift +// QuartzCoreShims.swift // OpenSwiftUICore -#if !canImport(CoreGraphics) +#if !canImport(QuartzCore) public import Foundation // MARK: - CAPresentationModifierGroup