created | url | author(s) | version | license(s) |
---|---|---|---|---|
2024-12-19 20:58:55 -0800 |
Cong Le |
1.0 |
MIT, CC BY 4.0 |
In this documentation, we will provide a comprehensive set of diagrams and illustrations explaining the functionalities and complexities of the Metal Primitives App. These diagrams are intended to serve as a reference for iOS developers who are interested in understanding the app's architecture, rendering pipeline, and how it implements advanced Metal rendering techniques across both iOS and macOS platforms.
- Table of Contents
- 1. High-Level Architecture Diagram
- 2. App Structure Overview
- 3. Class Diagram of View Controllers and Wrappers
- 4. App Initialization Sequence Diagram
- 5. Metal Views and Renderers Class Diagram
- 6. Metal View Rendering Flow Sequence Diagram
- 7. Platform-Specific View Creation Flowchart
- 8. Extensions and Utilities Relationships Diagram
- 9. Metal Rendering Process Flowchart
- 10. Metal View Hierarchy and Custom Views Diagram
- 11. Configurable References and Protocol Extensions Diagram
- 12. Core Graphics Extensions and Iterators Diagram
- 13. CAMetal2DView Class Diagram
- 14. CAMetal2DView Initialization and Rendering Sequence Diagram
- 15. CAMetal2DView Draw Method Flowchart
- 16. Shader Structures and Render Pipeline Diagram
- 17. Thread Safety and Synchronization Diagram
- Conclusion
This diagram provides an overview of the entire app's architecture, highlighting the conditional compilation for iOS and macOS platforms and how different views are integrated.
---
config:
layout: elk
look: handDrawn
theme: dark
---
graph TD
%% Define styles
classDef iOS fill:#43F6,stroke:#4285F4
classDef macOS fill:#F4F6,stroke:#34A853
%% App Structure
A["MetalPrimitivesApp<br>@main App"]
A -->|contains| B[WindowGroup]
%% Platform Conditional Views
B -->|Platform: iOS| C[iOS Views]
B -->|Platform: macOS| D[macOS Views]
%% iOS Views
subgraph iOS Views
direction TB
C1[ObjCMetalPlainViewControllerRepresentable]:::iOS
C2[MetalTexturingViewRepresentable]:::iOS
C3[MetalLightingViewRepresentable]:::iOS
C4[Metal3DViewRepresentable]:::iOS
C5[Metal2DViewRepresentable]:::iOS
C6[MetalPlainViewRepresentable]:::iOS
C7[iOS_ViewControllerRepresentable]:::iOS
C8[iOS_SwiftUI_RootContentView]:::iOS
end
C --> C1 & C2 & C3 & C4 & C5 & C6 & C7 & C8
%% macOS Views
subgraph macOS Views
direction TB
D1[MetalTexturingViewRepresentable]:::macOS
D2[MetalLightingViewRepresentable]:::macOS
D3[Metal3DViewRepresentable]:::macOS
D4[NSMetal2DViewRepresentable]:::macOS
D5[NSMetalPlainViewRepresentable]:::macOS
end
D --> D1 & D2 & D3 & D4 & D5
Explanation:
- The
MetalPrimitivesApp
uses aWindowGroup
to host the main content. - Based on the platform (iOS or macOS), it conditionally includes different views.
- The iOS Views and macOS Views are grouped under their respective platforms.
- Each platform includes a set of representable views that integrate Metal rendering into SwiftUI.
This class diagram illustrates the overall structure of the app, focusing on the relationships between the main app entry point, SwiftUI views, and UIKit/AppKit view controllers.
---
config:
layout: elk
look: handDrawn
theme: dark
---
classDiagram
%% Main App Entry Point
class MetalPrimitivesApp {
+var body: some Scene
}
MetalPrimitivesApp --> WindowGroup
%% SwiftUI Views
WindowGroup --> iOS_SwiftUI_RootContentView
iOS_SwiftUI_RootContentView --> ObjCMetalPlainViewControllerRepresentable
%% UIViewControllerRepresentable Wrapper
class ObjCMetalPlainViewControllerRepresentable {
+makeUIViewController()
+updateUIViewController()
}
ObjCMetalPlainViewControllerRepresentable ..|> UIViewControllerRepresentable
%% UIKit View Controllers
ObjCMetalPlainViewControllerRepresentable --> ObjCMetalPlainViewController
%% Typealiases and Base Classes
class MySwiftViewController
MySwiftViewController <|-- UIViewController
MySwiftViewController <|-- NSViewController
ObjCMetalPlainViewController <|-- MySwiftViewController
Explanation:
MetalPrimitivesApp
is the main entry point of the app, containing theWindowGroup
.iOS_SwiftUI_RootContentView
is the main SwiftUI view for iOS, which usesObjCMetalPlainViewControllerRepresentable
to bridge UIKit components.ObjCMetalPlainViewControllerRepresentable
conforms toUIViewControllerRepresentable
to integrate a UIKit view controller within SwiftUI.ObjCMetalPlainViewController
is an Objective-C view controller that handles Metal rendering.
This diagram shows how the SwiftUI views, UIKit/AppKit view controllers, and Objective-C view controllers interact.
---
config:
layout: elk
look: handDrawn
theme: dark
---
classDiagram
%% SwiftUI Views
class iOS_SwiftUI_RootContentView {
+body: some View
}
class iOS_SwiftUI_RootContentView:::SwiftUIView
class iOS_ViewControllerRepresentable {
+makeUIViewController()
+updateUIViewController()
}
class iOS_ViewControllerRepresentable:::UIViewControllerRepresentable
iOS_ViewControllerRepresentable ..|> UIViewControllerRepresentable
iOS_SwiftUI_RootContentView --> iOS_ViewControllerRepresentable
%% UIKit View Controller Wrapper
class ObjC_MetalPlainViewController_UIKitWrapperViewController {
+viewDidLoad()
+addChildViewController()
}
class ObjC_MetalPlainViewController_UIKitWrapperViewController:::ViewController
iOS_ViewControllerRepresentable --> ObjC_MetalPlainViewController_UIKitWrapperViewController : UIViewControllerType
%% Objective-C View Controller
class ObjCMetalPlainViewController {
+Metal rendering code
}
class ObjCMetalPlainViewController:::ObjectiveCVC
ObjC_MetalPlainViewController_UIKitWrapperViewController --> ObjCMetalPlainViewController : adds Child VC
%% Typealiases and Base Classes
class MySwiftViewController
MySwiftViewController <|-- UIViewController
MySwiftViewController <|-- NSViewController
ObjC_MetalPlainViewController_UIKitWrapperViewController <|-- MySwiftViewController
%%%%Note: GitHub does not support this ssyntax for now%%%%
%% Define styles
%%classDef UIViewControllerRepresentable fill:#E810F,stroke:#F54
%%classDef ViewController fill:#FCE8E,stroke:#E51
%%classDef SwiftUIView fill:#FFF7E,stroke:#F05
%%classDef ObjectiveCVC fill:#E6F4E,stroke:#F53
Explanation:
iOS_SwiftUI_RootContentView
is a SwiftUI view that includesiOS_ViewControllerRepresentable
.iOS_ViewControllerRepresentable
bridges the UIKit view controller (ObjC_MetalPlainViewController_UIKitWrapperViewController
) into SwiftUI.ObjC_MetalPlainViewController_UIKitWrapperViewController
is a UIKit view controller that adds the Objective-C Metal view controller as a child.ObjCMetalPlainViewController
is the Objective-C view controller that handles Metal rendering.
This sequence diagram illustrates the flow of control during the app's initialization, highlighting how views and view controllers are created and connected.
---
config:
layout: elk
look: handDrawn
theme: dark
---
sequenceDiagram
autonumber
participant App as MetalPrimitivesApp
participant WG as WindowGroup
participant View as iOS_SwiftUI_RootContentView
participant Wrapper as iOS_ViewControllerRepresentable
participant VC as ObjC_MetalPlainViewController_UIKitWrapperViewController
participant ObjC_VC as ObjCMetalPlainViewController
App->>+WG: Instantiate WindowGroup
WG->>+View: Instantiate iOS_SwiftUI_RootContentView
View->>+Wrapper: Instantiate iOS_ViewControllerRepresentable
Wrapper->>+VC: makeUIViewController()
VC->>+VC: viewDidLoad()
VC->>+ObjC_VC: addChild(ObjCMetalPlainViewController)
ObjC_VC->>+ObjC_VC: viewDidLoad()
VC-->>-Wrapper: didMove(toParent: VC)
Wrapper-->>-View: UIViewControllerType is VC
View-->>-WG: Body content is View
WG-->>-App: Display window with content
Explanation:
- The app starts by instantiating the
WindowGroup
. WindowGroup
creates theiOS_SwiftUI_RootContentView
.- The
iOS_SwiftUI_RootContentView
initializes theiOS_ViewControllerRepresentable
. iOS_ViewControllerRepresentable
creates theObjC_MetalPlainViewController_UIKitWrapperViewController
.- The wrapper view controller adds
ObjCMetalPlainViewController
as a child. - Each view controller's
viewDidLoad
method is called appropriately. - Control returns up the chain, and the window displays the content.
This diagram shows the relationship between the Metal views and their respective renderers.
---
config:
layout: elk
look: handDrawn
theme: dark
---
classDiagram
%% SwiftUI Representable Views
class Metal3DViewRepresentable {
+makeUIView()
+updateUIView()
+makeCoordinator()
}
class MetalLightingViewRepresentable {
+makeUIView()
+updateUIView()
+makeCoordinator()
}
class MetalTexturingViewRepresentable {
+makeUIView()
+updateUIView()
+makeCoordinator()
}
Metal3DViewRepresentable ..|> UIViewRepresentable
MetalLightingViewRepresentable ..|> UIViewRepresentable
MetalTexturingViewRepresentable ..|> UIViewRepresentable
%% Underlying Metal Views
class CAMetal3DView {
+device: MTLDevice
+renderer: RendererFor3DView
}
class MTKView
Metal3DViewRepresentable --> CAMetal3DView
MetalLightingViewRepresentable --> MTKView
MetalTexturingViewRepresentable --> MTKView
%% Coordinators (Renderers)
Metal3DViewRepresentable --> CubeRenderer : makeCoordinator()
MetalLightingViewRepresentable --> TeapotRenderer : makeCoordinator()
MetalTexturingViewRepresentable --> CowRenderer : makeCoordinator()
%% Renderers
class RendererFor3DView {
<<Protocol>>
+ device: MTLDevice
+ draw(layer: CAMetalLayer, time: (now: Double, display: Double))
}
RendererFor3DView <|.. CubeRenderer
RendererFor3DView <|.. TeapotRenderer
RendererFor3DView <|.. CowRenderer
CubeRenderer ..|> RendererFor3DView
TeapotRenderer ..|> MTKViewDelegate
CowRenderer ..|> MTKViewDelegate
Explanation:
- Each representable view conforms to
UIViewRepresentable
and integrates a Metal view into SwiftUI. Metal3DViewRepresentable
usesCAMetal3DView
, which utilizes a custom renderer (CubeRenderer
).MetalLightingViewRepresentable
andMetalTexturingViewRepresentable
useMTKView
and custom renderers (TeapotRenderer
andCowRenderer
respectively).- Renderers conform to either
RendererFor3DView
protocol orMTKViewDelegate
.
The following sequence diagram demonstrates how the Metal views are created and rendered within the app, showcasing the interaction between SwiftUI, UIViewRepresentable
, and the Metal rendering pipeline.
---
config:
layout: elk
look: handDrawn
theme: dark
---
sequenceDiagram
autonumber
participant SwiftUI as SwiftUI View
participant Representable as UIViewRepresentable
participant MetalView as CAMetal3DView / MTKView
participant Renderer as Renderer (Coordinator)
participant Device as MTLDevice
participant Queue as MTLCommandQueue
SwiftUI->>+Representable: makeUIView(context)
Representable->>+MetalView: Instantiate Metal View
MetalView->>+Device: Acquire MTLDevice
Device->>+Queue: Create MTLCommandQueue
Representable->>+Renderer: makeCoordinator()
MetalView->>Renderer: Set delegate / renderer
MetalView->>Renderer: draw(in: MetalView)
Renderer->>MetalView: Render Frame
Explanation:
- The SwiftUI view calls
makeUIView(context)
on theUIViewRepresentable
. - The representable creates an instance of the Metal view (
CAMetal3DView
orMTKView
). - The Metal view acquires the
MTLDevice
and creates aMTLCommandQueue
. - The representable creates a coordinator, which acts as the renderer.
- The Metal view sets its delegate or renderer.
- On each frame, the Metal view calls the renderer's
draw
method to render the frame.
This flowchart demonstrates how the code handles platform-specific view creation using conditional compilation.
---
config:
layout: elk
look: handDrawn
theme: dark
---
flowchart TD
%% Define styles
classDef ProcessStep fill:#F7E6,stroke:#FBBC05
Start([Start]):::ProcessStep
Start --> CheckOS{Is the OS iOS?}
class CheckOS decision
CheckOS -- Yes --> ImportUIKit[Import UIKit]:::ProcessStep
ImportUIKit --> DefineMySwiftViewController[Define MySwiftViewController<br>as UIViewController]:::ProcessStep
CheckOS -- No --> ImportAppKit[Import AppKit]:::ProcessStep
ImportAppKit --> DefineMySwiftViewControllerMac[Define MySwiftViewController<br>as NSViewController]:::ProcessStep
DefineMySwiftViewController --> Continue[Continue with Shared Logic]:::ProcessStep
DefineMySwiftViewControllerMac --> Continue
Continue --> ImplementViews[Implement Views using<br>UIKit or AppKit]:::ProcessStep
ImplementViews --> WrapViews[Wrap Views into SwiftUI using<br>UIViewRepresentable or NSViewRepresentable]:::ProcessStep
Explanation:
- The app starts and checks the operating system.
- If the OS is iOS, it imports UIKit and defines
MySwiftViewController
asUIViewController
. - If the OS is macOS, it imports AppKit and defines
MySwiftViewController
asNSViewController
. - Shared logic continues, and views are implemented using the appropriate framework.
- Views are wrapped into SwiftUI using the respective representable protocols.
The class diagram below shows how extensions and utilities are designed to add functionality to existing structures like CGPoint
, CGSize
, and CGRect
.
---
config:
layout: elk
look: handDrawn
theme: dark
---
classDiagram
%% Core Graphics Structures
class CGPoint {
+x: CGFloat
+y: CGFloat
}
class CGSize {
+width: CGFloat
+height: CGFloat
}
class CGRect {
+origin: CGPoint
+size: CGSize
}
%% Extensions
CGPoint ..|> Sequence
CGSize ..|> Sequence
CGRect ..|> Sequence
CGPoint ..|> ExpressibleByIntegerLiteral
CGSize ..|> ExpressibleByIntegerLiteral
CGRect ..|> ExpressibleByIntegerLiteral
CGPoint ..|> ExpressibleByFloatLiteral
CGSize ..|> ExpressibleByFloatLiteral
CGRect ..|> ExpressibleByFloatLiteral
CGPoint ..|> ExpressibleByArrayLiteral
CGSize ..|> ExpressibleByArrayLiteral
CGRect ..|> ExpressibleByArrayLiteral
%% Iterators
class PairIterator~T~
class QuadIterator~T~
CGPoint --> PairIterator
CGSize --> PairIterator
CGRect --> QuadIterator
Explanation:
CGPoint
,CGSize
, andCGRect
are extended to conform toSequence
and various literal protocols.- Custom iterators (
PairIterator
andQuadIterator
) are used to enable iteration over the components of these structures. - This adds syntactic sugar and convenience when working with these types in code.
This flowchart outlines the steps involved in the CAMetalPlainView
's rendering process.
---
config:
layout: elk
look: handDrawn
theme: dark
---
flowchart TD
%% Define styles
classDef ProcessStep fill:#E6F4,stroke:#34A853
classDef Decision fill:#FFF7,stroke:#FBBC05
Start([Start Drawing]):::ProcessStep
Start --> CheckDrawableSize{Is Drawable Size Valid?}:::Decision
CheckDrawableSize -- No --> End([Return]):::ProcessStep
CheckDrawableSize -- Yes --> GetDrawable[Get Next Drawable]:::ProcessStep
GetDrawable --> CreateCommandBuffer[Create Command Buffer]:::ProcessStep
CreateCommandBuffer --> CreateRenderPassDescriptor[Create Render Pass Descriptor]:::ProcessStep
CreateRenderPassDescriptor --> CreateRenderCommandEncoder[Create Render Command Encoder]:::ProcessStep
CreateRenderCommandEncoder --> EndEncoding[End Encoding]:::ProcessStep
EndEncoding --> PresentDrawable[Present Drawable]:::ProcessStep
PresentDrawable --> CommitCommandBuffer[Commit Command Buffer]:::ProcessStep
CommitCommandBuffer --> End([End Drawing]):::ProcessStep
Explanation:
- The rendering process starts by checking if the drawable size is valid.
- If valid, it proceeds to get the next drawable from the layer.
- A command buffer is created, along with a render pass descriptor.
- A render command encoder is created to encode rendering commands.
- After encoding, the encoder is ended.
- The drawable is presented, and the command buffer is committed.
- The process ends, ready for the next frame.
This class diagram shows the hierarchy and relationships between custom Metal views and their UIKit/AppKit counterparts, emphasizing the shared logic across platforms.
---
config:
layout: elk
look: handDrawn
theme: dark
---
classDiagram
%% Base Classes
class CAMetalPlainView
class CAMetal2DView
class CAMetal3DView
%% Platform-Specific Subclasses
CAMetalPlainView <|-- iOS_CAMetalPlainView
CAMetalPlainView <|-- macOS_CAMetalPlainView
CAMetal2DView <|-- iOS_CAMetal2DView
CAMetal2DView <|-- macOS_CAMetal2DView
CAMetal3DView <|-- iOS_CAMetal3DView
CAMetal3DView <|-- macOS_CAMetal3DView
%% Conformance
iOS_CAMetalPlainView ..|> UIView
macOS_CAMetalPlainView ..|> NSView
iOS_CAMetal2DView ..|> UIView
macOS_CAMetal2DView ..|> NSView
iOS_CAMetal3DView ..|> UIView
macOS_CAMetal3DView ..|> NSView
%% Renderers
class CubeRenderer
class TeapotRenderer
class CowRenderer
CAMetal3DView --> CubeRenderer
CAMetal2DView --> TeapotRenderer
CAMetalPlainView --> CowRenderer
Explanation:
- Custom Metal views (
CAMetalPlainView
,CAMetal2DView
,CAMetal3DView
) are subclassed for iOS and macOS. - These subclasses conform to their respective platform's view classes (
UIView
orNSView
). - Each Metal view uses a renderer that handles the drawing logic.
The class diagram below illustrates how protocols and extensions are used to provide configurable references across different types, enhancing code reusability and readability.
---
config:
layout: elk
look: handDrawn
theme: dark
---
classDiagram
%% Protocol
class ConfigurableReference {
<<protocol>>
+configure(block: (Self) -> Void): Self
}
%% Types Conforming to ConfigurableReference
NSObjectProtocol ..|> ConfigurableReference
MTLCommandQueue ..|> ConfigurableReference
CAMetalLayer ..|> ConfigurableReference
MTKView ..|> ConfigurableReference
MTLBuffer ..|> ConfigurableReference
NSLock ..|> ConfigurableReference
Explanation:
- The
ConfigurableReference
protocol allows objects to be configured using a closure. - Several classes conform to this protocol, making it convenient to chain configurations.
- The protocol extension provides a default implementation for any
NSObjectProtocol
conforming type.
This class diagram demonstrates how custom iterators are implemented for CGPoint
, CGSize
, and CGRect
, enabling them to conform to Sequence
and various literal protocols.
---
config:
layout: elk
look: handDrawn
theme: dark
---
classDiagram
%% Core Graphics Structures
class CGPoint {
+x: CGFloat
+y: CGFloat
}
class CGSize {
+width: CGFloat
+height: CGFloat
}
class CGRect {
+origin: CGPoint
+size: CGSize
}
%% Extensions
CGPoint ..|> Sequence
CGSize ..|> Sequence
CGRect ..|> Sequence
CGPoint ..|> ExpressibleByIntegerLiteral
CGSize ..|> ExpressibleByIntegerLiteral
CGRect ..|> ExpressibleByIntegerLiteral
CGPoint ..|> ExpressibleByFloatLiteral
CGSize ..|> ExpressibleByFloatLiteral
CGRect ..|> ExpressibleByFloatLiteral
CGPoint ..|> ExpressibleByArrayLiteral
CGSize ..|> ExpressibleByArrayLiteral
CGRect ..|> ExpressibleByArrayLiteral
%% Iterators
class PairIterator~T~
class QuadIterator~T~
CGPoint --> PairIterator
CGSize --> PairIterator
CGRect --> QuadIterator
This diagram shows the class hierarchy and composition of CAMetal2DView
and its inner class MetalState
.
---
config:
layout: elk
look: handDrawn
theme: dark
---
classDiagram
%% Base Class
class CAMetal2DView {
+state: MetalState
+draw(now: Double, frame: Double)
}
%% MetalState Inner Class
class MetalState {
-device: MTLDevice
-queue: MTLCommandQueue
-pipeline: MTLRenderPipelineState
-buffer: MTLBuffer
-layer: CAMetalLayer
-lock: NSLock
-_timer: FrameTimer?
+timer: FrameTimer?
}
%% Platform-Specific View Classes
class CAMetal2DViewMac {
+makeBackingLayer() : CALayer
+viewDidMoveToWindow()
+setBoundsSize(newSize: NSSize)
+setFrameSize(newSize: NSSize)
}
class CAMetal2DViewiOS {
+layerClass : AnyClass
+didMoveToWindow()
+layoutSubviews()
}
CAMetal2DView <|-- CAMetal2DViewMac : inherits
CAMetal2DView <|-- CAMetal2DViewiOS : inherits
%% Dependencies
CAMetal2DView --> MetalState : has a
MetalState --> MTLDevice
MetalState --> MTLCommandQueue
MetalState --> MTLRenderPipelineState
MetalState --> MTLBuffer
MetalState --> CAMetalLayer
MetalState --> NSLock
MetalState --> FrameTimer
%% ShaderVertexFor2DView Struct
class ShaderVertexFor2DView {
+position: float4
+color: float4
}
MetalState --> ShaderVertexFor2DView
Explanation:
CAMetal2DView
is a final class that represents the Metal view, with platform-specific subclasses for macOS (CAMetal2DViewMac
) and iOS (CAMetal2DViewiOS
).- The
MetalState
class encapsulates the Metal rendering state, including the device, command queue, render pipeline state, vertex buffer, Metal layer, and frame timer. MetalState
owns the Metal resources and manages synchronization using a lock.ShaderVertexFor2DView
is a struct representing the vertex data for rendering the triangle.
This diagram shows the class hierarchy and composition of CAMetal2DView
and its inner class MetalState
.
---
config:
layout: elk
look: handDrawn
theme: dark
---
classDiagram
%% Platform-Specific Classes
class CAMetal2DView {
+state: MetalState
+init(device: MTLDevice, queue: MTLCommandQueue)
+draw(now: Double, frame: Double)
}
%% MetalState Inner Class
class MetalState {
-device: MTLDevice
-queue: MTLCommandQueue
-pipeline: MTLRenderPipelineState
-buffer: MTLBuffer
-layerPointer: UnsafeMutablePointer<CAMetalLayer>
-lock: NSLock
-_timer: FrameTimer?
+init(device: MTLDevice, queue: MTLCommandQueue)
+layer: CAMetalLayer
+timer: FrameTimer?
}
%% Relationships between CAMetal2DView
%% and other Protocols and Extensions
CAMetal2DView --> MetalState : has
MetalState *-- CAMetalLayer : layerPointer
MetalState --> NSLock : lock
MetalState --> FrameTimer : timer
%% Relationship betwwen CAMetal2DView
%% and Platform Conditional Compilation
CAMetal2DView <|-- UIView : iOS
CAMetal2DView <|-- NSView : macOS
Explanation:
CAMetal2DView
is a custom view subclassing eitherUIView
(for iOS) orNSView
(for macOS) based on the platform.- It contains a
MetalState
instance, which holds the Metal rendering state and resources. MetalState
manages the Metal device, command queue, render pipeline state, vertex buffer, and a pointer to theCAMetalLayer
.- The
layerPointer
inMetalState
holds an unsafe pointer to theCAMetalLayer
associated with the view. - The
NSLock
is used to synchronize access to the_timer
property, which manages the rendering loop viaFrameTimer
.
This diagram highlights how protocols and extensions are used to enhance functionality and maintain code clarity.
---
config:
layout: elk
look: handDrawn
theme: dark
---
classDiagram
class ConfigurableReference {
<<protocol>>
+configure(block: (Self) -> Void) : Self
}
NSObjectProtocol ..|> ConfigurableReference
MTLBuffer ..|> ConfigurableReference
MTLRenderPassDescriptor ..|> ConfigurableReference
CAMetalLayer ..|> ConfigurableReference
NSLock ..|> ConfigurableReference
CAMetal2DView *-- MetalState
class MetalState {
+device: MTLDevice
+queue: MTLCommandQueue
+pipeline: MTLRenderPipelineState
+buffer: MTLBuffer
+layer: CAMetalLayer
+lock: NSLock
}
Explanation:
- The
ConfigurableReference
protocol provides aconfigure
method that allows for inline configuration of objects. - Extensions conforming various classes like
MTLBuffer
,MTLRenderPassDescriptor
,CAMetalLayer
, andNSLock
toConfigurableReference
enable cleaner and more readable code by chaining configuration calls. - This approach enhances code maintainability and expressiveness.
This diagram goes deeper into how the FrameTimer
, shaders, and rendering pipeline are set up and used.
---
config:
layout: elk
look: handDrawn
theme: dark
---
classDiagram
class CAMetal2DView {
+state: MetalState
+draw(now: Double, frame: Double)
}
CAMetal2DView --> MetalState
CAMetal2DView --> FrameTimer : uses
class MetalState {
+device: MTLDevice
+queue: MTLCommandQueue
+pipeline: MTLRenderPipelineState
+buffer: MTLBuffer
+layer: CAMetalLayer
+timer: FrameTimer?
}
MetalState --> MTLDevice
MetalState --> MTLCommandQueue
MetalState --> MTLRenderPipelineState
MetalState --> MTLBuffer
MetalState --> CAMetalLayer
MetalState --> FrameTimer
class ShaderLibrary
MTLDevice --> ShaderLibrary : makeDefaultLibrary()
ShaderLibrary --> VertexFunction : main_vertex_for_2D_view
ShaderLibrary --> FragmentFunction : main_fragment_for_2D_view
MTLRenderPipelineDescriptor --> VertexFunction
MTLRenderPipelineDescriptor --> FragmentFunction
MTLRenderPipelineDescriptor --> MTLRenderPipelineState : makeRenderPipelineState()
class MTLRenderPipelineDescriptor {
+vertexFunction
+fragmentFunction
+colorAttachments[0].pixelFormat
}
Explanation:
- The
MetalState
initializes the Metal pipeline by obtaining the default shader library from the device and setting up the vertex and fragment functions. - It creates a
MTLRenderPipelineDescriptor
, sets the functions and pixel format, and creates theMTLRenderPipelineState
. - The
FrameTimer
is used to synchronize the rendering with the display's refresh rate, calling thedraw
method on each frame.
This diagram highlights how CAMetal2DView
handles cross-platform support using conditional compilation.
---
config:
layout: elk
look: handDrawn
theme: dark
---
graph TD
CAMetal2DView
CAMetal2DView -->|"#if os(macOS)"| CAMetal2DViewMac
CAMetal2DView -->|"#elseif canImport(UIKit)"| CAMetal2DViewiOS
CAMetal2DViewMac --> AppKit
CAMetal2DViewiOS --> UIKit
Explanation:
CAMetal2DView
has platform-specific implementations for macOS and iOS, using#if
and#elseif
directives.- On macOS,
CAMetal2DView
subclassesNSView
and importsAppKit
. - On iOS,
CAMetal2DView
subclassesUIView
and importsUIKit
. - This allows the same class to be used on both platforms with the appropriate behavior and API usage.
This flowchart shows the conditional compilation and platform-specific implementations in CAMetal2DView
.
---
config:
layout: elk
look: handDrawn
theme: dark
---
flowchart TD
Start([Start])
Start --> CheckPlatform{Is Platform iOS?}
CheckPlatform -- Yes --> UseUIView[Subclass UIView]
UseUIView --> OverrideLayerClass
UseUIView --> didMoveToWindow["Override didMoveToWindow()"]
UseUIView --> layoutSubviews["Override layoutSubviews()"]
CheckPlatform -- No --> UseNSView[Subclass NSView]
UseNSView --> makeBackingLayer
UseNSView --> viewDidMoveToWindow["Override viewDidMoveToWindow()"]
UseNSView --> setBoundsSize["Override setBoundsSize()"]
UseNSView --> setFrameSize["Override setFrameSize()"]
Both --> InitializeState[Initialize MetalState]
Explanation:
- The code uses
#if
directives to differentiate between platforms. - On iOS,
CAMetal2DView
subclassesUIView
and overridesdidMoveToWindow()
andlayoutSubviews()
. - On macOS,
CAMetal2DView
subclassesNSView
and overridesviewDidMoveToWindow()
,setBoundsSize()
, andsetFrameSize()
. - Both implementations initialize
MetalState
and set up theCAMetalLayer
.
This diagram shows the sequence of events during initialization and the rendering loop of CAMetal2DView
.
---
config:
layout: elk
look: handDrawn
theme: dark
---
sequenceDiagram
autonumber
participant View as CAMetal2DView
participant State as MetalState
participant Layer as CAMetalLayer
participant Timer as FrameTimer
participant Device as MTLDevice
participant Queue as MTLCommandQueue
Note over View: Initialization
View->>+State: Initialize MetalState
State->>+Device: Create MTLDevice
State->>+Queue: Create MTLCommandQueue
State->>+Layer: Set up CAMetalLayer
View->>+View: didMoveToWindow()
View->>State: Set up FrameTimer
State->>+Timer: Initialize with callback
Timer-->>View: Callback on each frame
Note over View: Rendering Loop
loop Every Frame
Timer-->>+View: draw(now, frame)
View->>+Layer: Get next drawable
View->>State: Create command buffer
View->>State: Create render pass descriptor
View->>State: Create render command encoder
View->>State: Encode drawing commands
View->>State: End encoding
View->>State: Present drawable
View->>State: Commit command buffer
end
This flowchart details the steps taken within the draw(now: frame:)
method during each frame of rendering.
---
config:
layout: elk
look: handDrawn
theme: dark
---
flowchart TD
%% Define styles
classDef EndOfDrawMethod fill:#64EA,stroke:#34A853
classDef Decision fill:#F7E6,stroke:#FBBC05
classDef StartEndEncoding fill:#119F00, stroke:#333, stroke-width:2px
classDef CheckDrawableSize fill:#ff0000, stroke:#333, stroke-width:2px
classDef CommandBufferOperators fill:#00008B, stroke:#333, stroke-width:1px
classDef EncoderSettings fill:#8B8000, stroke:#333, stroke-width:1px
Start(["Start draw(now, frame)"]):::StartEndEncoding
Start --> CheckDrawableSize{Is drawable size valid?}:::CheckDrawableSize
CheckDrawableSize -- No --> End([Return]):::Decision
CheckDrawableSize -- Yes --> GetDrawable[Get next drawable]:::CommandBufferOperators
GetDrawable --> CreateCommandBuffer[Create command buffer]:::CommandBufferOperators
CreateCommandBuffer --> CreateRenderPassDescriptor[Create render pass descriptor]:::CommandBufferOperators
CreateRenderPassDescriptor --> CreateRenderCommandEncoder[Create render command encoder]:::CommandBufferOperators
CreateRenderCommandEncoder --> SetPipelineState[Set render pipeline state]:::EncoderSettings
SetPipelineState --> SetVertexBuffer[Set vertex buffer]:::EncoderSettings
SetVertexBuffer --> DrawPrimitives["Draw primitives (triangle)"]:::EncoderSettings
DrawPrimitives --> EndEncoding[End encoding]:::StartEndEncoding
EndEncoding --> PresentDrawable[Present drawable]:::CommandBufferOperators
PresentDrawable --> CommitCommandBuffer[Commit command buffer]:::CommandBufferOperators
CommitCommandBuffer --> End([End of draw method]):::EndOfDrawMethod
This class diagram illustrates the ShaderVertexFor2DView
struct and its role in the rendering pipeline.
---
config:
layout: elk
look: handDrawn
theme: dark
---
classDiagram
class ShaderVertexFor2DView {
+position: SIMD4<Float>
+color: SIMD4<Float>
}
class MTLBuffer
MetalState --> MTLBuffer : buffer
MTLBuffer --> ShaderVertexFor2DView
%% Shaders
class MTLLibrary
MetalState --> MTLLibrary : makeDefaultLibrary()
MTLLibrary --> VertexFunction : makeFunction(name "main_vertex_for_2D_view")
MTLLibrary --> FragmentFunction : makeFunction(name "main_fragment_for_2D_view")
MetalState --> MTLRenderPipelineDescriptor : descriptor
MTLRenderPipelineDescriptor --> VertexFunction
MTLRenderPipelineDescriptor --> FragmentFunction
Explanation:
ShaderVertexFor2DView
defines the vertex data structure used in the shaders.MTLBuffer
holds the vertex data.- The Metal library loads the shader functions used in the render pipeline.
- The pipeline descriptor references the vertex and fragment shader functions.
This sequence diagram illustrates how the FrameTimer
is used to synchronize rendering with the display's refresh rate.
---
config:
layout: elk
look: handDrawn
theme: dark
---
sequenceDiagram
participant View as CAMetal2DView
participant State as MetalState
participant Timer as FrameTimer
participant Display
View->>State: Initialize MetalState
View->>State: Set timer to nil
View->>View: didMoveToWindow / viewDidMoveToWindow
View->>State: Set up layer properties
State->>State: Set timer with FrameTimer
Timer-->>View: draw(now, frame)
View->>Display: Render frame
loop Every Frame
Display-->>Timer: VSync Signal
Timer-->>View: draw(now, frame)
View->>Display: Render frame
end
Explanation:
- When the view moves to the window, it initializes the
FrameTimer
in theMetalState
. - The
FrameTimer
invokes thedraw(now:frame:)
method on every display refresh (VSync). - This ensures smooth frame updates synchronized with the display's refresh rate.
- The rendering loop continues as long as the timer is active.
This diagram shows how the vertex data is set up and supplied to the vertex shader.
---
config:
layout: elk
look: handDrawn
theme: dark
---
flowchart LR
Start[Start_Initialization]
--> CreateVertices[Create Vertices Array]
--> CalculateLength[Calculate Buffer Length]
--> CreateBuffer[Create MTLBuffer with Vertex Data]
--> SetBufferLabel[Set Buffer Label]
--> UseBuffer[Use Buffer in Encoder]
--> VertexShader[Vertex Shader Receives Data]
subgraph Vertex_Data [Vertex_Data]
ShaderVertexFor2DView1((Top Vertex))
ShaderVertexFor2DView2((Left Vertex))
ShaderVertexFor2DView3((Right Vertex))
end
CreateVertices --> Vertex_Data
Explanation:
- An array of
ShaderVertexFor2DView
structs is created, representing the vertices of a triangle. - Each vertex includes position and color data.
- The total length is calculated based on the number of vertices and the size of the struct.
- A
MTLBuffer
is created with the vertex data and labeled. - The buffer is set in the render command encoder for use in the vertex shader.
This diagram shows the steps involved in initializing the MetalState
object.
---
config:
layout: elk
look: handDrawn
theme: dark
---
flowchart TD
%%%%%% Define styles
%% Define style for crtitical points on the flowchart
classDef Decision fill:#F7E6,stroke:#FBBC05,stroke-width:2px
classDef StartEndInitialization fill:#119F00, stroke:#333, stroke-width:2px
classDef ReturnNil fill:#7222F1, stroke:#34A853, stroke-width:2px
%% Define style for question
classDef CheckLibrary fill:#ff0000, stroke:#333, stroke-width:2px
classDef CheckPipelineState fill:#ff0000, stroke:#333, stroke-width:2px
classDef CheckBuffer fill:#ff0000, stroke:#333, stroke-width:2px
%% Define style for operators
classDef PipelineStateOperators fill:#00008B, stroke:#333, stroke-width:1px
classDef VertexBufferOperators fill:#8B8000, stroke:#333, stroke-width:1px
classDef OtherCommandOperators fill:#2F23, stroke:#333, stroke-width:1px
%%%%%% Starting the flowchart
Start([Start MetalState.init]):::StartEndInitialization
Start --> CheckLibrary{Make Default Library?}:::CheckLibrary
CheckLibrary -- No --> ReturnNil[Return nil]
CheckLibrary -- Yes --> GetFunctions[Get Vertex and Fragment Functions]:::PipelineStateOperators
CreateDescriptor[Create Render Pipeline Descriptor]:::PipelineStateOperators
GetFunctions --> CreateDescriptor
CreatePipelineState[Create Render Pipeline State]:::PipelineStateOperators
CreateDescriptor --> CreatePipelineState
CreatePipelineState --> CheckPipelineState{Pipeline State Created?}:::CheckPipelineState
CheckPipelineState -- No --> ReturnNil
CheckPipelineState -- Yes --> CreateVertices[Create Vertex Data]:::VertexBufferOperators
CreateVertices --> CreateBuffer[Create Vertex Buffer]:::VertexBufferOperators
CreateBuffer --> CheckBuffer{Buffer Created?}:::CheckBuffer
CheckBuffer -- No --> ReturnNil:::ReturnNil
CheckBuffer -- Yes --> InitLayerPointer[Initialize Layer Pointer]:::OtherCommandOperators
InitLayerPointer --> InitLock[Initialize NSLock]:::OtherCommandOperators
InitLock --> End([End Initialization]):::StartEndInitialization
Explanation:
- The
MetalState
initializer first attempts to create the default Metal library. - It retrieves the vertex and fragment functions needed for the pipeline.
- A render pipeline descriptor is created using these functions.
- The render pipeline state is created from the descriptor.
- Vertex data for the triangle is created and stored in a buffer.
- An unsafe pointer to the
CAMetalLayer
is allocated. - An
NSLock
is initialized for thread safety. - If any step fails, the initializer returns
nil
, indicating failure.
This diagram illustrates how the MetalState
class manages thread safety and synchronization using a lock when accessing the timer
property.
classDiagram
class MetalState {
-lock: NSLock
-_timer: FrameTimer?
+timer: FrameTimer?
+set timer(FrameTimer?)
+get timer() FrameTimer?
}
MetalState o-- NSLock : uses
class NSLock {
+lock()
+unlock()
+withLock(block)
}
%% Accessing Timer Property
MetalState --> "lock/unlock" NSLock : when accessing timer
Explanation:
MetalState
uses anNSLock
to ensure thread safety when accessing or modifying the_timer
property.- The
timer
getter and setter methods use the lock to synchronize access. - This prevents race conditions and ensures that the timer is safely managed across threads.
The provided diagrams offer a comprehensive visual representation of the Metal Primitives App's architecture, rendering pipeline, and class relationships. By examining these diagrams, developers can gain a deeper understanding of:
- How SwiftUI integrates with UIKit and AppKit through representable protocols.
- The rendering process using Metal, including device and command queue setup, pipeline configuration, and shader usage.
- The handling of platform-specific code using conditional compilation.
- The advanced rendering techniques implemented, such as lighting and texturing in custom shaders.
- The importance of thread safety and synchronization in managing rendering loops and state.
These visual aids serve as valuable references for developers looking to explore or extend the functionalities of the app, providing insights into best practices for cross-platform Metal rendering applications.