├── doc ├── halo.png ├── step.png ├── Helvetica.png ├── drop_shadow.png ├── plot_halo.png ├── plot_step.png ├── slope_step.png ├── smooth_step.png ├── trapezoid.png ├── twin_peaks.png ├── pass_through.png ├── StarWarsARBanner.png ├── plot_slope_step.png ├── plot_smooth_step.png ├── plot_trapezoid.png ├── plot_twin_peaks.png ├── StarWarsMacBanner.png ├── plot_pass_through.png └── generate_plots.py ├── SWOpeningRoll ├── ios │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── MetalView.swift │ └── RenderCoordinator.swift ├── macos │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── SWOpeningRollmacos.entitlements │ ├── MetalView.swift │ └── RenderCoordinator.swift ├── SWOpeningRoll.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── SWOpeningRollios.xcscheme │ │ └── SWOpeningRollmacos.xcscheme ├── shared │ ├── ContentView.swift │ ├── SWOpeningRollApp.swift │ ├── AnimationSequencer.swift │ ├── AnimationConstants.swift │ ├── SDTextPlane.swift │ └── WorldManager.swift └── rendering │ ├── shaders │ ├── render_vertex_ins.h │ ├── render_uniforms.h │ └── default_renderer.metal │ ├── FloatUtil.swift │ ├── Camera.swift │ ├── VertexDescriptorGenerator.swift │ ├── RenderVertexIns.swift │ ├── PerSceneRenderHelper.swift │ └── RenderUniforms.swift ├── SWOpeningRollAR ├── SWOpeningRollAR │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Textures.xcassets │ │ ├── Contents.json │ │ └── texture01.textureset │ │ │ ├── Universal.mipmapset │ │ │ ├── texture512.png │ │ │ └── Contents.json │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── SWOpeningRollARApp.swift │ ├── ContentView.swift │ ├── TouchMTKView.swift │ ├── ShaderCameraImage.metal │ ├── MetalView.swift │ ├── ARCoordinator.swift │ └── PhotoImageRenderer.swift ├── SWOpeningRollAR.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── rendering │ ├── Camera.swift │ ├── shaders │ │ ├── render_vertex_ins.h │ │ ├── render_uniforms.h │ │ └── default_renderer.metal │ ├── FloatUtil.swift │ ├── VertexDescriptorGenerator.swift │ ├── RenderVertexIns.swift │ ├── PerSceneRenderHelper.swift │ └── RenderUniforms.swift └── demo │ ├── AnimationSequencer.swift │ ├── AnimationConstants.swift │ └── SDTextPlane.swift ├── SDFont ├── SDFont.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── SDFont.xcscheme │ │ └── sdfontgen.xcscheme ├── SDFont │ ├── SDFontUtilities.swift │ ├── SDFont.docc │ │ └── SDFont.md │ ├── SDFont.h │ ├── MetalZeroInitializer.swift │ ├── MetalComputeBase.swift │ ├── SDFontGlyphBounds.swift │ ├── SDFontSignedDistanceGenerator.swift │ ├── SDFontDownSampler.swift │ ├── SDFontBinaryPixmapGenerator.swift │ ├── SDFont.metal │ ├── SDFontMetalSourceCode.swift │ ├── SDFontIOHandler.swift │ ├── SDFontRuntimeHelper.swift │ └── SDFontGlyphPackingFinder.swift ├── build_all.sh └── sdfontgen │ └── main.swift └── .gitignore /doc/halo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/halo.png -------------------------------------------------------------------------------- /doc/step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/step.png -------------------------------------------------------------------------------- /doc/Helvetica.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/Helvetica.png -------------------------------------------------------------------------------- /doc/drop_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/drop_shadow.png -------------------------------------------------------------------------------- /doc/plot_halo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/plot_halo.png -------------------------------------------------------------------------------- /doc/plot_step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/plot_step.png -------------------------------------------------------------------------------- /doc/slope_step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/slope_step.png -------------------------------------------------------------------------------- /doc/smooth_step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/smooth_step.png -------------------------------------------------------------------------------- /doc/trapezoid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/trapezoid.png -------------------------------------------------------------------------------- /doc/twin_peaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/twin_peaks.png -------------------------------------------------------------------------------- /doc/pass_through.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/pass_through.png -------------------------------------------------------------------------------- /doc/StarWarsARBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/StarWarsARBanner.png -------------------------------------------------------------------------------- /doc/plot_slope_step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/plot_slope_step.png -------------------------------------------------------------------------------- /doc/plot_smooth_step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/plot_smooth_step.png -------------------------------------------------------------------------------- /doc/plot_trapezoid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/plot_trapezoid.png -------------------------------------------------------------------------------- /doc/plot_twin_peaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/plot_twin_peaks.png -------------------------------------------------------------------------------- /doc/StarWarsMacBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/StarWarsMacBanner.png -------------------------------------------------------------------------------- /doc/plot_pass_through.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/doc/plot_pass_through.png -------------------------------------------------------------------------------- /SWOpeningRoll/ios/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SWOpeningRoll/macos/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/Textures.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SWOpeningRoll/ios/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SWOpeningRoll/macos/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SDFont/SDFont.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SWOpeningRoll/SWOpeningRoll.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SWOpeningRoll/ios/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SWOpeningRoll/macos/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/Textures.xcassets/texture01.textureset/Universal.mipmapset/texture512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShoYamanishi/SDFontApple/HEAD/SWOpeningRollAR/SWOpeningRollAR/Textures.xcassets/texture01.textureset/Universal.mipmapset/texture512.png -------------------------------------------------------------------------------- /SWOpeningRoll/shared/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | 5 | @EnvironmentObject var worldManager : WorldManager 6 | 7 | var body: some View { 8 | VStack { 9 | MetalView() 10 | }//.padding() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SWOpeningRoll/ios/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/Textures.xcassets/texture01.textureset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "textures" : [ 7 | { 8 | "filename" : "Universal.mipmapset", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/Textures.xcassets/texture01.textureset/Universal.mipmapset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "levels" : [ 7 | { 8 | "filename" : "texture512.png", 9 | "mipmap-level" : "base" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /SDFont/SDFont/SDFontUtilities.swift: -------------------------------------------------------------------------------- 1 | func roundUp16( _ v : Int ) -> Int { 2 | return (v + 15) / 16 * 16 3 | } 4 | 5 | func roundUp16( _ v : Int32 ) -> Int32 { 6 | return (v + 15) / 16 * 16 7 | } 8 | 9 | func clamp( low : T, val : T, high : T ) -> T { 10 | return max( low, min( high, val ) ) 11 | } 12 | -------------------------------------------------------------------------------- /SDFont/SDFont.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SDFont/SDFont/SDFont.docc/SDFont.md: -------------------------------------------------------------------------------- 1 | # ``SDFont`` 2 | 3 | Summary 4 | 5 | ## Overview 6 | 7 | Text 8 | 9 | ## Topics 10 | 11 | ### Group 12 | 13 | - ``Symbol`` -------------------------------------------------------------------------------- /SWOpeningRoll/SWOpeningRoll.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SWOpeningRoll/macos/SWOpeningRollmacos.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SDFont/SDFont/SDFont.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | //! Project version number for SDFont. 5 | FOUNDATION_EXPORT double SDFontVersionNumber; 6 | 7 | //! Project version string for SDFont. 8 | FOUNDATION_EXPORT const unsigned char SDFontVersionString[]; 9 | 10 | // In this header, you should import all the public headers of your framework using statements like #import 11 | 12 | 13 | -------------------------------------------------------------------------------- /SWOpeningRoll/shared/SWOpeningRollApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct SWOpeningRollApp: App { 5 | 6 | let worldManager : WorldManager 7 | 8 | init() { 9 | worldManager = WorldManager( device: MTLCreateSystemDefaultDevice()! ) 10 | } 11 | 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView().environmentObject( worldManager ) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/SWOpeningRollARApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct SWOpeningRollARApp: App { 5 | 6 | let worldManager : WorldManager! 7 | 8 | init() { 9 | worldManager = WorldManager( device: MTLCreateSystemDefaultDevice()! ) 10 | } 11 | 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView().environmentObject( worldManager ) 15 | } 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /SWOpeningRollAR/rendering/Camera.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | import simd 4 | 5 | class Camera { 6 | 7 | var projectionMatrix: float4x4 8 | var viewMatrixLHS: float4x4 9 | 10 | init () { 11 | self.projectionMatrix = float4x4.identity() 12 | self.viewMatrixLHS = float4x4.identity() 13 | } 14 | 15 | init ( projectionMatrix: float4x4, viewMatrixLHS: float4x4 ) { 16 | self.projectionMatrix = projectionMatrix 17 | self.viewMatrixLHS = viewMatrixLHS 18 | } 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | 5 | @EnvironmentObject var worldManager: WorldManager 6 | 7 | @Environment(\.verticalSizeClass) var verticalSizeClass 8 | 9 | var body: some View { 10 | 11 | if verticalSizeClass == .compact { 12 | HStack { 13 | MetalView().padding() 14 | } 15 | 16 | } else { 17 | VStack(alignment: .center) { 18 | MetalView().padding() 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SWOpeningRoll/rendering/shaders/render_vertex_ins.h: -------------------------------------------------------------------------------- 1 | #ifndef __RENDER_VERTEX_INS_H__ 2 | #define __RENDER_VERTEX_INS_H__ 3 | 4 | struct VertexInPositionUV { 5 | 6 | // MemoryLayout.stride 32 7 | // MemoryLayout.size 24 8 | // MemoryLayout.alignment 16 9 | 10 | float4 position [[ attribute( 0 ) ]]; // size 16, alignment 16 11 | 12 | float2 uv [[ attribute( 1 ) ]]; // size 8, alignment 8 13 | 14 | //float _padding_01_; // size 4, alignment 4 15 | //float _padding_02_; // size 4, alignment 4 16 | }; 17 | 18 | #endif /*__RENDER_VERTEX_INS_H__*/ 19 | -------------------------------------------------------------------------------- /SWOpeningRollAR/rendering/shaders/render_vertex_ins.h: -------------------------------------------------------------------------------- 1 | #ifndef __RENDER_VERTEX_INS_H__ 2 | #define __RENDER_VERTEX_INS_H__ 3 | 4 | struct VertexInPositionUV { 5 | 6 | // MemoryLayout.stride 32 7 | // MemoryLayout.size 24 8 | // MemoryLayout.alignment 16 9 | 10 | float4 position [[ attribute( 0 ) ]]; // size 16, alignment 16 11 | 12 | float2 uv [[ attribute( 1 ) ]]; // size 8, alignment 8 13 | 14 | //float _padding_01_; // size 4, alignment 4 15 | //float _padding_02_; // size 4, alignment 4 16 | }; 17 | 18 | #endif /*__RENDER_VERTEX_INS_H__*/ 19 | -------------------------------------------------------------------------------- /SWOpeningRoll/rendering/FloatUtil.swift: -------------------------------------------------------------------------------- 1 | import simd 2 | 3 | extension float4x4 { 4 | 5 | 6 | static func identity() -> float4x4 { 7 | matrix_identity_float4x4 8 | } 9 | 10 | init( fov: Float, aspect: Float, near: Float, far: Float,lhs: Bool = true ) { 11 | 12 | let y = 1 / tan(fov * 0.5) 13 | let x = y / aspect 14 | let z = lhs ? far / (far - near) : far / (near - far) 15 | let X = SIMD4( x, 0.0, 0.0, 0.0) 16 | let Y = SIMD4( 0.0, y, 0.0, 0.0) 17 | let Z = lhs ? SIMD4( 0.0, 0.0, z, 1.0 ) : SIMD4( 0.0, 0.0, z, -1.0 ) 18 | let W = lhs ? SIMD4( 0.0, 0.0, -1.0*near*z, 0.0 ) : SIMD4( 0.0, 0.0, z * near, 0.0 ) 19 | 20 | self.init() 21 | columns = ( X, Y, Z, W ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SWOpeningRollAR/rendering/FloatUtil.swift: -------------------------------------------------------------------------------- 1 | import simd 2 | 3 | extension float4x4 { 4 | 5 | 6 | static func identity() -> float4x4 { 7 | matrix_identity_float4x4 8 | } 9 | 10 | init( fov: Float, aspect: Float, near: Float, far: Float,lhs: Bool = true ) { 11 | 12 | let y = 1 / tan(fov * 0.5) 13 | let x = y / aspect 14 | let z = lhs ? far / (far - near) : far / (near - far) 15 | let X = SIMD4( x, 0.0, 0.0, 0.0) 16 | let Y = SIMD4( 0.0, y, 0.0, 0.0) 17 | let Z = lhs ? SIMD4( 0.0, 0.0, z, 1.0 ) : SIMD4( 0.0, 0.0, z, -1.0 ) 18 | let W = lhs ? SIMD4( 0.0, 0.0, -1.0*near*z, 0.0 ) : SIMD4( 0.0, 0.0, z * near, 0.0 ) 19 | 20 | self.init() 21 | columns = ( X, Y, Z, W ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SWOpeningRoll/rendering/Camera.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | import simd 4 | 5 | class Camera { 6 | 7 | var fov : Float = 0.3 * Float.pi 8 | var aspect : Float = 1.0 9 | var near : Float = 0.01 // Do not make this too small, otherwise the depth buffer gets messed up 10 | var far : Float = 1000.0 // Do not make this too large, otherwise the depth buffer gets messed up 11 | 12 | func updateCameraDimension( dimension: SIMD2 ) { 13 | aspect = dimension.x / dimension.y 14 | } 15 | 16 | var projectionMatrix: float4x4 { 17 | return float4x4( fov: fov, aspect: aspect, near: near, far: far ) 18 | } 19 | 20 | var viewMatrixLHS: float4x4 { 21 | 22 | var toLHS = float4x4.identity() 23 | toLHS.columns.2.z = -1.0 24 | 25 | return toLHS 26 | } 27 | } 28 | 29 | 30 | -------------------------------------------------------------------------------- /SWOpeningRoll/rendering/VertexDescriptorGenerator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | 4 | extension MTLVertexDescriptor { 5 | 6 | static let float4Stride = MemoryLayout>.stride 7 | static let float3Stride = MemoryLayout>.stride 8 | static let float2Stride = MemoryLayout>.stride 9 | 10 | 11 | static func positionUV() -> MTLVertexDescriptor { 12 | 13 | let d = MTLVertexDescriptor() 14 | 15 | d.attributes[0].format = .float4 16 | d.attributes[0].offset = 0 17 | d.attributes[0].bufferIndex = 0 18 | 19 | d.attributes[1].format = .float2 20 | d.attributes[1].offset = float4Stride 21 | d.attributes[1].bufferIndex = 0 22 | 23 | d.layouts[0].stride = float4Stride + float4Stride 24 | d.layouts[0].stepFunction = .perVertex 25 | 26 | return d 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /SWOpeningRollAR/rendering/VertexDescriptorGenerator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | 4 | extension MTLVertexDescriptor { 5 | 6 | static let float4Stride = MemoryLayout>.stride 7 | static let float3Stride = MemoryLayout>.stride 8 | static let float2Stride = MemoryLayout>.stride 9 | 10 | 11 | static func positionUV() -> MTLVertexDescriptor { 12 | 13 | let d = MTLVertexDescriptor() 14 | 15 | d.attributes[0].format = .float4 16 | d.attributes[0].offset = 0 17 | d.attributes[0].bufferIndex = 0 18 | 19 | d.attributes[1].format = .float2 20 | d.attributes[1].offset = float4Stride 21 | d.attributes[1].bufferIndex = 0 22 | 23 | d.layouts[0].stride = float4Stride + float4Stride 24 | d.layouts[0].stepFunction = .perVertex 25 | 26 | return d 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /SWOpeningRoll/macos/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/TouchMTKView.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | 4 | protocol TouchMTKViewDelegate { 5 | func touchesBegan( location: CGPoint, size: CGRect ) 6 | func touchesMoved( location: CGPoint, size: CGRect ) 7 | func touchesEnded( location: CGPoint, size: CGRect ) 8 | } 9 | 10 | 11 | // Decorator to MTKView in order to forward touches to the delegate 12 | class TouchMTKView: MTKView { 13 | 14 | var touchDelegate : TouchMTKViewDelegate? 15 | 16 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 17 | if touches.count > 0 { 18 | let loc = touches.first!.location( in: self ) 19 | touchDelegate?.touchesBegan( location: loc, size: self.frame ) 20 | } 21 | } 22 | 23 | override func touchesMoved(_ touches: Set, with event: UIEvent?) { 24 | if touches.count > 0 { 25 | let loc = touches.first!.location( in: self ) 26 | touchDelegate?.touchesMoved( location: loc, size: self.frame ) 27 | } 28 | } 29 | 30 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 31 | if touches.count > 0 { 32 | let loc = touches.first!.location( in: self ) 33 | touchDelegate?.touchesEnded( location: loc, size: self.frame ) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SDFont/SDFont/MetalZeroInitializer.swift: -------------------------------------------------------------------------------- 1 | import MetalKit 2 | 3 | class ZeroInitializer : MetalComputeBase { 4 | 5 | struct Config { 6 | var lengthInInt32: Int32 7 | } 8 | 9 | override init( device : MTLDevice ) { 10 | super.init( device : device ) 11 | createPipelineState( functionName : "initialize_with_zero") 12 | } 13 | 14 | func perform( buffer: MTLBuffer, lengthInInt32 : Int ) { 15 | 16 | var config = Config( lengthInInt32 : Int32(lengthInInt32) ) 17 | 18 | guard let commandBuffer = queue!.makeCommandBuffer() else { 19 | print ("ERROR: Cannot make command buffer for ZeroInitializer.") 20 | return 21 | } 22 | 23 | let encoder = commandBuffer.makeComputeCommandEncoder() 24 | 25 | encoder!.setComputePipelineState( pipelineState! ) 26 | 27 | encoder!.setBytes( &config, length: MemoryLayout.stride, index: 0 ) 28 | encoder!.setBuffer( buffer, offset: 0, index: 1 ) 29 | 30 | encoder!.dispatchThreadgroups( 31 | MTLSizeMake( 1, 1, 1 ), 32 | threadsPerThreadgroup: MTLSizeMake( Self.ThreadsPerGroup, 1, 1 ) 33 | ) 34 | encoder!.endEncoding() 35 | commandBuffer.commit() 36 | commandBuffer.waitUntilCompleted() 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /SWOpeningRoll/rendering/RenderVertexIns.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | 4 | struct VertexInIndex { 5 | 6 | // MemoryLayout.stride 4 7 | // MemoryLayout.size 4 8 | // MemoryLayout.alignment 4 9 | var index: Int32 10 | 11 | static func generateMTLBuffer( device: MTLDevice, indices : [Int32] ) -> MTLBuffer? { 12 | return device.makeBuffer(bytes: indices, length: MemoryLayout.stride * indices.count, options: .storageModeShared ) 13 | } 14 | } 15 | 16 | struct VertexInPositionUV { 17 | 18 | // MemoryLayout.stride 32 19 | // MemoryLayout.size 24 20 | // MemoryLayout.alignment 16 21 | var position : SIMD4 22 | var uv : SIMD2 23 | 24 | static func generateMTLBuffer( device: MTLDevice, positions : [SIMD4], uvs: [SIMD2] ) -> MTLBuffer? { 25 | var V : [Self] = [] 26 | for i in 0 ..< positions.count { 27 | V.append( Self( position: positions[i], uv: uvs[i] ) ) 28 | } 29 | return Self.generateMTLBuffer( device: device, vertices : V ) 30 | } 31 | 32 | static func generateMTLBuffer( device: MTLDevice, vertices : [Self] ) -> MTLBuffer? { 33 | return device.makeBuffer(bytes: vertices, length: MemoryLayout.stride * vertices.count, options: .storageModeShared ) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /SWOpeningRollAR/rendering/RenderVertexIns.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | 4 | struct VertexInIndex { 5 | 6 | // MemoryLayout.stride 4 7 | // MemoryLayout.size 4 8 | // MemoryLayout.alignment 4 9 | var index: Int32 10 | 11 | static func generateMTLBuffer( device: MTLDevice, indices : [Int32] ) -> MTLBuffer? { 12 | return device.makeBuffer(bytes: indices, length: MemoryLayout.stride * indices.count, options: .storageModeShared ) 13 | } 14 | } 15 | 16 | struct VertexInPositionUV { 17 | 18 | // MemoryLayout.stride 32 19 | // MemoryLayout.size 24 20 | // MemoryLayout.alignment 16 21 | var position : SIMD4 22 | var uv : SIMD2 23 | 24 | static func generateMTLBuffer( device: MTLDevice, positions : [SIMD4], uvs: [SIMD2] ) -> MTLBuffer? { 25 | var V : [Self] = [] 26 | for i in 0 ..< positions.count { 27 | V.append( Self( position: positions[i], uv: uvs[i] ) ) 28 | } 29 | return Self.generateMTLBuffer( device: device, vertices : V ) 30 | } 31 | 32 | static func generateMTLBuffer( device: MTLDevice, vertices : [Self] ) -> MTLBuffer? { 33 | return device.makeBuffer(bytes: vertices, length: MemoryLayout.stride * vertices.count, options: .storageModeShared ) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /SWOpeningRoll/rendering/shaders/render_uniforms.h: -------------------------------------------------------------------------------- 1 | #ifndef __RENDER_UNIFORMS_H__ 2 | #define __RENDER_UNIFORMS_H__ 3 | 4 | using namespace metal; 5 | 6 | struct UniformPerInstance { 7 | 8 | // MemoryLayout.stride 64 9 | // MemoryLayout.size 64 10 | // MemoryLayout.alignment 16 11 | 12 | float4x4 model_matrix; // size 64, alignment 16 13 | }; 14 | 15 | struct UniformPerScene { 16 | 17 | // MemoryLayout.stride 160 18 | // MemoryLayout.size 148 19 | // MemoryLayout.alignment 16 20 | 21 | float4x4 view_matrix; // size 64, alignment 16 22 | float4x4 projection_matrix; // size 64, alignment 16 23 | float3 camera_position; // size 16, alignment 16 24 | int32_t num_lights; // size 4, alignment 4 25 | int32_t _padding_01_; // size 4, alignment 4 26 | int32_t _padding_02_; // size 4, alignment 4 27 | int32_t _padding_03_; // size 4, alignment 4 28 | }; 29 | 30 | typedef enum { 31 | 32 | SDFONT_PASS_THROUGH = 0, 33 | SDFONT_STEP = 1, 34 | SDFONT_SMOOTH_STEP = 2, 35 | SDFONT_SLOPE_STEP = 3, 36 | SDFONT_TRAPEZOID = 4, 37 | SDFONT_TWIN_PEAKS = 5, 38 | SDFONT_HALO = 6 39 | 40 | } SDFontFunctionType; 41 | 42 | 43 | struct UniformSDFont { 44 | float4 foreground_color; 45 | int func_type; 46 | float width; 47 | float point1; 48 | float point2; 49 | }; 50 | 51 | 52 | #endif /* __RENDER_UNIFORMS_H__ */ 53 | -------------------------------------------------------------------------------- /SWOpeningRollAR/rendering/shaders/render_uniforms.h: -------------------------------------------------------------------------------- 1 | #ifndef __RENDER_UNIFORMS_H__ 2 | #define __RENDER_UNIFORMS_H__ 3 | 4 | using namespace metal; 5 | 6 | struct UniformPerInstance { 7 | 8 | // MemoryLayout.stride 64 9 | // MemoryLayout.size 64 10 | // MemoryLayout.alignment 16 11 | 12 | float4x4 model_matrix; // size 64, alignment 16 13 | }; 14 | 15 | struct UniformPerScene { 16 | 17 | // MemoryLayout.stride 160 18 | // MemoryLayout.size 148 19 | // MemoryLayout.alignment 16 20 | 21 | float4x4 view_matrix; // size 64, alignment 16 22 | float4x4 projection_matrix; // size 64, alignment 16 23 | float3 camera_position; // size 16, alignment 16 24 | int32_t num_lights; // size 4, alignment 4 25 | int32_t _padding_01_; // size 4, alignment 4 26 | int32_t _padding_02_; // size 4, alignment 4 27 | int32_t _padding_03_; // size 4, alignment 4 28 | }; 29 | 30 | typedef enum { 31 | 32 | SDFONT_PASS_THROUGH = 0, 33 | SDFONT_STEP = 1, 34 | SDFONT_SMOOTH_STEP = 2, 35 | SDFONT_SLOPE_STEP = 3, 36 | SDFONT_TRAPEZOID = 4, 37 | SDFONT_TWIN_PEAKS = 5, 38 | SDFONT_HALO = 6 39 | 40 | } SDFontFunctionType; 41 | 42 | 43 | struct UniformSDFont { 44 | float4 foreground_color; 45 | int func_type; 46 | float width; 47 | float point1; 48 | float point2; 49 | }; 50 | 51 | 52 | #endif /* __RENDER_UNIFORMS_H__ */ 53 | -------------------------------------------------------------------------------- /SDFont/build_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | xcodebuild archive -scheme SDFont -configuration Release -destination 'generic/platform=iOS' -archivePath './build/SDFont.framework-iphoneos.xcarchive' SKIP_INSTALL=NO BUILD_LIBRARIES_FOR_DISTRIBUTION=YES 3 | xcodebuild archive -scheme SDFont -configuration Release -destination 'generic/platform=iOS Simulator' -archivePath './build/SDFont.framework-iphonesimulator.xcarchive' SKIP_INSTALL=NO BUILD_LIBRARIES_FOR_DISTRIBUTION=YES 4 | xcodebuild archive -scheme SDFont -configuration Release -sdk macosx -archivePath './build/SDFont.framework-macos.xcarchive' SKIP_INSTALL=NO BUILD_LIBRARIES_FOR_DISTRIBUTION=YES 5 | xcodebuild -create-xcframework -framework './build/SDFont.framework-iphoneos.xcarchive/Products/Library/Frameworks/SDFont.framework' -framework './build/SDFont.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/SDFont.framework' -framework './build/SDFont.framework-macos.xcarchive/Products/Library/Frameworks/SDFont.framework' -output './build/SDFont.xcframework' 6 | xcodebuild docbuild -scheme SDFont -derivedDataPath ./build 7 | cp -r ./build/Build/Products/Release/SDFont.doccarchive . 8 | xcodebuild archive -scheme sdfontgen -configuration Release -sdk macosx -archivePath './build/sdfontgen' SKIP_INSTALL=NO 9 | cp build/sdfontgen.xcarchive/Products/usr/local/bin/sdfontgen ./build/ 10 | 11 | while true; do 12 | read -p "Do you wish to install SDFont.framework--macosx to this Mac under /Library/Frameworks?" yn 13 | case $yn in 14 | [Yy]* ) sudo cp -r build/SDFont.xcframework/macos-arm64_x86_64/SDFont.framework /Library/Frameworks; break;; 15 | [Nn]* ) exit;; 16 | * ) echo "Please answer yes or no.";; 17 | esac 18 | done 19 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/ShaderCameraImage.metal: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace metal; 3 | 4 | typedef struct { 5 | float2 position [[ attribute(0) ]]; 6 | float2 texCoord [[ attribute(1) ]]; 7 | } ImageVertex; 8 | 9 | 10 | typedef struct { 11 | float4 position [[position]]; 12 | float2 texCoord; 13 | } ImageColorInOut; 14 | 15 | 16 | // Captured image vertex function 17 | vertex ImageColorInOut capturedImageVertexTransform(ImageVertex in [[stage_in]]) { 18 | ImageColorInOut out; 19 | 20 | // Pass through the image vertex's position 21 | out.position = float4(in.position, 0.0, 1.0); 22 | 23 | // Pass through the texture coordinate 24 | out.texCoord = in.texCoord; 25 | 26 | return out; 27 | } 28 | 29 | // Captured image fragment function 30 | fragment float4 capturedImageFragmentShader(ImageColorInOut in [[stage_in]], 31 | texture2d capturedImageTextureY [[ texture(1) ]], 32 | texture2d capturedImageTextureCbCr [[ texture(2) ]]) { 33 | 34 | constexpr sampler colorSampler(mip_filter::linear, 35 | mag_filter::linear, 36 | min_filter::linear); 37 | 38 | const float4x4 ycbcrToRGBTransform = float4x4( 39 | float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f), 40 | float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f), 41 | float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f), 42 | float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f) 43 | ); 44 | 45 | // Sample Y and CbCr textures to get the YCbCr color at the given texture coordinate 46 | float4 ycbcr = float4(capturedImageTextureY.sample(colorSampler, in.texCoord).r, 47 | capturedImageTextureCbCr.sample(colorSampler, in.texCoord).rg, 1.0); 48 | 49 | // Return converted RGB color 50 | return ycbcrToRGBTransform * ycbcr; 51 | } 52 | 53 | -------------------------------------------------------------------------------- /SWOpeningRoll/macos/MetalView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import MetalKit 3 | 4 | struct MetalView: NSViewRepresentable { 5 | 6 | @EnvironmentObject var worldManager: WorldManager 7 | 8 | func makeCoordinator() -> Coordinator { 9 | 10 | Coordinator(self) 11 | } 12 | 13 | func makeNSView( context: NSViewRepresentableContext ) -> MTKView { 14 | 15 | let mtkView = MTKView() 16 | 17 | mtkView.delegate = context.coordinator 18 | mtkView.preferredFramesPerSecond = 60 19 | mtkView.device = worldManager.device 20 | mtkView.framebufferOnly = false 21 | mtkView.clearColor = MTLClearColor( red: 0, green: 0, blue: 0, alpha: 0 ) 22 | mtkView.drawableSize = mtkView.frame.size 23 | //mtkView.enableSetNeedsDisplay = true 24 | mtkView.depthStencilPixelFormat = .depth32Float 25 | 26 | context.coordinator.renderCoordinator.createPipelineStates( colorPixelFormat: mtkView.colorPixelFormat ) 27 | context.coordinator.mtkView( mtkView, drawableSizeWillChange: mtkView.bounds.size ) 28 | 29 | return mtkView 30 | } 31 | 32 | func updateNSView(_ nsView: MTKView, context: NSViewRepresentableContext ) { 33 | 34 | } 35 | 36 | class Coordinator : NSObject, MTKViewDelegate { 37 | 38 | let renderCoordinator : RenderCoordinator 39 | let parent : MetalView 40 | 41 | init( _ parent: MetalView ) { 42 | 43 | self.renderCoordinator = RenderCoordinator( worldManager : parent.worldManager, device: parent.worldManager.device ) 44 | self.parent = parent 45 | 46 | super.init() 47 | } 48 | 49 | func mtkView( _ view : MTKView, drawableSizeWillChange size: CGSize ) { 50 | 51 | renderCoordinator.mtkView( view, drawableSizeWillChange: size ) 52 | } 53 | 54 | func draw( in view: MTKView ) { 55 | 56 | renderCoordinator.draw( in: view ) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SWOpeningRoll/ios/MetalView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import MetalKit 3 | 4 | struct MetalView: UIViewRepresentable { 5 | 6 | @EnvironmentObject var worldManager: WorldManager 7 | 8 | func makeCoordinator() -> Coordinator { 9 | 10 | return Coordinator( self ) 11 | } 12 | 13 | func makeUIView( context: UIViewRepresentableContext ) -> MTKView { 14 | 15 | let mtkView = MTKView() 16 | 17 | mtkView.delegate = context.coordinator 18 | mtkView.preferredFramesPerSecond = 60 19 | mtkView.device = MTLCreateSystemDefaultDevice() 20 | mtkView.framebufferOnly = false 21 | mtkView.clearColor = MTLClearColor( red: 0, green: 0, blue: 0, alpha: 0 ) 22 | mtkView.drawableSize = mtkView.frame.size 23 | mtkView.enableSetNeedsDisplay = false 24 | mtkView.depthStencilPixelFormat = .depth32Float 25 | 26 | context.coordinator.renderCoordinator.createPipelineStates( colorPixelFormat: mtkView.colorPixelFormat ) 27 | context.coordinator.mtkView( mtkView, drawableSizeWillChange: mtkView.bounds.size ) 28 | 29 | return mtkView 30 | } 31 | 32 | func updateUIView( _ uiView: MTKView, context: UIViewRepresentableContext ) { 33 | 34 | } 35 | 36 | class Coordinator : NSObject, MTKViewDelegate { 37 | 38 | let renderCoordinator : RenderCoordinator 39 | let parent : MetalView 40 | 41 | init( _ parent: MetalView ) { 42 | 43 | self.renderCoordinator = RenderCoordinator( worldManager : parent.worldManager, device: parent.worldManager.device ) 44 | self.parent = parent 45 | 46 | super.init() 47 | } 48 | 49 | func mtkView( _ view: MTKView, drawableSizeWillChange size: CGSize ) { 50 | 51 | renderCoordinator.mtkView( view, drawableSizeWillChange: size ) 52 | } 53 | 54 | func draw( in view: MTKView ) { 55 | renderCoordinator.draw( in: view ) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /SDFont/SDFont/MetalComputeBase.swift: -------------------------------------------------------------------------------- 1 | import MetalKit 2 | 3 | class MetalComputeBase { 4 | 5 | #if os(iOS) 6 | static let ThreadsPerGroup = 512 7 | #else 8 | static let ThreadsPerGroup = 1024 9 | #endif 10 | 11 | let device : MTLDevice 12 | var queue : MTLCommandQueue? 13 | var pipelineState : MTLComputePipelineState? 14 | 15 | init( device : MTLDevice ) { 16 | 17 | self.device = device 18 | self.queue = nil 19 | self.pipelineState = nil 20 | 21 | guard let queue = device.makeCommandQueue() 22 | else { 23 | print ("ERROR: Cannot make a Metal command queue.") 24 | return 25 | } 26 | self.queue = queue 27 | } 28 | 29 | func createPipelineState( functionName : String) { 30 | 31 | do { 32 | // var options = MTLCompileOptions() 33 | // options.fastMathEnabled = false 34 | let library = try device.makeLibrary(source: SDFontMetalSourceCode, options: nil ) 35 | 36 | let desc = MTLComputePipelineDescriptor() 37 | desc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true 38 | desc.computeFunction = library.makeFunction( name: functionName ) 39 | do { 40 | pipelineState = try device.makeComputePipelineState( descriptor: desc, options: [], reflection : nil ) 41 | } 42 | catch { 43 | print ( "ERROR: Cannot make pipeline state for \(functionName)" ) 44 | return 45 | } 46 | } catch { 47 | fatalError("ERROR: Cannot compile metal code from the baked string.") 48 | } 49 | } 50 | 51 | static func getThreadConfiguration( numThreads: Int ) -> ( numGroupsPerGrid: Int, numThreadsPerGroup: Int ) { 52 | 53 | let TPG = Self.ThreadsPerGroup 54 | 55 | let numThreadsAligned32 = Int( ( ( numThreads + 31 ) / 32 ) * 32 ) 56 | let numThreadsPerGroup = Int( ( numThreadsAligned32 < TPG ) ? numThreadsAligned32 : TPG ) 57 | let numGroupsPerGrid = Int( ( numThreadsAligned32 + TPG - 1 ) / TPG ) 58 | 59 | return ( numGroupsPerGrid: numGroupsPerGrid, numThreadsPerGroup: numThreadsPerGroup ) 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /SWOpeningRoll/shared/AnimationSequencer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class AnimationSequencer { 4 | 5 | var timeStart : TimeInterval 6 | 7 | let temporalPoints : [TimeInterval] 8 | let spacialPoints : [SIMD3] 9 | let colors : [SIMD4] 10 | 11 | var timeNow : TimeInterval 12 | var spacialPointNow : SIMD3 13 | var colorNow : SIMD4 14 | var animationActive : Bool 15 | 16 | init( 17 | temporalPoints : [TimeInterval], 18 | spacialPoints : [SIMD3], 19 | colors : [SIMD4] 20 | ) { 21 | self.timeStart = Date().timeIntervalSince1970 22 | self.temporalPoints = temporalPoints 23 | self.spacialPoints = spacialPoints 24 | self.colors = colors 25 | self.timeNow = timeStart 26 | self.spacialPointNow = spacialPoints[0] 27 | self.colorNow = colors[0] 28 | self.animationActive = false 29 | } 30 | 31 | func startAnimation() { 32 | timeStart = Date().timeIntervalSince1970 33 | timeNow = timeStart 34 | spacialPointNow = spacialPoints[0] 35 | colorNow = colors[0] 36 | animationActive = true 37 | } 38 | 39 | func step() { 40 | 41 | let timeNow = Date().timeIntervalSince1970 42 | let timeSinceStart = timeNow - timeStart 43 | 44 | for i in 1 ..< temporalPoints.count { 45 | 46 | if temporalPoints[i] > timeSinceStart { 47 | 48 | // interpolate between temporalPoints[i-1] and temporalPoints[i] 49 | 50 | let alpha = Float( ( timeSinceStart - temporalPoints[ i - 1 ] ) 51 | / ( temporalPoints[ i ] - temporalPoints[ i - 1 ] ) ) 52 | 53 | spacialPointNow = spacialPoints[ i - 1 ] * ( 1.0 - alpha ) + spacialPoints[i] * alpha 54 | colorNow = colors[ i - 1 ] * ( 1.0 - alpha ) + colors[i] * alpha 55 | return 56 | } 57 | } 58 | animationActive = false 59 | spacialPointNow = spacialPoints[ temporalPoints.count - 1 ] 60 | colorNow = colors [ temporalPoints.count - 1 ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /SWOpeningRollAR/demo/AnimationSequencer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class AnimationSequencer { 4 | 5 | var timeStart : TimeInterval 6 | 7 | let temporalPoints : [TimeInterval] 8 | let spacialPoints : [SIMD3] 9 | let colors : [SIMD4] 10 | 11 | var timeNow : TimeInterval 12 | var spacialPointNow : SIMD3 13 | var colorNow : SIMD4 14 | var animationActive : Bool 15 | 16 | init( 17 | temporalPoints : [TimeInterval], 18 | spacialPoints : [SIMD3], 19 | colors : [SIMD4] 20 | ) { 21 | self.timeStart = Date().timeIntervalSince1970 22 | self.temporalPoints = temporalPoints 23 | self.spacialPoints = spacialPoints 24 | self.colors = colors 25 | self.timeNow = timeStart 26 | self.spacialPointNow = spacialPoints[0] 27 | self.colorNow = colors[0] 28 | self.animationActive = false 29 | } 30 | 31 | func startAnimation() { 32 | timeStart = Date().timeIntervalSince1970 33 | timeNow = timeStart 34 | spacialPointNow = spacialPoints[0] 35 | colorNow = colors[0] 36 | animationActive = true 37 | } 38 | 39 | func step() { 40 | 41 | let timeNow = Date().timeIntervalSince1970 42 | let timeSinceStart = timeNow - timeStart 43 | 44 | for i in 1 ..< temporalPoints.count { 45 | 46 | if temporalPoints[i] > timeSinceStart { 47 | 48 | // interpolate between temporalPoints[i-1] and temporalPoints[i] 49 | 50 | let alpha = Float( ( timeSinceStart - temporalPoints[ i - 1 ] ) 51 | / ( temporalPoints[ i ] - temporalPoints[ i - 1 ] ) ) 52 | 53 | spacialPointNow = spacialPoints[ i - 1 ] * ( 1.0 - alpha ) + spacialPoints[i] * alpha 54 | colorNow = colors[ i - 1 ] * ( 1.0 - alpha ) + colors[i] * alpha 55 | return 56 | } 57 | } 58 | animationActive = false 59 | spacialPointNow = spacialPoints[ temporalPoints.count - 1 ] 60 | colorNow = colors [ temporalPoints.count - 1 ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/MetalView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import MetalKit 3 | 4 | 5 | // This is based on https://developer.apple.com/forums/thread/119112?answerId=654964022#654964022 6 | struct MetalView: UIViewRepresentable { 7 | 8 | @EnvironmentObject var worldManager: WorldManager 9 | 10 | class Coordinator : NSObject, MTKViewDelegate { 11 | 12 | let arCoordinator : ARCoordinator 13 | let parent : MetalView 14 | 15 | init( _ parent: MetalView ) { 16 | 17 | self.arCoordinator = ARCoordinator( worldManager : parent.worldManager, device: parent.worldManager.device ) 18 | self.parent = parent 19 | 20 | super.init() 21 | 22 | parent.worldManager.arCoordinator = arCoordinator 23 | } 24 | 25 | func mtkView( _ view: MTKView, drawableSizeWillChange size: CGSize ) { 26 | 27 | arCoordinator.mtkView( view, drawableSizeWillChange: size ) 28 | } 29 | 30 | func draw( in view: MTKView ) { 31 | arCoordinator.draw( in: view ) 32 | } 33 | } 34 | 35 | func makeCoordinator() -> Coordinator { 36 | 37 | return Coordinator( self ) 38 | } 39 | 40 | func makeUIView( context: UIViewRepresentableContext ) -> MTKView { 41 | 42 | let mtkView = TouchMTKView() 43 | 44 | mtkView.delegate = context.coordinator 45 | mtkView.touchDelegate = worldManager 46 | mtkView.preferredFramesPerSecond = 60 47 | mtkView.device = context.coordinator.arCoordinator.device 48 | mtkView.framebufferOnly = true 49 | mtkView.clearColor = MTLClearColor( red: 0, green: 0, blue: 0, alpha: 0 ) 50 | mtkView.drawableSize = mtkView.frame.size 51 | mtkView.enableSetNeedsDisplay = false 52 | mtkView.depthStencilPixelFormat = .depth32Float 53 | 54 | context.coordinator.arCoordinator.createPipelineStates( mtkView: mtkView ) 55 | context.coordinator.mtkView( mtkView, drawableSizeWillChange: mtkView.frame.size ) 56 | 57 | return mtkView 58 | } 59 | 60 | func updateUIView( _ uiView: MTKView, context: UIViewRepresentableContext ) { 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SDFont/SDFont/SDFontGlyphBounds.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | 3 | struct SignedDistanceFontGlyphBounds { 4 | 5 | let outer : CGRect // with spread. Integer-aligned 6 | let inner : CGRect // glyph bounds from CoreText 7 | let spreadThickness : CGFloat // spread in pixels 8 | let textureSideLen : CGFloat // length of the dies of the texture in which this glyph resides. 9 | 10 | func normalizedInnerBound( flipY: Bool ) -> CGRect { 11 | 12 | let adjustedOriginY = flipY ? (textureSideLen - (inner.origin.y + inner.size.height)) : inner.origin.y 13 | 14 | return CGRect( 15 | x : inner.origin.x / textureSideLen, 16 | y : adjustedOriginY / textureSideLen, 17 | width : inner.size.width / textureSideLen, 18 | height : inner.size.height / textureSideLen 19 | ) 20 | } 21 | 22 | init( outerOrigin : CGPoint, outerSize : CGSize, spreadThickness : CGFloat, innerSize : CGSize, textureSideLen : CGFloat ) { 23 | 24 | self.outer = CGRect( 25 | 26 | x : outerOrigin.x, 27 | y : outerOrigin.y, 28 | width : outerSize.width, 29 | height : outerSize.height 30 | ) 31 | 32 | self.inner = CGRect( 33 | 34 | x : outerOrigin.x + spreadThickness, 35 | y : outerOrigin.y + spreadThickness, 36 | width : innerSize.width, 37 | height : innerSize.height 38 | ) 39 | 40 | self.spreadThickness = spreadThickness 41 | self.textureSideLen = textureSideLen 42 | } 43 | 44 | func localizeAndUpSample( upSamplingFactor : CGFloat ) -> Self { 45 | 46 | let converted = SignedDistanceFontGlyphBounds( 47 | 48 | outerOrigin : CGPoint(x: 0, y: 0 ), 49 | 50 | outerSize : CGSize( width: self.outer.size.width * upSamplingFactor, 51 | height: self.outer.size.height * upSamplingFactor ), 52 | 53 | spreadThickness : self.spreadThickness * upSamplingFactor, 54 | 55 | innerSize : CGSize( width: self.inner.size.width * upSamplingFactor, 56 | height: self.inner.size.height * upSamplingFactor ), 57 | 58 | textureSideLen : self.textureSideLen * upSamplingFactor 59 | ) 60 | 61 | return converted 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SWOpeningRoll/macos/RenderCoordinator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | 4 | class RenderCoordinator: NSObject, MTKViewDelegate { 5 | 6 | var device: MTLDevice! 7 | var commandQueue: MTLCommandQueue! 8 | var colorPixelFormat: MTLPixelFormat! 9 | var worldManager: WorldManager? 10 | 11 | var previousFrameSize: CGSize // to detect screen dimension change. SwiftUI does not always call mtkView(). 12 | 13 | init( worldManager: WorldManager, device : MTLDevice ) { 14 | 15 | self.worldManager = worldManager 16 | self.device = worldManager.device 17 | guard 18 | let commandQueue = device.makeCommandQueue() 19 | else { 20 | fatalError("GPU not available") 21 | } 22 | self.commandQueue = commandQueue 23 | self.previousFrameSize = CGSize( width: 0.0, height: 0.0 ) 24 | 25 | super.init() 26 | } 27 | 28 | /// 2nd part of init. It must wait until device and the pixel format become available in MTKView 29 | func createPipelineStates( colorPixelFormat: MTLPixelFormat ) { 30 | worldManager?.createPipelineStates( colorPixelFormat: colorPixelFormat ) 31 | } 32 | 33 | // MARK: - MTKViewDelegate 34 | 35 | func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize ) { 36 | 37 | self.colorPixelFormat = view.colorPixelFormat 38 | worldManager?.updateScreenSizes( view ) 39 | } 40 | 41 | func draw( in view: MTKView ) { 42 | 43 | if previousFrameSize != view.frame.size { 44 | previousFrameSize = view.frame.size 45 | self.mtkView( view, drawableSizeWillChange: view.frame.size ) 46 | } 47 | 48 | worldManager?.updateWorld() 49 | 50 | guard 51 | let descriptor = view.currentRenderPassDescriptor, 52 | let commandBuffer = commandQueue.makeCommandBuffer(), 53 | let encoder = commandBuffer.makeRenderCommandEncoder( descriptor: descriptor ) 54 | else { 55 | return 56 | } 57 | 58 | worldManager?.encode( encoder: encoder ) 59 | 60 | encoder.endEncoding() 61 | 62 | guard let drawable = view.currentDrawable 63 | else { 64 | return 65 | } 66 | commandBuffer.present( drawable ) 67 | commandBuffer.commit() 68 | commandBuffer.waitUntilCompleted() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /SWOpeningRoll/ios/RenderCoordinator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | 4 | 5 | class RenderCoordinator: NSObject, MTKViewDelegate { 6 | 7 | var device: MTLDevice! 8 | var commandQueue: MTLCommandQueue! 9 | var colorPixelFormat: MTLPixelFormat! 10 | var worldManager: WorldManager? 11 | 12 | var previousFrameSize: CGSize // to detect screen dimension change. SwiftUI does not always call mtkView(). 13 | 14 | init( worldManager: WorldManager, device : MTLDevice ) { 15 | 16 | self.worldManager = worldManager 17 | self.device = worldManager.device 18 | guard 19 | let commandQueue = device.makeCommandQueue() 20 | else { 21 | fatalError("GPU not available") 22 | } 23 | self.commandQueue = commandQueue 24 | 25 | self.previousFrameSize = CGSize( width: 0.0, height: 0.0 ) 26 | 27 | super.init() 28 | 29 | } 30 | 31 | /// 2nd part of init. It must wait until device and the pixel format become available in MTKView 32 | func createPipelineStates( colorPixelFormat: MTLPixelFormat ) { 33 | worldManager?.createPipelineStates( colorPixelFormat: colorPixelFormat ) 34 | } 35 | 36 | // MARK: - MTKViewDelegate 37 | 38 | func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize ) { 39 | 40 | self.colorPixelFormat = view.colorPixelFormat 41 | worldManager?.updateScreenSizes( view ) 42 | } 43 | 44 | func draw( in view: MTKView ) { 45 | 46 | if previousFrameSize != view.frame.size { 47 | previousFrameSize = view.frame.size 48 | self.mtkView( view, drawableSizeWillChange: view.frame.size ) 49 | } 50 | 51 | worldManager?.updateWorld() 52 | 53 | guard 54 | let descriptor = view.currentRenderPassDescriptor, 55 | let commandBuffer = commandQueue.makeCommandBuffer(), 56 | let encoder = commandBuffer.makeRenderCommandEncoder( descriptor: descriptor ) 57 | else { 58 | return 59 | } 60 | 61 | worldManager?.encode( encoder: encoder ) 62 | 63 | encoder.endEncoding() 64 | 65 | guard let drawable = view.currentDrawable 66 | else { 67 | return 68 | } 69 | commandBuffer.present( drawable ) 70 | commandBuffer.commit() 71 | commandBuffer.waitUntilCompleted() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SDFont/SDFont/SDFontSignedDistanceGenerator.swift: -------------------------------------------------------------------------------- 1 | import MetalKit 2 | 3 | class SDFontSignedDistanceGenerator : MetalComputeBase { 4 | 5 | struct Config { 6 | 7 | var drawAreaSideLen : Int32 8 | var spreadThickness : Float 9 | var width : Int32 10 | var height : Int32 11 | 12 | init( drawAreaSideLen : Int32, spreadThickness : Float ) { 13 | self.drawAreaSideLen = drawAreaSideLen 14 | self.spreadThickness = spreadThickness 15 | self.width = 0 16 | self.height = 0 17 | } 18 | } 19 | 20 | var config : Config 21 | var signedDistanceBuffer : MTLBuffer! 22 | 23 | init( device : MTLDevice, drawAreaSideLen : Int , spreadThickness : Float ) { 24 | 25 | self.signedDistanceBuffer = device.makeBuffer( 26 | length: drawAreaSideLen * drawAreaSideLen * MemoryLayout.stride, 27 | options: .storageModeShared 28 | ) 29 | 30 | config = Config( drawAreaSideLen : Int32(drawAreaSideLen), spreadThickness : spreadThickness ) 31 | 32 | super.init( device : device ) 33 | 34 | createPipelineState( functionName : "generate_signed_distance" ) 35 | } 36 | 37 | func generate( pixmapBuffer: MTLBuffer, glyphBounds : SignedDistanceFontGlyphBounds ) { 38 | 39 | config.width = Int32( glyphBounds.outer.size.width ) 40 | config.height = Int32( glyphBounds.outer.size.height ) 41 | 42 | guard let commandBuffer = queue!.makeCommandBuffer() else { 43 | print ("ERROR: Cannot make command buffer for SigneDistanceGenerator.") 44 | return 45 | } 46 | 47 | let encoder = commandBuffer.makeComputeCommandEncoder() 48 | 49 | encoder!.setComputePipelineState( pipelineState! ) 50 | 51 | encoder!.setBytes( &config, length: MemoryLayout.stride, index: 0 ) 52 | encoder!.setBuffer( pixmapBuffer, offset: 0, index: 1 ) 53 | encoder!.setBuffer( signedDistanceBuffer, offset: 0, index: 2 ) 54 | 55 | let threadConfig = MetalComputeBase.getThreadConfiguration( numThreads: Int( config.width * config.height ) ) 56 | encoder!.dispatchThreadgroups( 57 | MTLSizeMake( threadConfig.numGroupsPerGrid, 1, 1 ), 58 | threadsPerThreadgroup: MTLSizeMake( threadConfig.numThreadsPerGroup, 1, 1 ) 59 | ) 60 | encoder!.endEncoding() 61 | commandBuffer.commit() 62 | commandBuffer.waitUntilCompleted() 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /SDFont/SDFont.xcodeproj/xcshareddata/xcschemes/SDFont.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 34 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /SDFont/SDFont.xcodeproj/xcshareddata/xcschemes/sdfontgen.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 44 | 46 | 52 | 53 | 54 | 55 | 61 | 63 | 69 | 70 | 71 | 72 | 74 | 75 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /SWOpeningRoll/SWOpeningRoll.xcodeproj/xcshareddata/xcschemes/SWOpeningRollios.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /SWOpeningRoll/SWOpeningRoll.xcodeproj/xcshareddata/xcschemes/SWOpeningRollmacos.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /SDFont/SDFont/SDFontDownSampler.swift: -------------------------------------------------------------------------------- 1 | import MetalKit 2 | 3 | class Downsampler : MetalComputeBase { 4 | 5 | struct Config { 6 | 7 | // per-glyph local upsampled region 8 | var sideLenSrc : Int32 9 | var widthSrc : Int32 10 | var heightSrc : Int32 11 | 12 | // output drawing area 13 | var sideLenDst : Int32 14 | var widthDst : Int32 15 | var heightDst : Int32 16 | var originXDst : Int32 17 | var originYDst : Int32 18 | 19 | var upSamplingFactor : Int32 20 | var spreadThickness : Float 21 | var flipY : Int32 22 | 23 | init( rowLenSrc : Int32, rowLenDst : Int32, upSamplingFactor: Int32, spreadThickness : Float , flipY: Bool ) { 24 | 25 | self.sideLenSrc = rowLenSrc 26 | self.sideLenDst = rowLenDst 27 | self.upSamplingFactor = upSamplingFactor 28 | self.spreadThickness = spreadThickness 29 | 30 | self.widthSrc = 0 31 | self.heightSrc = 0 32 | self.widthDst = 0 33 | self.heightDst = 0 34 | self.originXDst = 0 35 | self.originYDst = 0 36 | self.flipY = flipY ? 1 : 0 37 | } 38 | } 39 | 40 | var bufferSrc : MTLBuffer 41 | var bufferDst : MTLBuffer 42 | var config : Config 43 | 44 | init( 45 | device : MTLDevice, 46 | bufferSrc : MTLBuffer, 47 | bufferSrcSideLen : Int, 48 | bufferDst : MTLBuffer, 49 | bufferDstSideLen : Int, 50 | upSamplingFactor : Int, 51 | spreadThickness : Float, 52 | flipY : Bool 53 | ) { 54 | self.bufferSrc = bufferSrc 55 | self.bufferDst = bufferDst 56 | 57 | config = Config( 58 | rowLenSrc : Int32(bufferSrcSideLen), 59 | rowLenDst : Int32(bufferDstSideLen), 60 | upSamplingFactor : Int32(upSamplingFactor), 61 | spreadThickness : spreadThickness, 62 | flipY : flipY 63 | ) 64 | 65 | super.init( device : device ) 66 | 67 | createPipelineState( functionName : "downsample") 68 | } 69 | 70 | func perform( 71 | originalBounds : SignedDistanceFontGlyphBounds, 72 | upSampledLocalizedBounds : SignedDistanceFontGlyphBounds 73 | ) { 74 | 75 | config.widthSrc = Int32( upSampledLocalizedBounds.outer.size.width ) 76 | config.heightSrc = Int32( upSampledLocalizedBounds.outer.size.height ) 77 | config.widthDst = Int32( originalBounds.outer.size.width ) 78 | config.heightDst = Int32( originalBounds.outer.size.height ) 79 | config.originXDst = Int32( originalBounds.outer.origin.x ) 80 | config.originYDst = Int32( originalBounds.outer.origin.y ) 81 | 82 | guard let commandBuffer = queue!.makeCommandBuffer() else { 83 | print ("ERROR: Cannot make command buffer for Downsampler.") 84 | return 85 | } 86 | 87 | let encoder = commandBuffer.makeComputeCommandEncoder() 88 | 89 | encoder!.setComputePipelineState( pipelineState! ) 90 | 91 | encoder!.setBytes( &config, length: MemoryLayout.stride, index: 0 ) 92 | encoder!.setBuffer( bufferSrc, offset: 0, index: 1 ) 93 | encoder!.setBuffer( bufferDst, offset: 0, index: 2 ) 94 | 95 | let threadConfig = MetalComputeBase.getThreadConfiguration( numThreads: Int( config.widthDst * config.heightDst ) ) 96 | encoder!.dispatchThreadgroups( 97 | MTLSizeMake( threadConfig.numGroupsPerGrid, 1, 1 ), 98 | threadsPerThreadgroup: MTLSizeMake( threadConfig.numThreadsPerGroup, 1, 1 ) 99 | ) 100 | encoder!.endEncoding() 101 | commandBuffer.commit() 102 | commandBuffer.waitUntilCompleted() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /SDFont/SDFont/SDFontBinaryPixmapGenerator.swift: -------------------------------------------------------------------------------- 1 | import MetalKit 2 | 3 | class SDFontBinaryPixmapGenerator { 4 | 5 | static let EPSILON = 1.0e-8 6 | let fontName : CFString 7 | let fontSize : CGFloat 8 | var cgFont : CGFont? 9 | let drawAreaSideLen : Int 10 | let flipY : Bool 11 | 12 | var pixelBuffer : MTLBuffer! 13 | var context : CGContext! 14 | 15 | init( device: MTLDevice, fontName: CFString, fontSize : CGFloat, drawAreaSideLen : Int, flipY : Bool ) { 16 | 17 | self.fontName = fontName 18 | self.fontSize = fontSize 19 | self.cgFont = CGFont( fontName ) 20 | 21 | self.drawAreaSideLen = drawAreaSideLen 22 | self.flipY = flipY 23 | 24 | guard let pixelBuffer = device.makeBuffer( length: MemoryLayout.stride * drawAreaSideLen * drawAreaSideLen, options: .storageModeShared ) 25 | else { 26 | print ( "ERROR: MTLBuffer cannot be made for the Pixmap of size \(drawAreaSideLen)" ) 27 | return 28 | } 29 | self.pixelBuffer = pixelBuffer 30 | 31 | let colorSpace : CGColorSpace = CGColorSpaceCreateDeviceGray(); 32 | let bitmapInfo = CGBitmapInfo( rawValue: CGImageAlphaInfo.none.rawValue 33 | | CGImageByteOrderInfo.orderDefault.rawValue 34 | | CGImagePixelFormatInfo.packed.rawValue 35 | ) 36 | 37 | guard let context = CGContext( 38 | data: pixelBuffer.contents(), 39 | width: drawAreaSideLen, 40 | height: drawAreaSideLen, 41 | bitsPerComponent: 8, 42 | bytesPerRow: drawAreaSideLen, 43 | space: colorSpace, 44 | bitmapInfo: bitmapInfo.rawValue 45 | ) else { 46 | print ( "ERROR: CGContext cannot be made for the Pixmap of size \(drawAreaSideLen)" ) 47 | return 48 | } 49 | context.setAllowsAntialiasing(false) 50 | self.context = context 51 | 52 | if flipY { 53 | self.context.translateBy( x: 0, y: CGFloat(drawAreaSideLen) ) 54 | self.context.scaleBy( x: 1, y: -1 ) 55 | } 56 | } 57 | 58 | func generatePixmap( forGlyph : CGGlyph, fontSize : CGFloat, glyphBounds : SignedDistanceFontGlyphBounds ) { 59 | 60 | var g = forGlyph 61 | 62 | context.setFillColor(red: 0, green: 0, blue: 0, alpha: 1) 63 | context.fill( CGRect( x: 0, y: 0, width: CGFloat( drawAreaSideLen ), height: CGFloat( drawAreaSideLen ) ) ) 64 | context.setFillColor( red: 1, green: 1, blue: 1, alpha: 1 ) 65 | 66 | var bboxes = UnsafeMutablePointer.allocate( capacity: 1 ) 67 | var advances = UnsafeMutablePointer.allocate( capacity: 1 ) 68 | 69 | if ( !cgFont!.getGlyphBBoxes( glyphs: [g], count: 1, bboxes: bboxes ) ) { 70 | print ( "WARNING: Cant't retrieve BBox for glyph [\(g)].") 71 | return 72 | } 73 | if ( !cgFont!.getGlyphAdvances(glyphs: [g], count: 1, advances: advances ) ) { 74 | print ( "WARNING: Cant't retrieve advances for glyph [\(g)].") 75 | return 76 | } 77 | 78 | let fontScaleFactor = fontSize / CGFloat( cgFont!.unitsPerEm ) 79 | 80 | let rect = CGRect( 81 | origin: CGPoint( x: bboxes[0].origin.x * fontScaleFactor, y: bboxes[0].origin.y * fontScaleFactor ), 82 | size: CGSize( width: bboxes[0].width * fontScaleFactor, height: bboxes[0].height * fontScaleFactor ) 83 | ) 84 | 85 | let normalizedAdvance = CGFloat( advances[0] ) / CGFloat( cgFont!.unitsPerEm ) 86 | 87 | if abs( glyphBounds.inner.size.width - rect.size.width ) >= Self.EPSILON 88 | || abs( glyphBounds.inner.size.height - rect.size.height ) >= Self.EPSILON { 89 | 90 | print ( "WARNING: Boundary requested: \(glyphBounds.inner.size) is different from the boundary from CGFont: \(rect.size) for glyph [\(g)]") 91 | } 92 | 93 | var translation = CGAffineTransform( translationX: glyphBounds.inner.origin.x - rect.origin.x, 94 | y: glyphBounds.inner.origin.y - rect.origin.y ) 95 | context.setShouldSmoothFonts( false ) 96 | context.setShouldSubpixelPositionFonts( false ) 97 | context.setShouldSubpixelQuantizeFonts( false ) 98 | context.textMatrix = translation 99 | context.setFont( cgFont! ) 100 | context.setFontSize( fontSize ) 101 | context.showGlyphs( [g], at: [CGPoint( x: 0, y: 0 )] ) 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/ARCoordinator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | import ARKit 4 | 5 | class ARCoordinator: NSObject, MTKViewDelegate, ARSessionDelegate { 6 | 7 | var arSession: ARSession! 8 | 9 | var device: MTLDevice! 10 | var renderSemaphore: DispatchSemaphore 11 | var commandQueue: MTLCommandQueue! 12 | 13 | var previousFrameSize: CGSize // to detect screen dimension change. SwiftUI does not always call mtkView(). 14 | var viewportSize: CGSize // current viewport size 15 | 16 | let worldManager: WorldManager 17 | let photoImageRenderer: PhotoImageRenderer 18 | 19 | init( worldManager: WorldManager, device : MTLDevice ) { 20 | 21 | self.device = worldManager.device 22 | self.renderSemaphore = DispatchSemaphore( value: 1 ) 23 | 24 | guard let commandQueue = device.makeCommandQueue() 25 | else { 26 | fatalError("GPU not available") 27 | } 28 | self.commandQueue = commandQueue 29 | self.worldManager = worldManager 30 | self.photoImageRenderer = PhotoImageRenderer( device: device ) 31 | self.previousFrameSize = CGSize( width: 0.0, height: 0.0 ) 32 | self.viewportSize = CGSize() 33 | 34 | super.init() 35 | 36 | arSession = ARSession() 37 | arSession.delegate = self 38 | } 39 | 40 | /// 2nd part of init. It must wait until device and the pixel format become available in MTKView 41 | func createPipelineStates( mtkView: MTKView ) { 42 | worldManager.createPipelineStates( mtkView: mtkView ) 43 | } 44 | 45 | // MARK: - MTKViewDelegate 46 | 47 | func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize ) { 48 | 49 | let sizeInPixels = CGSize( width: size.width * view.contentScaleFactor, height: size.height * view.contentScaleFactor ) 50 | 51 | worldManager.updateScreenSizes( size: sizeInPixels ) 52 | 53 | photoImageRenderer.arrangeMetalForCameraImage( view: view ) 54 | 55 | let arConfiguration = ARWorldTrackingConfiguration() 56 | 57 | arSession.run( arConfiguration ) 58 | } 59 | 60 | func draw( in view: MTKView ) { 61 | 62 | // update the world 63 | 64 | guard let currentFrame = arSession.currentFrame else { 65 | return 66 | } 67 | 68 | guard let drawable = view.currentDrawable 69 | else { 70 | return 71 | } 72 | 73 | if previousFrameSize != view.frame.size { 74 | 75 | viewportSize = view.frame.size 76 | previousFrameSize = view.frame.size 77 | self.mtkView( view, drawableSizeWillChange: view.frame.size ) 78 | photoImageRenderer.updateImagePlane(frame: currentFrame, viewportSize : viewportSize ) 79 | } 80 | 81 | photoImageRenderer.updateCapturedImageTextures( frame: currentFrame ) 82 | 83 | // MARK: - Device Orientation 84 | let V = arSession.currentFrame!.camera.viewMatrix( for: .portrait ) 85 | let P = arSession.currentFrame!.camera.projectionMatrix( for: .portrait, viewportSize: viewportSize, zNear: 0.001, zFar: 10000.0 ) 86 | // Correction matrix from UIDeviceOrientation.landscapeRight to UIDeviceOrientation.portrait, 87 | // as camera.transform is based on .landscapeRight. 88 | var C = float4x4.identity() 89 | C.columns.0 = SIMD4( 0.0, 1.0, 0.0, 0.0) 90 | C.columns.1 = SIMD4( -1.0, 0.0, 0.0, 0.0) 91 | let M = C * arSession.currentFrame!.camera.transform 92 | 93 | worldManager.updateViewAndProjectionMatrixForCamera( viewMatrix: V, projectionMatrix: P, transform: M ) 94 | worldManager.updateWorld() 95 | 96 | // render the world 97 | 98 | guard 99 | let commandBuffer = commandQueue.makeCommandBuffer() 100 | else { 101 | return 102 | } 103 | 104 | commandBuffer.addCompletedHandler { _ in self.renderSemaphore.signal() } 105 | renderSemaphore.wait() 106 | 107 | photoImageRenderer.draw( in : view, commandBuffer: commandBuffer ) 108 | 109 | worldManager.draw( in : view, commandBuffer: commandBuffer ) 110 | 111 | commandBuffer.present( drawable ) 112 | commandBuffer.commit() 113 | } 114 | 115 | // MARK: - ARSessionDelegate 116 | 117 | func session(_ session: ARSession, didUpdate frame: ARFrame){ 118 | 119 | } 120 | 121 | func session(_ session: ARSession, didAdd anchors: [ARAnchor]){ 122 | 123 | } 124 | 125 | func session(_ session: ARSession, didUpdate anchors: [ARAnchor]){ 126 | 127 | } 128 | 129 | func session(_ session: ARSession, didRemove anchors: [ARAnchor]){ 130 | 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /SWOpeningRollAR/demo/AnimationConstants.swift: -------------------------------------------------------------------------------- 1 | import MetalKit 2 | 3 | class AnimationConstants { 4 | 5 | static let Helvetica = "Helvetica" 6 | static let HelveticaBold = "Helvetica-Bold" 7 | static let SDTextureSize = 1024 8 | 9 | // MARK: Text 1 10 | 11 | static let Text1 = "A long time ago, in a gallaxy far\nfar away...." 12 | static let FontName1 = Helvetica 13 | static let ColorOn1 = SIMD4(0.2, 0.6, 1.0, 1.0) 14 | static let ColorOff1 = SIMD4(0.2, 0.6, 1.0, 0.0) 15 | static let Dimension1 = CGSize( width: 1.2, height: 0.24 ) 16 | static let Scale1 = 250.0 17 | static let Translation1 = SIMD3( x: 0.065, y: -0.1, z: -0.15 ) 18 | static let Translation1Demo = SIMD3( x: 0.065, y: 0.03, z: -0.15 ) 19 | 20 | static let TemporalPoints1 : [TimeInterval] = [ 0.0, 1.0, 2.0, 4.0, 5.0 ] 21 | static let SpacialPoints1 : [SIMD3] = [ Translation1, Translation1, Translation1, Translation1, Translation1 ] 22 | static let Colors1 : [SIMD4] = [ ColorOff1, ColorOff1, ColorOn1, ColorOn1, ColorOff1 ] 23 | 24 | static var SDFontUniform1 = UniformSDFont( 25 | foregroundColor : ColorOn1, 26 | funcType : UniformSDFont.FunctionType.SDFONT_SMOOTH_STEP, 27 | point1 : 0.2, 28 | point2 : 0.9, 29 | width : 0.0 30 | ) 31 | 32 | // MARK: Text 2 33 | 34 | static let Text2 = "STAR\nWARS" 35 | static let FontName2 = HelveticaBold 36 | static let ColorOn2 = SIMD4( 1.0, 0.8, 0.0, 1.0 ) 37 | static let ColorOff2 = SIMD4( 1.0, 0.8, 0.0, 0.0 ) 38 | static let Dimension2 = CGSize( width: 2.0, height: 1.2 ) 39 | static let Scale2 = 25.0 40 | static let Translation2_1 = SIMD3( x: 0.0, y: 0.0, z: 0.0 ) 41 | static let Translation2_2 = SIMD3( x: 0.0, y: 0.0, z: -10.0 ) 42 | static let Translation2_3 = SIMD3( x: 0.0, y: 0.0, z: -15.0 ) 43 | static let Translation2Demo = SIMD3( x: 0.0, y: 0.0, z: -1.5 ) 44 | 45 | static let TemporalPoints2 : [TimeInterval] = [ 0.0, 5.99, 6.0, 14.0, 17.0, ] 46 | static let SpacialPoints2 : [SIMD3] = [ Translation2_1, Translation2_1, Translation2_1, Translation2_2, Translation2_3 ] 47 | static let Colors2 : [SIMD4] = [ ColorOff2, ColorOff2, ColorOn2, ColorOn2, ColorOff2 ] 48 | 49 | static var SDFontUniform2 = UniformSDFont( 50 | foregroundColor : ColorOn2, 51 | funcType : UniformSDFont.FunctionType.SDFONT_TRAPEZOID, 52 | point1 : 0.55, 53 | point2 : 0.65, 54 | width : 0.1 55 | ) 56 | 57 | // MARK: Text 3 58 | 59 | static let Text3 = 60 | """ 61 | EPISODE IV 62 | 63 | It is a period of civil war. 64 | Rebel spaceships, striking 65 | from a hidden base, have won 66 | their first victory against 67 | the evil Galactic Empire. 68 | 69 | During the battle, Rebel 70 | spies managed to steal secret 71 | plans to the Empire's 72 | ultimate weapon, the DEATH 73 | STAR, an armored space 74 | station with enough power 75 | to destroy an entire planet. 76 | 77 | Pursued by the Empire's 78 | sinister agents, Princess 79 | Leia races home aboard her 80 | starship, custodian of the 81 | stolen plans that can save her 82 | people and restore 83 | freedom to the galaxy.... 84 | """ 85 | static let FontName3 = Helvetica 86 | static let ColorOn3 = SIMD4( 1.0, 0.8, 0.0, 1.0 ) 87 | static let ColorOff3 = SIMD4( 1.0, 0.8, 0.0, 0.0 ) 88 | static let Dimension3 = CGSize( width: 0.6, height: 1.0 ) 89 | static let Scale3 = 500.0 90 | static let Translation3_1 = SIMD3( x: 0.0, y: -0.06, z: 0.6 ) 91 | static let Translation3_2 = SIMD3( x: 0.0, y: -0.06, z: -0.5 + 0.6 ) 92 | static let Translation3_3 = SIMD3( x: 0.0, y: -0.06, z: -0.5 - 0.25 + 0.6 ) 93 | static let Translation3Demo = SIMD3( x: 0.0, y: -0.06, z: 0.1 ) 94 | 95 | static let TemporalPoints3 : [TimeInterval] = [ 0.0, 7.99, 8.0, 18.0, 23.0, ] 96 | static let SpacialPoints3 : [SIMD3] = [ Translation3_1, Translation3_1, Translation3_1, Translation3_2, Translation3_3 ] 97 | static let Colors3 : [SIMD4] = [ ColorOff3, ColorOff3, ColorOn3, ColorOn3, ColorOff3 ] 98 | 99 | static var SDFontUniform3 = UniformSDFont( 100 | foregroundColor : ColorOn3, 101 | funcType : UniformSDFont.FunctionType.SDFONT_STEP, 102 | point1 : 0.49, 103 | point2 : 0.51, 104 | width : 0.1 105 | ) 106 | } 107 | -------------------------------------------------------------------------------- /doc/generate_plots.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | 6 | def through(x): 7 | return x 8 | 9 | 10 | def smooth_step(e0, e1, x): 11 | x = min( 1.0, max( 0.0, (x - e0) / (e1 - e0) ) ) 12 | return x * x * x * (x * (x * 6 - 15) + 10); 13 | 14 | def step(c, x): 15 | if x < c: 16 | return 0.0 17 | else: 18 | return 1.0 19 | 20 | def slope_step( c, w, x ): 21 | if x < c - w * 0.5: 22 | return 0.0 23 | 24 | elif x < c + w * 0.5: 25 | return (x - ( c - w * 0.5 ) ) / w 26 | 27 | else: 28 | return 1.0 29 | 30 | def trapezoid( e0, e1, w, x ): 31 | if x < e0 - w: 32 | return 0.0 33 | 34 | elif x < e0: 35 | return ( x - (e0 - w))/w 36 | 37 | elif x < e1: 38 | return 1.0 39 | 40 | elif x < e1 + w: 41 | return 1.0 - (x - e1) /w 42 | 43 | else: 44 | return 0.0 45 | 46 | 47 | def twin_peaks( e0, e1, w, x ): 48 | hw = 0.5 * w 49 | if x < e0 - hw: 50 | return 0.0; 51 | 52 | elif x < e0: 53 | return (x - (e0 - hw))/ hw 54 | 55 | elif x < e0 + hw: 56 | return 1.0 - (x - e0) / hw 57 | 58 | if x < e1 - hw: 59 | return 0.0; 60 | 61 | elif x < e1: 62 | return (x - (e1 - hw))/ hw 63 | 64 | elif x < e1 + hw: 65 | return 1.0 - (x - e1) / hw 66 | 67 | else: 68 | return 0.0; 69 | 70 | def halo( e0, e1, w, x ): 71 | if x > e1: 72 | return 0.0 73 | else: 74 | return slope_step( e0, w, x ) 75 | 76 | 77 | xs = np.arange( start=0.0, stop=1.001, step=0.001 ) 78 | 79 | 80 | ys = [] 81 | for x in xs: 82 | ys.append( x ) 83 | ys = np.array(ys) 84 | plt.plot( xs, ys ) 85 | plt.xlabel( 'x' ) 86 | plt.ylabel( 'pass_thgourh(x)' ) 87 | plt.title( 'pass through' ) 88 | plt.savefig( 'plot_pass_through.png' ) 89 | plt.clf() 90 | 91 | ys = [] 92 | for x in xs: 93 | ys.append( step(0.5, x) ) 94 | ys = np.array(ys) 95 | plt.plot( xs, ys ) 96 | plt.text( 0.52, 0.2, 'edge' ) 97 | plt.xlabel( 'x' ) 98 | plt.ylabel( 'step(x)' ) 99 | plt.title( 'step' ) 100 | plt.savefig( 'plot_step.png' ) 101 | plt.clf() 102 | 103 | ys = [] 104 | for x in xs: 105 | ys.append( smooth_step(0.2, 0.8, x) ) 106 | ys = np.array(ys) 107 | plt.plot( xs, ys ) 108 | plt.plot( [0.2, 0.2], [0.0, 1.0], '--' ) 109 | plt.plot( [0.8, 0.8], [0.0, 1.0], '--' ) 110 | plt.text( 0.22, 0.2, 'edge0' ) 111 | plt.text( 0.82, 0.2, 'edge1' ) 112 | plt.xlabel( 'x' ) 113 | plt.ylabel( 'smooth_step(x)' ) 114 | plt.title( 'smooth step' ) 115 | plt.savefig( 'plot_smooth_step.png' ) 116 | plt.clf() 117 | 118 | ys = [] 119 | for x in xs: 120 | ys.append( slope_step(0.5, 0.2, x) ) 121 | ys = np.array(ys) 122 | plt.plot( xs, ys ) 123 | plt.plot( [0.5, 0.5], [0.0, 1.0], '--' ) 124 | plt.plot( [0.4, 0.6], [0.0, 0.0], '--' ) 125 | plt.text( 0.51, 0.2, 'edge' ) 126 | plt.text( 0.6, 0.01, 'width' ) 127 | plt.xlabel( 'x' ) 128 | plt.ylabel( 'slope_step(x)' ) 129 | plt.title( 'slope step' ) 130 | plt.savefig( 'plot_slope_step.png' ) 131 | plt.clf() 132 | 133 | 134 | ys = [] 135 | for x in xs: 136 | ys.append( trapezoid( 0.3, 0.7, 0.2, x ) ) 137 | ys = np.array(ys) 138 | plt.plot( xs, ys ) 139 | plt.plot( [0.3, 0.3], [0.0, 1.0], '--' ) 140 | plt.plot( [0.7, 0.7], [0.0, 1.0], '--' ) 141 | plt.plot( [0.1, 0.3], [0.0, 0.0], '--' ) 142 | plt.plot( [0.7, 0.9], [0.0, 0.0], '--' ) 143 | plt.text( 0.31, 0.2, 'edge0' ) 144 | plt.text( 0.71, 0.2, 'edge1' ) 145 | plt.text( 0.15, 0.01, 'width' ) 146 | plt.text( 0.75, 0.01, 'width' ) 147 | plt.xlabel( 'x' ) 148 | plt.ylabel( 'trapezoid(x)' ) 149 | plt.title( 'trapezoid' ) 150 | plt.savefig( 'plot_trapezoid.png' ) 151 | plt.clf() 152 | 153 | 154 | ys = [] 155 | for x in xs: 156 | ys.append( twin_peaks( 0.3, 0.7, 0.2, x ) ) 157 | ys = np.array(ys) 158 | plt.plot( xs, ys ) 159 | plt.plot( [0.3, 0.3], [0.0, 1.0], '--' ) 160 | plt.plot( [0.7, 0.7], [0.0, 1.0], '--' ) 161 | plt.plot( [0.2, 0.4], [0.0, 0.0], '--' ) 162 | plt.plot( [0.6, 0.8], [0.0, 0.0], '--' ) 163 | plt.text( 0.31, 0.2, 'edge0' ) 164 | plt.text( 0.71, 0.2, 'edge1' ) 165 | plt.text( 0.25, 0.01, 'width' ) 166 | plt.text( 0.65, 0.01, 'width' ) 167 | plt.xlabel( 'x' ) 168 | plt.ylabel( 'twin_peaks(x)' ) 169 | plt.title( 'twin peaks' ) 170 | plt.savefig( 'plot_twin_peaks.png' ) 171 | plt.clf() 172 | 173 | 174 | ys = [] 175 | for x in xs: 176 | ys.append( halo( 0.5, 0.65, 0.6, x ) ) 177 | ys = np.array(ys) 178 | plt.plot( xs, ys ) 179 | plt.plot( [0.5, 0.5 ], [0.0, 1.0], '--' ) 180 | plt.plot( [0.65, 0.65], [(1.0/0.6)*0.65 - 0.2/0.6, 1.0], '--' ) 181 | plt.plot( [0.65, 0.8 ], [(1.0/0.6)*0.65 - 0.2/0.6, 1.0], '--' ) 182 | plt.plot( [0.2, 0.8], [0.01, 0.01], '--' ) 183 | 184 | plt.text( 0.51, 0.2, 'edge0' ) 185 | plt.text( 0.66, 0.2, 'cutoff' ) 186 | plt.text( 0.25, 0.01, 'width' ) 187 | plt.xlabel( 'x' ) 188 | plt.ylabel( 'halo(x)' ) 189 | plt.title( 'halo' ) 190 | plt.savefig( 'plot_halo.png' ) 191 | plt.clf() 192 | -------------------------------------------------------------------------------- /SWOpeningRoll/shared/AnimationConstants.swift: -------------------------------------------------------------------------------- 1 | import MetalKit 2 | 3 | class AnimationConstants { 4 | 5 | static let Helvetica = "Helvetica" 6 | static let HelveticaBold = "Helvetica-Bold" 7 | static let SDTextureSize = 1024 8 | 9 | // MARK: Text 1 10 | 11 | static let Text1 = "A long time ago, in a gallaxy far\nfar away...." 12 | static let FontName1 = Helvetica 13 | static let ColorOn1 = SIMD4(0.2, 0.6, 1.0, 1.0) 14 | static let ColorOff1 = SIMD4(0.2, 0.6, 1.0, 0.0) 15 | static let Dimension1 = CGSize( width: 0.3, height: 0.06 ) 16 | static let Scale1 = 1000.0 17 | static let Translation1 = SIMD3( x: 0.065, y: -0.02, z: -0.15 ) 18 | static let Translation1Demo = SIMD3( x: 0.065, y: 0.03, z: -0.15 ) 19 | 20 | static let TemporalPoints1 : [TimeInterval] = [ 0.0, 1.0, 2.0, 4.0, 5.0 ] 21 | static let SpacialPoints1 : [SIMD3] = [ Translation1, Translation1, Translation1, Translation1, Translation1 ] 22 | static let Colors1 : [SIMD4] = [ ColorOff1, ColorOff1, ColorOn1, ColorOn1, ColorOff1 ] 23 | 24 | static var SDFontUniform1 = UniformSDFont( 25 | foregroundColor : ColorOn1, 26 | funcType : UniformSDFont.FunctionType.SDFONT_SMOOTH_STEP, 27 | point1 : 0.2, 28 | point2 : 0.9, 29 | width : 0.0 30 | ) 31 | 32 | // MARK: Text 2 33 | 34 | static let Text2 = "STAR\nWARS" 35 | static let FontName2 = HelveticaBold 36 | static let ColorOn2 = SIMD4( 1.0, 0.8, 0.0, 1.0 ) 37 | static let ColorOff2 = SIMD4( 1.0, 0.8, 0.0, 0.0 ) 38 | static let Dimension2 = CGSize( width: 1.0, height: 0.6 ) 39 | static let Scale2 = 50.0 40 | static let Translation2_1 = SIMD3( x: 0.0, y: 0.0, z: 0.0 ) 41 | static let Translation2_2 = SIMD3( x: 0.0, y: 0.0, z: -10.0 ) 42 | static let Translation2_3 = SIMD3( x: 0.0, y: 0.0, z: -15.0 ) 43 | static let Translation2Demo = SIMD3( x: 0.0, y: 0.0, z: -1.5 ) 44 | 45 | static let TemporalPoints2 : [TimeInterval] = [ 0.0, 5.99, 6.0, 14.0, 17.0, ] 46 | static let SpacialPoints2 : [SIMD3] = [ Translation2_1, Translation2_1, Translation2_1, Translation2_2, Translation2_3 ] 47 | static let Colors2 : [SIMD4] = [ ColorOff2, ColorOff2, ColorOn2, ColorOn2, ColorOff2 ] 48 | 49 | static var SDFontUniform2 = UniformSDFont( 50 | foregroundColor : ColorOn2, 51 | funcType : UniformSDFont.FunctionType.SDFONT_TRAPEZOID, 52 | point1 : 0.55, 53 | point2 : 0.65, 54 | width : 0.1 55 | ) 56 | 57 | // MARK: Text 3 58 | 59 | static let Text3 = 60 | """ 61 | EPISODE IV 62 | 63 | It is a period of civil war. 64 | Rebel spaceships, striking 65 | from a hidden base, have won 66 | their first victory against 67 | the evil Galactic Empire. 68 | 69 | During the battle, Rebel 70 | spies managed to steal secret 71 | plans to the Empire's 72 | ultimate weapon, the DEATH 73 | STAR, an armored space 74 | station with enough power 75 | to destroy an entire planet. 76 | 77 | Pursued by the Empire's 78 | sinister agents, Princess 79 | Leia races home aboard her 80 | starship, custodian of the 81 | stolen plans that can save her 82 | people and restore 83 | freedom to the galaxy.... 84 | """ 85 | static let FontName3 = Helvetica 86 | static let ColorOn3 = SIMD4( 1.0, 0.8, 0.0, 1.0 ) 87 | static let ColorOff3 = SIMD4( 1.0, 0.8, 0.0, 0.0 ) 88 | static let Dimension3 = CGSize( width: 0.3, height: 0.5 ) 89 | static let Scale3 = 1000.0 90 | static let Translation3_1 = SIMD3( x: 0.0, y: -0.03, z: 0.3 ) 91 | static let Translation3_2 = SIMD3( x: 0.0, y: -0.03, z: -0.5 + 0.3 ) 92 | static let Translation3_3 = SIMD3( x: 0.0, y: -0.03, z: -0.5 - 0.25 + 0.3 ) 93 | static let Translation3Demo = SIMD3( x: 0.0, y: -0.03, z: 0.1 ) 94 | 95 | static let TemporalPoints3 : [TimeInterval] = [ 0.0, 7.99, 8.0, 28.0, 38.0, ] 96 | static let SpacialPoints3 : [SIMD3] = [ Translation3_1, Translation3_1, Translation3_1, Translation3_2, Translation3_3 ] 97 | static let Colors3 : [SIMD4] = [ ColorOff3, ColorOff3, ColorOn3, ColorOn3, ColorOff3 ] 98 | 99 | static var SDFontUniform3 = UniformSDFont( 100 | foregroundColor : ColorOn3, 101 | funcType : UniformSDFont.FunctionType.SDFONT_STEP, 102 | point1 : 0.49, 103 | point2 : 0.51, 104 | width : 0.1 105 | ) 106 | } 107 | -------------------------------------------------------------------------------- /SWOpeningRoll/rendering/shaders/default_renderer.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "render_uniforms.h" 3 | #include "render_vertex_ins.h" 4 | using namespace metal; 5 | 6 | constant int VertexBufferIndexUniformPerScene = 1; 7 | constant int VertexBufferIndexUniformPerInstance = 2; 8 | constant int FragmentBufferIndexUniformSDFont = 4; 9 | constant int TextureIndexSDFont = 3; 10 | 11 | struct VertexOut { 12 | 13 | float4 position [[ position ]]; 14 | float2 uv; 15 | 16 | }; 17 | 18 | vertex VertexOut vertex_position_uv( 19 | 20 | const VertexInPositionUV vertex_in [[ stage_in ]], 21 | constant UniformPerScene& per_scene [[ buffer( VertexBufferIndexUniformPerScene ) ]], 22 | constant UniformPerInstance* per_instance [[ buffer( VertexBufferIndexUniformPerInstance ) ]], 23 | const ushort instance_id [[ instance_id ]] 24 | ) { 25 | const float4 position = per_scene.projection_matrix 26 | * per_scene.view_matrix 27 | * per_instance[instance_id].model_matrix 28 | * vertex_in.position; 29 | 30 | const float2 uv = vertex_in.uv; 31 | 32 | VertexOut out { 33 | 34 | .position = position, 35 | .uv = uv 36 | }; 37 | 38 | return out; 39 | } 40 | 41 | static inline float sdfont_step( const float v, const float pos ) 42 | { 43 | if ( v < pos ) { 44 | return 0.0; 45 | } 46 | else { 47 | return 1.0; 48 | } 49 | } 50 | 51 | static inline float sdfont_slope_step( const float v, const float width, const float pos ) 52 | { 53 | if ( v < pos - width * 0.5 ) { 54 | return 0.0; 55 | } 56 | else if (v < pos + width * 0.5 ) { 57 | return (v - ( pos - width * 0.5 ) ) / width; 58 | } 59 | else { 60 | return 1.0; 61 | } 62 | } 63 | 64 | static inline float sdfont_trapezoid( const float v, const float width, const float pos1, const float pos2 ) 65 | { 66 | if ( v < pos1 - width ) { 67 | return 0.0; 68 | } 69 | else if (v < pos1 ) { 70 | return (v - (pos1 - width))/width; 71 | } 72 | else if (v < pos2 ) { 73 | return 1.0; 74 | } 75 | else if (v < pos2 + width ) { 76 | return 1.0 - (v - pos2) /width; 77 | } 78 | else { 79 | return 0.0; 80 | } 81 | } 82 | 83 | static inline float sdfont_twin_peaks( const float v, const float width, const float pos1, const float pos2 ) 84 | { 85 | const float hw = 0.5 * width; 86 | 87 | if ( v < pos1 - hw ) { 88 | return 0.0; 89 | } 90 | else if (v < pos1 ) { 91 | return (v - (pos1 - hw)) / hw; 92 | } 93 | else if (v < pos1 + hw ) { 94 | return 1.0 - (v - pos1) / hw; 95 | } 96 | if ( v < pos2 - 0.5 * hw ) { 97 | return 0.0; 98 | } 99 | 100 | else if (v < pos2 ) { 101 | return (v - (pos2 - hw)) / hw; 102 | } 103 | else if (v < pos2 + hw ) { 104 | return 1.0 - (v - pos2) /hw; 105 | } 106 | else { 107 | return 0.0; 108 | } 109 | } 110 | 111 | static inline float sdfont_halo( const float v, const float width, const float pos1, const float pos2 ) 112 | { 113 | if (v > pos2) { 114 | return 0.0; // cutoff at pos2 115 | } 116 | else { 117 | return sdfont_slope_step( v, width, pos1 ); 118 | } 119 | } 120 | 121 | fragment float4 fragment_sdfont( 122 | VertexOut in [[ stage_in ]], 123 | constant UniformSDFont& sd_font [[ buffer( FragmentBufferIndexUniformSDFont ) ]], 124 | sampler texture_sampler [[ sampler( 0 ) ]], 125 | texture2d texture_sdfont [[ texture( TextureIndexSDFont ) ]] 126 | ) { 127 | float4 base_color = sd_font.foreground_color; 128 | 129 | float sampleDistance = texture_sdfont.sample(texture_sampler, in.uv).r; 130 | 131 | const auto func_type = (SDFontFunctionType)sd_font.func_type; 132 | 133 | float alpha = 0.0; 134 | 135 | switch (func_type) { 136 | 137 | case SDFONT_PASS_THROUGH: 138 | alpha = sampleDistance; 139 | break; 140 | 141 | case SDFONT_STEP: 142 | alpha = sdfont_step( sampleDistance, sd_font.point1 ); 143 | break; 144 | 145 | case SDFONT_SMOOTH_STEP: 146 | alpha = smoothstep( sd_font.point1, sd_font.point2, sampleDistance ); 147 | break; 148 | 149 | case SDFONT_SLOPE_STEP: 150 | alpha = sdfont_slope_step( sampleDistance, sd_font.width, sd_font.point1 ); 151 | break; 152 | 153 | case SDFONT_TRAPEZOID: 154 | alpha = sdfont_trapezoid( sampleDistance, sd_font.width, sd_font.point1, sd_font.point2 ); 155 | break; 156 | 157 | case SDFONT_TWIN_PEAKS: 158 | alpha = sdfont_twin_peaks( sampleDistance, sd_font.width, sd_font.point1, sd_font.point2 ); 159 | break; 160 | 161 | case SDFONT_HALO: 162 | alpha = sdfont_halo( sampleDistance, sd_font.width, sd_font.point1, sd_font.point2 ); 163 | break; 164 | 165 | 166 | } 167 | return float4(base_color.r, base_color.g, base_color.b, alpha * base_color.a ); 168 | } 169 | -------------------------------------------------------------------------------- /SWOpeningRollAR/rendering/shaders/default_renderer.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include "render_uniforms.h" 3 | #include "render_vertex_ins.h" 4 | using namespace metal; 5 | 6 | constant int VertexBufferIndexUniformPerScene = 1; 7 | constant int VertexBufferIndexUniformPerInstance = 2; 8 | constant int FragmentBufferIndexUniformSDFont = 4; 9 | constant int TextureIndexSDFont = 3; 10 | 11 | struct VertexOut { 12 | 13 | float4 position [[ position ]]; 14 | float2 uv; 15 | 16 | }; 17 | 18 | vertex VertexOut vertex_position_uv( 19 | 20 | const VertexInPositionUV vertex_in [[ stage_in ]], 21 | constant UniformPerScene& per_scene [[ buffer( VertexBufferIndexUniformPerScene ) ]], 22 | constant UniformPerInstance* per_instance [[ buffer( VertexBufferIndexUniformPerInstance ) ]], 23 | const ushort instance_id [[ instance_id ]] 24 | ) { 25 | const float4 position = per_scene.projection_matrix 26 | * per_scene.view_matrix 27 | * per_instance[instance_id].model_matrix 28 | * vertex_in.position; 29 | 30 | const float2 uv = vertex_in.uv; 31 | 32 | VertexOut out { 33 | 34 | .position = position, 35 | .uv = uv 36 | }; 37 | 38 | return out; 39 | } 40 | 41 | static inline float sdfont_step( const float v, const float pos ) 42 | { 43 | if ( v < pos ) { 44 | return 0.0; 45 | } 46 | else { 47 | return 1.0; 48 | } 49 | } 50 | 51 | static inline float sdfont_slope_step( const float v, const float width, const float pos ) 52 | { 53 | if ( v < pos - width * 0.5 ) { 54 | return 0.0; 55 | } 56 | else if (v < pos + width * 0.5 ) { 57 | return (v - ( pos - width * 0.5 ) ) / width; 58 | } 59 | else { 60 | return 1.0; 61 | } 62 | } 63 | 64 | static inline float sdfont_trapezoid( const float v, const float width, const float pos1, const float pos2 ) 65 | { 66 | if ( v < pos1 - width ) { 67 | return 0.0; 68 | } 69 | else if (v < pos1 ) { 70 | return (v - (pos1 - width))/width; 71 | } 72 | else if (v < pos2 ) { 73 | return 1.0; 74 | } 75 | else if (v < pos2 + width ) { 76 | return 1.0 - (v - pos2) /width; 77 | } 78 | else { 79 | return 0.0; 80 | } 81 | } 82 | 83 | static inline float sdfont_twin_peaks( const float v, const float width, const float pos1, const float pos2 ) 84 | { 85 | const float hw = 0.5 * width; 86 | 87 | if ( v < pos1 - hw ) { 88 | return 0.0; 89 | } 90 | else if (v < pos1 ) { 91 | return (v - (pos1 - hw)) / hw; 92 | } 93 | else if (v < pos1 + hw ) { 94 | return 1.0 - (v - pos1) / hw; 95 | } 96 | if ( v < pos2 - 0.5 * hw ) { 97 | return 0.0; 98 | } 99 | 100 | else if (v < pos2 ) { 101 | return (v - (pos2 - hw)) / hw; 102 | } 103 | else if (v < pos2 + hw ) { 104 | return 1.0 - (v - pos2) /hw; 105 | } 106 | else { 107 | return 0.0; 108 | } 109 | } 110 | 111 | static inline float sdfont_halo( const float v, const float width, const float pos1, const float pos2 ) 112 | { 113 | if (v > pos2) { 114 | return 0.0; // cutoff at pos2 115 | } 116 | else { 117 | return sdfont_slope_step( v, width, pos1 ); 118 | } 119 | } 120 | 121 | fragment float4 fragment_sdfont( 122 | VertexOut in [[ stage_in ]], 123 | constant UniformSDFont& sd_font [[ buffer( FragmentBufferIndexUniformSDFont ) ]], 124 | sampler texture_sampler [[ sampler( 0 ) ]], 125 | texture2d texture_sdfont [[ texture( TextureIndexSDFont ) ]] 126 | ) { 127 | float4 base_color = sd_font.foreground_color; 128 | 129 | float sampleDistance = texture_sdfont.sample(texture_sampler, in.uv).r; 130 | 131 | const auto func_type = (SDFontFunctionType)sd_font.func_type; 132 | 133 | float alpha = 0.0; 134 | 135 | switch (func_type) { 136 | 137 | case SDFONT_PASS_THROUGH: 138 | alpha = sampleDistance; 139 | break; 140 | 141 | case SDFONT_STEP: 142 | alpha = sdfont_step( sampleDistance, sd_font.point1 ); 143 | break; 144 | 145 | case SDFONT_SMOOTH_STEP: 146 | alpha = smoothstep( sd_font.point1, sd_font.point2, sampleDistance ); 147 | break; 148 | 149 | case SDFONT_SLOPE_STEP: 150 | alpha = sdfont_slope_step( sampleDistance, sd_font.width, sd_font.point1 ); 151 | break; 152 | 153 | case SDFONT_TRAPEZOID: 154 | alpha = sdfont_trapezoid( sampleDistance, sd_font.width, sd_font.point1, sd_font.point2 ); 155 | break; 156 | 157 | case SDFONT_TWIN_PEAKS: 158 | alpha = sdfont_twin_peaks( sampleDistance, sd_font.width, sd_font.point1, sd_font.point2 ); 159 | break; 160 | 161 | case SDFONT_HALO: 162 | alpha = sdfont_halo( sampleDistance, sd_font.width, sd_font.point1, sd_font.point2 ); 163 | break; 164 | 165 | 166 | } 167 | return float4(base_color.r, base_color.g, base_color.b, alpha * base_color.a ); 168 | } 169 | -------------------------------------------------------------------------------- /SWOpeningRoll/rendering/PerSceneRenderHelper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | 4 | class PerSceneRenderHelper { 5 | 6 | static let FragmentFunctionNameSDFont = "fragment_sdfont" 7 | static let VertexFunctionNamePositionUV = "vertex_position_uv" 8 | 9 | static let SamplerStateMaxAnisotropy = 4 10 | 11 | static let VertexBufferIndexVertexIn = 0 12 | static let VertexBufferIndexUniformPerScene = 1 13 | static let VertexBufferIndexUniformPerInstance = 2 14 | 15 | static let FragmentBufferIndexUniformPerScene = 1 16 | static let FragmentBufferIndexUniformSDFont = 4 17 | 18 | static let TextureIndexSDFont = 3 19 | 20 | let device : MTLDevice 21 | var pipelineState : MTLRenderPipelineState? 22 | var depthStencilState : MTLDepthStencilState? 23 | let bufferUniformPerScene : MTLBuffer? 24 | var samplerState : MTLSamplerState? 25 | 26 | init( 27 | device : MTLDevice, 28 | bufferUniformPerScene : MTLBuffer 29 | ) { 30 | self.device = device 31 | self.bufferUniformPerScene = bufferUniformPerScene 32 | self.samplerState = Self.buildSamplerStateSDFont( device: device ) 33 | 34 | let desc = MTLDepthStencilDescriptor() 35 | desc.depthCompareFunction = .always 36 | desc.isDepthWriteEnabled = false 37 | depthStencilState = device.makeDepthStencilState( descriptor: desc ) 38 | } 39 | 40 | 41 | func createPipelineState( colorPixelFormat : MTLPixelFormat ) { 42 | 43 | let library = device.makeDefaultLibrary() 44 | 45 | let descriptor = MTLRenderPipelineDescriptor() 46 | 47 | descriptor.vertexFunction = library!.makeFunction( name: Self.VertexFunctionNamePositionUV ) 48 | descriptor.fragmentFunction = library!.makeFunction( name: Self.FragmentFunctionNameSDFont ) 49 | 50 | descriptor.vertexDescriptor = MTLVertexDescriptor.positionUV() 51 | descriptor.colorAttachments[0].pixelFormat = colorPixelFormat 52 | 53 | descriptor.colorAttachments[0].isBlendingEnabled = true 54 | descriptor.colorAttachments[0].rgbBlendOperation = .add 55 | descriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha 56 | descriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha 57 | descriptor.colorAttachments[0].alphaBlendOperation = .add; 58 | descriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha; 59 | descriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha; 60 | 61 | descriptor.depthAttachmentPixelFormat = .depth32Float 62 | 63 | do { 64 | pipelineState = try device.makeRenderPipelineState( descriptor: descriptor ) 65 | } catch let error { 66 | fatalError( error.localizedDescription ) 67 | } 68 | } 69 | 70 | func renderModelSDFont( 71 | encoder : MTLRenderCommandEncoder, 72 | bufferVertexIn : MTLBuffer, 73 | bufferUniformPerInstance : MTLBuffer, 74 | numInstances : Int, 75 | bufferIndices : MTLBuffer, 76 | numIndices : Int, 77 | bufferUniformSDFont : MTLBuffer, 78 | textureSDFont : MTLTexture, 79 | wireFrame : Bool = false 80 | ) { 81 | encoder.setRenderPipelineState( pipelineState! ) 82 | encoder.setDepthStencilState( depthStencilState ) 83 | 84 | encoder.setVertexBuffer( bufferVertexIn, offset: 0, index: Self.VertexBufferIndexVertexIn ) 85 | encoder.setVertexBuffer( bufferUniformPerScene, offset: 0, index: Self.VertexBufferIndexUniformPerScene ) 86 | encoder.setVertexBuffer( bufferUniformPerInstance, offset: 0, index: Self.VertexBufferIndexUniformPerInstance ) 87 | 88 | encoder.setFragmentBuffer( bufferUniformSDFont, offset: 0, index: Self.FragmentBufferIndexUniformSDFont ) 89 | 90 | encoder.setFragmentTexture( textureSDFont, index: Self.TextureIndexSDFont ) 91 | 92 | encoder.setFragmentSamplerState( samplerState, index: 0 ) 93 | 94 | if wireFrame { 95 | encoder.setTriangleFillMode( .lines ) 96 | } 97 | 98 | encoder.drawIndexedPrimitives( 99 | type: .triangle, 100 | indexCount: numIndices, 101 | indexType: .uint32, 102 | indexBuffer: bufferIndices, 103 | indexBufferOffset: 0, 104 | instanceCount: numInstances, 105 | baseVertex: 0, 106 | baseInstance: 0 107 | ) 108 | 109 | if wireFrame { 110 | encoder.setTriangleFillMode( .fill ) 111 | } 112 | } 113 | 114 | static func buildSamplerStateSDFont( device: MTLDevice ) -> MTLSamplerState? { 115 | 116 | let descriptor = MTLSamplerDescriptor() 117 | descriptor.mipFilter = .linear 118 | descriptor.minFilter = .nearest; 119 | descriptor.magFilter = .linear; 120 | descriptor.sAddressMode = .clampToZero; 121 | descriptor.tAddressMode = .clampToZero; 122 | descriptor.maxAnisotropy = Self.SamplerStateMaxAnisotropy 123 | 124 | let samplerState = device.makeSamplerState( descriptor: descriptor ) 125 | 126 | return samplerState 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /SWOpeningRollAR/rendering/PerSceneRenderHelper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | 4 | class PerSceneRenderHelper { 5 | 6 | static let FragmentFunctionNameSDFont = "fragment_sdfont" 7 | static let VertexFunctionNamePositionUV = "vertex_position_uv" 8 | 9 | static let SamplerStateMaxAnisotropy = 4 10 | 11 | static let VertexBufferIndexVertexIn = 0 12 | static let VertexBufferIndexUniformPerScene = 1 13 | static let VertexBufferIndexUniformPerInstance = 2 14 | 15 | static let FragmentBufferIndexUniformPerScene = 1 16 | static let FragmentBufferIndexUniformSDFont = 4 17 | 18 | static let TextureIndexSDFont = 3 19 | 20 | let device : MTLDevice 21 | var pipelineState : MTLRenderPipelineState? 22 | var depthStencilState : MTLDepthStencilState? 23 | let bufferUniformPerScene : MTLBuffer? 24 | var samplerState : MTLSamplerState? 25 | 26 | init( 27 | device : MTLDevice, 28 | bufferUniformPerScene : MTLBuffer 29 | ) { 30 | self.device = device 31 | self.bufferUniformPerScene = bufferUniformPerScene 32 | self.samplerState = Self.buildSamplerStateSDFont( device: device ) 33 | 34 | let desc = MTLDepthStencilDescriptor() 35 | desc.depthCompareFunction = .always 36 | desc.isDepthWriteEnabled = false 37 | depthStencilState = device.makeDepthStencilState( descriptor: desc ) 38 | } 39 | 40 | 41 | func createPipelineState( colorPixelFormat : MTLPixelFormat ) { 42 | 43 | let library = device.makeDefaultLibrary() 44 | 45 | let descriptor = MTLRenderPipelineDescriptor() 46 | 47 | descriptor.vertexFunction = library!.makeFunction( name: Self.VertexFunctionNamePositionUV ) 48 | descriptor.fragmentFunction = library!.makeFunction( name: Self.FragmentFunctionNameSDFont ) 49 | 50 | descriptor.vertexDescriptor = MTLVertexDescriptor.positionUV() 51 | descriptor.colorAttachments[0].pixelFormat = colorPixelFormat 52 | 53 | descriptor.colorAttachments[0].isBlendingEnabled = true 54 | descriptor.colorAttachments[0].rgbBlendOperation = .add 55 | descriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha 56 | descriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha 57 | descriptor.colorAttachments[0].alphaBlendOperation = .add; 58 | descriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha; 59 | descriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha; 60 | 61 | descriptor.depthAttachmentPixelFormat = .depth32Float 62 | 63 | do { 64 | pipelineState = try device.makeRenderPipelineState( descriptor: descriptor ) 65 | } catch let error { 66 | fatalError( error.localizedDescription ) 67 | } 68 | } 69 | 70 | func renderModelSDFont( 71 | encoder : MTLRenderCommandEncoder, 72 | bufferVertexIn : MTLBuffer, 73 | bufferUniformPerInstance : MTLBuffer, 74 | numInstances : Int, 75 | bufferIndices : MTLBuffer, 76 | numIndices : Int, 77 | bufferUniformSDFont : MTLBuffer, 78 | textureSDFont : MTLTexture, 79 | wireFrame : Bool = false 80 | ) { 81 | encoder.setRenderPipelineState( pipelineState! ) 82 | encoder.setDepthStencilState( depthStencilState ) 83 | 84 | encoder.setVertexBuffer( bufferVertexIn, offset: 0, index: Self.VertexBufferIndexVertexIn ) 85 | encoder.setVertexBuffer( bufferUniformPerScene, offset: 0, index: Self.VertexBufferIndexUniformPerScene ) 86 | encoder.setVertexBuffer( bufferUniformPerInstance, offset: 0, index: Self.VertexBufferIndexUniformPerInstance ) 87 | 88 | encoder.setFragmentBuffer( bufferUniformSDFont, offset: 0, index: Self.FragmentBufferIndexUniformSDFont ) 89 | 90 | encoder.setFragmentTexture( textureSDFont, index: Self.TextureIndexSDFont ) 91 | 92 | encoder.setFragmentSamplerState( samplerState, index: 0 ) 93 | 94 | if wireFrame { 95 | encoder.setTriangleFillMode( .lines ) 96 | } 97 | 98 | encoder.drawIndexedPrimitives( 99 | type: .triangle, 100 | indexCount: numIndices, 101 | indexType: .uint32, 102 | indexBuffer: bufferIndices, 103 | indexBufferOffset: 0, 104 | instanceCount: numInstances, 105 | baseVertex: 0, 106 | baseInstance: 0 107 | ) 108 | 109 | if wireFrame { 110 | encoder.setTriangleFillMode( .fill ) 111 | } 112 | } 113 | 114 | static func buildSamplerStateSDFont( device: MTLDevice ) -> MTLSamplerState? { 115 | 116 | let descriptor = MTLSamplerDescriptor() 117 | descriptor.mipFilter = .linear 118 | descriptor.minFilter = .nearest; 119 | descriptor.magFilter = .linear; 120 | descriptor.sAddressMode = .clampToZero; 121 | descriptor.tAddressMode = .clampToZero; 122 | descriptor.maxAnisotropy = Self.SamplerStateMaxAnisotropy 123 | 124 | let samplerState = device.makeSamplerState( descriptor: descriptor ) 125 | 126 | return samplerState 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /SWOpeningRoll/rendering/RenderUniforms.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | 4 | struct UniformPerInstance { 5 | 6 | // MemoryLayout.stride 64 7 | // MemoryLayout.size 64 8 | // MemoryLayout.alignment 16 9 | 10 | var modelMatrix : float4x4 11 | 12 | public init ( modelMatrix: matrix_float4x4 ) { 13 | self.modelMatrix = modelMatrix 14 | } 15 | 16 | public init () { 17 | modelMatrix = matrix_identity_float4x4 18 | } 19 | 20 | static func generateMTLBuffer( device: MTLDevice, instances : [Self] ) -> MTLBuffer? { 21 | return device.makeBuffer(bytes: instances, length: MemoryLayout.stride * instances.count, options: .storageModeShared ) 22 | } 23 | 24 | static func updateMTLBuffer( buffer : MTLBuffer, instances : [Self] ) { 25 | 26 | let rawPointer = buffer.contents() 27 | let typedPointer = rawPointer.bindMemory( to: Self.self, capacity: MemoryLayout.stride ) 28 | let bufferedPointer = UnsafeMutableBufferPointer( start: typedPointer, count: instances.count ) 29 | for (index, instance) in instances.enumerated() { 30 | bufferedPointer[index] = instance 31 | } 32 | // NOTE: The following does not work with optimization as of XCode 14.2 33 | // buffer.contents().copyMemory( from: instances, byteCount: MemoryLayout.stride * instances.count ) 34 | } 35 | } 36 | 37 | struct UniformPerScene { 38 | 39 | // MemoryLayout.stride 160 40 | // MemoryLayout.size 148 41 | // MemoryLayout.alignment 16 42 | 43 | var viewMatrix : float4x4 44 | var projectionMatrix : float4x4 45 | var cameraPosition : SIMD3 46 | var numLights : Int32 47 | 48 | public init( viewMatrix: float4x4, projectionMatrix: float4x4, cameraPosition : SIMD3, numLights: Int ) { 49 | self.viewMatrix = viewMatrix 50 | self.projectionMatrix = projectionMatrix 51 | self.cameraPosition = cameraPosition 52 | self.numLights = Int32(numLights) 53 | } 54 | 55 | public init() { 56 | viewMatrix = matrix_identity_float4x4 57 | projectionMatrix = matrix_identity_float4x4 58 | cameraPosition = SIMD3( 0.0, 0.0, 0.0 ) 59 | self.numLights = 0 60 | } 61 | 62 | mutating func update( camera: Camera ) { 63 | viewMatrix = camera.viewMatrixLHS 64 | projectionMatrix = camera.projectionMatrix 65 | } 66 | 67 | static func generateMTLBuffer( device: MTLDevice, uniformPerScene : inout Self ) -> MTLBuffer? { 68 | return device.makeBuffer(bytes: &uniformPerScene, length: MemoryLayout.stride, options: .storageModeShared ) 69 | } 70 | 71 | static func updateMTLBuffer( buffer : MTLBuffer, uniformPerScene : inout Self ) { 72 | let rawPointer = buffer.contents() 73 | let typedPointer = rawPointer.bindMemory( to: Self.self, capacity: MemoryLayout.stride ) 74 | let bufferedPointer = UnsafeMutableBufferPointer( start: typedPointer, count: 1 ) 75 | bufferedPointer[0] = uniformPerScene 76 | // NOTE: The following does not work with optimization as of XCode 14.2 77 | // buffer.contents().copyMemory( from: &uniformPerScene, byteCount: MemoryLayout.stride ) 78 | } 79 | } 80 | 81 | 82 | struct UniformSDFont { 83 | 84 | enum FunctionType : Int32 { 85 | case SDFONT_PASS_THROUGH = 0 86 | case SDFONT_STEP = 1 87 | case SDFONT_SMOOTH_STEP = 2 88 | case SDFONT_SLOPE_STEP = 3 89 | case SDFONT_TRAPEZOID = 4 90 | case SDFONT_TWIN_PEAKS = 5 91 | case SDFONT_HALO = 6 92 | } 93 | 94 | var foregroundColor : SIMD4 95 | var funcType : Int32 96 | var width : Float 97 | var point1 : Float 98 | var point2 : Float 99 | 100 | init( 101 | foregroundColor : SIMD4, 102 | funcType : FunctionType, 103 | point1 : Float, 104 | point2 : Float, 105 | width : Float 106 | ) { 107 | self.foregroundColor = foregroundColor 108 | self.funcType = funcType.rawValue 109 | self.width = width 110 | self.point1 = point1 111 | self.point2 = point2 112 | } 113 | 114 | init() { 115 | self.foregroundColor = SIMD4(1.0, 1.0, 1.0, 1.0) 116 | self.funcType = FunctionType.SDFONT_PASS_THROUGH.rawValue 117 | self.width = 0 118 | self.point1 = 0 119 | self.point2 = 0 120 | } 121 | 122 | static func generateMTLBuffer( device: MTLDevice, v : inout Self ) -> MTLBuffer? { 123 | return device.makeBuffer(bytes: &v, length: MemoryLayout.stride, options: .storageModeShared ) 124 | } 125 | 126 | static func updateMTLBuffer( buffer : MTLBuffer, v : inout Self ) { 127 | let rawPointer = buffer.contents() 128 | let typedPointer = rawPointer.bindMemory( to: Self.self, capacity: MemoryLayout.stride ) 129 | let bufferedPointer = UnsafeMutableBufferPointer( start: typedPointer, count: 1 ) 130 | bufferedPointer[0] = v 131 | // NOTE: The following does not work with optimization as of XCode 14.2 132 | // buffer.contents().copyMemory( from: &v, byteCount: MemoryLayout.stride ) 133 | } 134 | } 135 | 136 | -------------------------------------------------------------------------------- /SWOpeningRollAR/rendering/RenderUniforms.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | 4 | struct UniformPerInstance { 5 | 6 | // MemoryLayout.stride 64 7 | // MemoryLayout.size 64 8 | // MemoryLayout.alignment 16 9 | 10 | var modelMatrix : float4x4 11 | 12 | public init ( modelMatrix: matrix_float4x4 ) { 13 | self.modelMatrix = modelMatrix 14 | } 15 | 16 | public init () { 17 | modelMatrix = matrix_identity_float4x4 18 | } 19 | 20 | static func generateMTLBuffer( device: MTLDevice, instances : [Self] ) -> MTLBuffer? { 21 | return device.makeBuffer(bytes: instances, length: MemoryLayout.stride * instances.count, options: .storageModeShared ) 22 | } 23 | 24 | static func updateMTLBuffer( buffer : MTLBuffer, instances : [Self] ) { 25 | 26 | let rawPointer = buffer.contents() 27 | let typedPointer = rawPointer.bindMemory( to: Self.self, capacity: MemoryLayout.stride * instances.count ) 28 | let bufferedPointer = UnsafeMutableBufferPointer( start: typedPointer, count: instances.count ) 29 | for (index, instance) in instances.enumerated() { 30 | bufferedPointer[index] = instance 31 | } 32 | // NOTE: The following does not work with optimization as of XCode 14.2 33 | //buffer.contents().copyMemory( from: instances, byteCount: MemoryLayout.stride * instances.count ) 34 | } 35 | } 36 | 37 | struct UniformPerScene { 38 | 39 | // MemoryLayout.stride 160 40 | // MemoryLayout.size 148 41 | // MemoryLayout.alignment 16 42 | 43 | var viewMatrix : float4x4 44 | var projectionMatrix : float4x4 45 | var cameraPosition : SIMD3 46 | var numLights : Int32 47 | 48 | public init( viewMatrix: float4x4, projectionMatrix: float4x4, cameraPosition : SIMD3, numLights: Int ) { 49 | self.viewMatrix = viewMatrix 50 | self.projectionMatrix = projectionMatrix 51 | self.cameraPosition = cameraPosition 52 | self.numLights = Int32(numLights) 53 | } 54 | 55 | public init() { 56 | viewMatrix = matrix_identity_float4x4 57 | projectionMatrix = matrix_identity_float4x4 58 | cameraPosition = SIMD3( 0.0, 0.0, 0.0 ) 59 | self.numLights = 0 60 | } 61 | 62 | mutating func update( camera: Camera ) { 63 | viewMatrix = camera.viewMatrixLHS 64 | projectionMatrix = camera.projectionMatrix 65 | } 66 | 67 | static func generateMTLBuffer( device: MTLDevice, uniformPerScene : inout Self ) -> MTLBuffer? { 68 | return device.makeBuffer(bytes: &uniformPerScene, length: MemoryLayout.stride, options: .storageModeShared ) 69 | } 70 | 71 | static func updateMTLBuffer( buffer : MTLBuffer, uniformPerScene : inout Self ) { 72 | let rawPointer = buffer.contents() 73 | let typedPointer = rawPointer.bindMemory( to: Self.self, capacity: MemoryLayout.stride ) 74 | let bufferedPointer = UnsafeMutableBufferPointer( start: typedPointer, count: 1 ) 75 | bufferedPointer[0] = uniformPerScene 76 | // NOTE: The following does not work with optimization as of XCode 14.2 77 | // buffer.contents().copyMemory( from: &uniformPerScene, byteCount: MemoryLayout.stride ) 78 | } 79 | } 80 | 81 | 82 | struct UniformSDFont { 83 | 84 | enum FunctionType : Int32 { 85 | case SDFONT_PASS_THROUGH = 0 86 | case SDFONT_STEP = 1 87 | case SDFONT_SMOOTH_STEP = 2 88 | case SDFONT_SLOPE_STEP = 3 89 | case SDFONT_TRAPEZOID = 4 90 | case SDFONT_TWIN_PEAKS = 5 91 | case SDFONT_HALO = 6 92 | } 93 | 94 | var foregroundColor : SIMD4 95 | var funcType : Int32 96 | var width : Float 97 | var point1 : Float 98 | var point2 : Float 99 | 100 | init( 101 | foregroundColor : SIMD4, 102 | funcType : FunctionType, 103 | point1 : Float, 104 | point2 : Float, 105 | width : Float 106 | ) { 107 | self.foregroundColor = foregroundColor 108 | self.funcType = funcType.rawValue 109 | self.width = width 110 | self.point1 = point1 111 | self.point2 = point2 112 | } 113 | 114 | init() { 115 | self.foregroundColor = SIMD4(1.0, 1.0, 1.0, 1.0) 116 | self.funcType = FunctionType.SDFONT_PASS_THROUGH.rawValue 117 | self.width = 0 118 | self.point1 = 0 119 | self.point2 = 0 120 | } 121 | 122 | static func generateMTLBuffer( device: MTLDevice, v : inout Self ) -> MTLBuffer? { 123 | return device.makeBuffer(bytes: &v, length: MemoryLayout.stride, options: .storageModeShared ) 124 | } 125 | 126 | static func updateMTLBuffer( buffer : MTLBuffer, v : inout Self ) { 127 | let rawPointer = buffer.contents() 128 | let typedPointer = rawPointer.bindMemory( to: Self.self, capacity: MemoryLayout.stride ) 129 | let bufferedPointer = UnsafeMutableBufferPointer( start: typedPointer, count: 1 ) 130 | bufferedPointer[0] = v 131 | // NOTE: The following does not work with optimization as of XCode 14.2 132 | // buffer.contents().copyMemory( from: &v, byteCount: MemoryLayout.stride ) 133 | } 134 | } 135 | 136 | -------------------------------------------------------------------------------- /SDFont/SDFont/SDFont.metal: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace metal; 3 | 4 | typedef struct _ConfigGen { 5 | int draw_area_side_len; // number of pixels per row. 6 | float spread_thickness; // it detemines the rectangular area for searching signed distance. 7 | int width; // width of the glyph including the spread. 8 | int height; // height of the glyph including the spread. 9 | } ConfigGen; 10 | 11 | 12 | // Brute-force signed-distance finder. Each pixel gets one thread to calculate 13 | // the signed distance (distance from the closest pixel of the different value.) 14 | // Interpretation of the output float value: 15 | // * 0.5 <= v : the pixel is inside the glyph and v is the distance to the closest pixel outside the glyph 16 | // * -0.5 >= v : the pixel is outside the glyph and v is the distance to the closest pixel inside the glyph 17 | kernel void generate_signed_distance ( 18 | device const ConfigGen& config [[ buffer(0)]], 19 | device const uint8_t* pixmap_buffer [[ buffer(1)]], 20 | device float* sd_buffer [[ buffer(2)]], 21 | const uint tid [[ thread_position_in_grid ]] 22 | ) { 23 | if ( (int)tid < config.width * config.height ) { 24 | 25 | const int base_i = tid % config.width; 26 | const int base_j = tid / config.width; 27 | 28 | const int v = pixmap_buffer[ base_j * config.draw_area_side_len + base_i ]; 29 | const int spread = (int)config.spread_thickness; 30 | 31 | float sq_dist_min = (float)config.draw_area_side_len * (float)config.draw_area_side_len * 4.0; // arbitrary large value 32 | 33 | for ( int j = -1 * spread; j < spread; j++ ) { 34 | 35 | if ( base_j + j >= 0 && base_j + j < config.height ) { 36 | 37 | for ( int i = -1 * spread; i < spread; i++ ) { 38 | 39 | if ( base_i + i >= 0 && base_i + i < config.width ) { 40 | 41 | if ( (i != 0) || (j != 0) ) { 42 | 43 | const int v_other = pixmap_buffer[ ( base_j + j ) * config.draw_area_side_len + base_i + i ]; 44 | 45 | if ( ( v >= 128 && v_other < 128 ) || ( v < 128 && v_other >= 128 ) ) { 46 | 47 | const float sq_dist = (float)( i * i + j * j ); 48 | 49 | if ( sq_dist_min > sq_dist ) { 50 | sq_dist_min = sq_dist; 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | const float normalized_min_dist = ( /*precise::*/sqrt(sq_dist_min) - 1.0 ) / config.spread_thickness; 60 | if ( v >= 128 ) { 61 | // inside glyph 62 | sd_buffer[ base_j * config.draw_area_side_len + base_i ] = 0.5 + normalized_min_dist / 2.0; 63 | } 64 | else { 65 | // outside glyph 66 | sd_buffer[ base_j * config.draw_area_side_len + base_i ] = 0.5 - normalized_min_dist / 2.0; 67 | } 68 | } 69 | } 70 | 71 | typedef struct _ConfigDownsample { 72 | 73 | // glyph-local upsampled space 74 | int side_len_src; 75 | int width_src; 76 | int height_src; 77 | 78 | // output texture space 79 | int side_len_dst; 80 | int width_dst; 81 | int height_dst; 82 | int origin_x_dst; 83 | int origin_y_dst; 84 | 85 | int upsampling_factor; 86 | float spread_thickness; 87 | 88 | int flip_y; 89 | 90 | } ConfigDownsample; 91 | 92 | 93 | // Each output value in uint8 is calculated from the square region of the upsampled signed distance values, 94 | // whose side length is config.upsampling_factor. 95 | // The output value is the average of the square resion, re-scaled to the down-sampled space. 96 | kernel void downsample ( 97 | device const ConfigDownsample& config [[ buffer(0)]], 98 | device const float* sd_buffer_src [[ buffer(1)]], 99 | device uint8_t* sd_buffer_dst [[ buffer(2)]], 100 | const uint tid [[ thread_position_in_grid ]] 101 | ) { 102 | if ( (int)tid < config.width_dst * config.height_dst ) { 103 | 104 | const int i_dst = tid % config.width_dst; 105 | const int j_dst = tid / config.width_dst; 106 | 107 | const int base_i_src = i_dst * config.upsampling_factor; 108 | const int base_j_src = j_dst * config.upsampling_factor; 109 | 110 | const float sd_in_upsampled = sd_buffer_src[ base_j_src * config.side_len_src + base_i_src ]; 111 | const long quantized_sd = (long)( sd_in_upsampled * 256.0 ); 112 | const long clamped_sd = max( (long)0, ( min( (long)255, quantized_sd ) ) ); 113 | 114 | if ( config.flip_y ) { 115 | 116 | sd_buffer_dst[ ( ( config.side_len_dst - 1 ) - ( config.origin_y_dst + j_dst ) ) * config.side_len_dst + config.origin_x_dst + i_dst ] = (uint8_t)clamped_sd; 117 | } 118 | else { 119 | sd_buffer_dst[ (config.origin_y_dst + j_dst) * config.side_len_dst + config.origin_x_dst + i_dst ] = (uint8_t)clamped_sd; 120 | } 121 | } 122 | } 123 | 124 | 125 | typedef struct _ConfigZeroInitialize { 126 | int length_in_int32; 127 | } ConfigZeroInitialize; 128 | 129 | kernel void initialize_with_zero ( 130 | device const ConfigZeroInitialize& config [[ buffer(0)]], 131 | device int* buf [[ buffer(1)]], 132 | const uint tid [[ thread_position_in_grid ]], 133 | const uint threads_per_threadgroup [[ threads_per_threadgroup ]] 134 | ) { 135 | for( int i = tid; i < config.length_in_int32 ; i += threads_per_threadgroup ) { 136 | buf[i] = 0; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /SDFont/SDFont/SDFontMetalSourceCode.swift: -------------------------------------------------------------------------------- 1 | let SDFontMetalSourceCode = """ 2 | #include 3 | using namespace metal; 4 | 5 | typedef struct _ConfigGen { 6 | int draw_area_side_len; // number of pixels per row. 7 | float spread_thickness; // it detemines the rectangular area for searching signed distance. 8 | int width; // width of the glyph including the spread. 9 | int height; // height of the glyph including the spread. 10 | } ConfigGen; 11 | 12 | 13 | // Brute-force signed-distance finder. Each pixel gets one thread to calculate 14 | // the signed distance (distance from the closest pixel of the different value.) 15 | // Interpretation of the output float value: 16 | // * 0.5 <= v : the pixel is inside the glyph and v is the distance to the closest pixel outside the glyph 17 | // * -0.5 >= v : the pixel is outside the glyph and v is the distance to the closest pixel inside the glyph 18 | kernel void generate_signed_distance ( 19 | device const ConfigGen& config [[ buffer(0)]], 20 | device const uint8_t* pixmap_buffer [[ buffer(1)]], 21 | device float* sd_buffer [[ buffer(2)]], 22 | const uint tid [[ thread_position_in_grid ]] 23 | ) { 24 | if ( (int)tid < config.width * config.height ) { 25 | 26 | const int base_i = tid % config.width; 27 | const int base_j = tid / config.width; 28 | 29 | const int v = pixmap_buffer[ base_j * config.draw_area_side_len + base_i ]; 30 | const int spread = (int)config.spread_thickness; 31 | 32 | float sq_dist_min = (float)config.draw_area_side_len * (float)config.draw_area_side_len * 4.0; // arbitrary large value 33 | 34 | for ( int j = -1 * spread; j < spread; j++ ) { 35 | 36 | if ( base_j + j >= 0 && base_j + j < config.height ) { 37 | 38 | for ( int i = -1 * spread; i < spread; i++ ) { 39 | 40 | if ( base_i + i >= 0 && base_i + i < config.width ) { 41 | 42 | if ( (i != 0) || (j != 0) ) { 43 | 44 | const int v_other = pixmap_buffer[ ( base_j + j ) * config.draw_area_side_len + base_i + i ]; 45 | 46 | if ( ( v >= 128 && v_other < 128 ) || ( v < 128 && v_other >= 128 ) ) { 47 | 48 | const float sq_dist = (float)( i * i + j * j ); 49 | 50 | if ( sq_dist_min > sq_dist ) { 51 | sq_dist_min = sq_dist; 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | const float normalized_min_dist = ( /*precise::*/sqrt(sq_dist_min) - 1.0 ) / config.spread_thickness; 61 | if ( v >= 128 ) { 62 | // inside glyph 63 | sd_buffer[ base_j * config.draw_area_side_len + base_i ] = 0.5 + normalized_min_dist / 2.0; 64 | } 65 | else { 66 | // outside glyph 67 | sd_buffer[ base_j * config.draw_area_side_len + base_i ] = 0.5 - normalized_min_dist / 2.0; 68 | } 69 | } 70 | } 71 | 72 | typedef struct _ConfigDownsample { 73 | 74 | // glyph-local upsampled space 75 | int side_len_src; 76 | int width_src; 77 | int height_src; 78 | 79 | // output texture space 80 | int side_len_dst; 81 | int width_dst; 82 | int height_dst; 83 | int origin_x_dst; 84 | int origin_y_dst; 85 | 86 | int upsampling_factor; 87 | float spread_thickness; 88 | 89 | int flip_y; 90 | 91 | } ConfigDownsample; 92 | 93 | 94 | // Each output value in uint8 is calculated from the square region of the upsampled signed distance values, 95 | // whose side length is config.upsampling_factor. 96 | // The output value is the average of the square resion, re-scaled to the down-sampled space. 97 | kernel void downsample ( 98 | device const ConfigDownsample& config [[ buffer(0)]], 99 | device const float* sd_buffer_src [[ buffer(1)]], 100 | device uint8_t* sd_buffer_dst [[ buffer(2)]], 101 | const uint tid [[ thread_position_in_grid ]] 102 | ) { 103 | if ( (int)tid < config.width_dst * config.height_dst ) { 104 | 105 | const int i_dst = tid % config.width_dst; 106 | const int j_dst = tid / config.width_dst; 107 | 108 | const int base_i_src = i_dst * config.upsampling_factor; 109 | const int base_j_src = j_dst * config.upsampling_factor; 110 | 111 | const float sd_in_upsampled = sd_buffer_src[ base_j_src * config.side_len_src + base_i_src ]; 112 | const long quantized_sd = (long)( sd_in_upsampled * 256.0 ); 113 | const long clamped_sd = max( (long)0, ( min( (long)255, quantized_sd ) ) ); 114 | 115 | if ( config.flip_y ) { 116 | 117 | sd_buffer_dst[ ( ( config.side_len_dst - 1 ) - ( config.origin_y_dst + j_dst ) ) * config.side_len_dst + config.origin_x_dst + i_dst ] = (uint8_t)clamped_sd; 118 | } 119 | else { 120 | sd_buffer_dst[ (config.origin_y_dst + j_dst) * config.side_len_dst + config.origin_x_dst + i_dst ] = (uint8_t)clamped_sd; 121 | } 122 | } 123 | } 124 | 125 | 126 | typedef struct _ConfigZeroInitialize { 127 | int length_in_int32; 128 | } ConfigZeroInitialize; 129 | 130 | kernel void initialize_with_zero ( 131 | device const ConfigZeroInitialize& config [[ buffer(0)]], 132 | device int* buf [[ buffer(1)]], 133 | const uint tid [[ thread_position_in_grid ]], 134 | const uint threads_per_threadgroup [[ threads_per_threadgroup ]] 135 | ) { 136 | for( int i = tid; i < config.length_in_int32 ; i += threads_per_threadgroup ) { 137 | buf[i] = 0; 138 | } 139 | } 140 | """ 141 | -------------------------------------------------------------------------------- /SDFont/sdfontgen/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | import UniformTypeIdentifiers 4 | //import SDFont 5 | 6 | var helpFound : Bool = false 7 | var verboseFound : Bool = false 8 | var showFontListFound : Bool = false 9 | var fontName : String = "" 10 | var fontNameExpected : Bool = false 11 | var glyphNumCutoff : Int = 0 12 | var glyphNumCutoffExpected : Bool = false 13 | var spread : Double = 0 14 | var spreadExpected : Bool = false 15 | var upSample : Int = 1 16 | var upSampleExpected : Bool = false 17 | var outputPath : String = "" 18 | var outputPathExpected : Bool = false 19 | var textureSize : Int = 512 20 | var textureSizeExpected : Bool = false 21 | 22 | for arg in CommandLine.arguments { 23 | 24 | switch arg { 25 | 26 | case "-h", "--help": 27 | helpFound = true 28 | 29 | case "-verbose": 30 | verboseFound = true 31 | 32 | case "-showfontlist": 33 | showFontListFound = true 34 | 35 | case "-fontname": 36 | fontNameExpected = true 37 | 38 | case "-glyphnumcutoff": 39 | glyphNumCutoffExpected = true 40 | 41 | case "-spread": 42 | spreadExpected = true 43 | 44 | case "-upsample": 45 | upSampleExpected = true 46 | 47 | case "-outputpath": 48 | outputPathExpected = true 49 | 50 | case "-texturesize": 51 | textureSizeExpected = true 52 | 53 | default: 54 | 55 | if fontNameExpected { 56 | fontName = arg 57 | } 58 | else if glyphNumCutoffExpected { 59 | if let intArg = Int(arg) { 60 | glyphNumCutoff = max(0, intArg) 61 | } 62 | } 63 | else if spreadExpected { 64 | if let doubleArg = Double(arg) { 65 | spread = min( 2.0, max(0.0, doubleArg) ) 66 | } 67 | } 68 | else if upSampleExpected { 69 | if let intArg = Int(arg) { 70 | upSample = max(0, intArg) 71 | } 72 | } 73 | else if outputPathExpected { 74 | outputPath = arg 75 | } 76 | else if textureSizeExpected { 77 | if let intArg = Int(arg) { 78 | textureSize = max(0, intArg) 79 | } 80 | } 81 | 82 | fontNameExpected = false 83 | glyphNumCutoffExpected = false 84 | spreadExpected = false 85 | upSampleExpected = false 86 | outputPathExpected = false 87 | textureSizeExpected = false 88 | } 89 | } 90 | 91 | if showFontListFound { 92 | print ("Available fonts:") 93 | print ("(Please consult https://developer.apple.com/fonts/ for the official information.)") 94 | for font in NSFontManager.shared.availableFonts { 95 | print (" [\(font)]") 96 | } 97 | exit(0) 98 | } 99 | 100 | if helpFound || fontName == "" || spread == 0.0 || outputPath == "" { 101 | print ("") 102 | print ("sdfontgen : command-line signed distance font generator for macos.") 103 | print ("") 104 | print ("options") 105 | print ("") 106 | print (" -h/--help: show this message" ) 107 | print ("") 108 | print (" -verbose: show INFO and WARNING messages" ) 109 | print ("") 110 | print (" -showfontlist: show the list of the fonts in the system" ) 111 | print ("") 112 | print (" -fontname : name of the font, preferrably in Postscript name," ) 113 | print (" e.g. -fontname Helvetica" ) 114 | print ("") 115 | print (" -glyphnumcutoff : maximum index of the glyphs to process." ) 116 | print ("") 117 | print (" -spread : specifies the margin around each glyph in the fraction" ) 118 | print (" of the average glyph width and height. Usually within the range of [0.1,0.2].") 119 | print ("") 120 | print (" -upsample : specifies the fontsize in the integer multiple to the original") 121 | print (" font size, in order to sample the glyph bitmaps. The original fontsize") 122 | print (" is determined as the best size to pack the glyphs to the output texture of") 123 | print (" the specified size. For example if the side length of the output texture") 124 | print (" is 2048, and the font is Helvecita with about 2000 glyphs, then the best") 125 | print (" font size will be 43.0. If the upsample factor is 4, then the font size 172.0") 126 | print (" is used to sample the glyph bitmap and to generate signed distance.") 127 | print (" Usually within the range of [2,4].") 128 | print ("") 129 | print (" -texturesize : the length of the sides in pixels of the output texture.") 130 | print (" Usually within the range of [512,4096].") 131 | print ("") 132 | print (" -outputpath : the path in which the output PNG and JSON files are stored.") 133 | print (" If the outputpath is \"/path/to/output\", and fontname is \"Helvetica\",") 134 | print (" then the output files will be /path/to/output/Helvetica.png and /path/to/output/Helvetica.json.") 135 | print ("") 136 | print ("NOTES on -glyphnumcutoff:") 137 | print (" This is useful for example, if you want a small texture size for some games," ) 138 | print (" and you know you use only the first 256 glyphs. However, you should be careful" ) 139 | print (" as the CoreText's typesetter may select a ligature glyph such as 'fi' and 'ff'" ) 140 | print (" whose indices are above 255, even if you use only English alphabets." ) 141 | print ("") 142 | exit(0) 143 | } 144 | 145 | if verboseFound { 146 | print ("sdfontgen : generating signed distance fonts with the following parameters.") 147 | print (" font name: [\(fontName)]") 148 | print (" glyph number cut-off: [\(glyphNumCutoff)]") 149 | print (" spread: [\(spread)]") 150 | print (" up-sample factor: [\(upSample)]") 151 | print (" texture size: [\(textureSize)]") 152 | print (" output path: [\(outputPath)]") 153 | } 154 | 155 | let sdGenerator = SDFontGenerator( 156 | device : MTLCreateSystemDefaultDevice()!, 157 | fontName : fontName, 158 | outputTextureSideLen : textureSize, 159 | spreadFactor : spread, 160 | upSamplingFactor : upSample, 161 | glyphNumCutoff : glyphNumCutoff, 162 | verbose : verboseFound, 163 | usePosixPath : true 164 | ) 165 | 166 | let rtn1 = sdGenerator.writeToPNGFile(fileName: fontName, path: outputPath ) 167 | if !rtn1 { 168 | print ("ERROR: cannot write PNG file [\(outputPath)/\(fontName).png]") 169 | } 170 | 171 | let rtn2 = sdGenerator.writeMetricsToJSONFile(fileName: fontName, path: outputPath ) 172 | if !rtn2 { 173 | print ("ERROR: cannot write JSON file [\(outputPath)/\(fontName).json]") 174 | } 175 | print ("sdfontgen: finished processing.") 176 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ######################### 2 | # .gitignore file for Xcode4 and Xcode5 Source projects 3 | # 4 | # Apple bugs, waiting for Apple to fix/respond: 5 | # 6 | # 15564624 - what does the xccheckout file in Xcode5 do? Where's the documentation? 7 | # 8 | # Version 2.6 9 | # For latest version, see: http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects 10 | # 11 | # 2015 updates: 12 | # - Fixed typo in "xccheckout" line - thanks to @lyck for pointing it out! 13 | # - Fixed the .idea optional ignore. Thanks to @hashier for pointing this out 14 | # - Finally added "xccheckout" to the ignore. Apple still refuses to answer support requests about this, but in practice it seems you should ignore it. 15 | # - minor tweaks from Jona and Coeur (slightly more precise xc* filtering/names) 16 | # 2014 updates: 17 | # - appended non-standard items DISABLED by default (uncomment if you use those tools) 18 | # - removed the edit that an SO.com moderator made without bothering to ask me 19 | # - researched CocoaPods .lock more carefully, thanks to Gokhan Celiker 20 | # 2013 updates: 21 | # - fixed the broken "save personal Schemes" 22 | # - added line-by-line explanations for EVERYTHING (some were missing) 23 | # 24 | # NB: if you are storing "built" products, this WILL NOT WORK, 25 | # and you should use a different .gitignore (or none at all) 26 | # This file is for SOURCE projects, where there are many extra 27 | # files that we want to exclude 28 | # 29 | ######################### 30 | 31 | ##### 32 | # OS X temporary files that should never be committed 33 | # 34 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 35 | 36 | .DS_Store 37 | 38 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 39 | 40 | .Trashes 41 | 42 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 43 | 44 | *.swp 45 | 46 | # 47 | # *.lock - this is used and abused by many editors for many different things. 48 | # For the main ones I use (e.g. Eclipse), it should be excluded 49 | # from source-control, but YMMV. 50 | # (lock files are usually local-only file-synchronization on the local FS that should NOT go in git) 51 | # c.f. the "OPTIONAL" section at bottom though, for tool-specific variations! 52 | # 53 | # In particular, if you're using CocoaPods, you'll want to comment-out this line: 54 | *.lock 55 | 56 | 57 | # 58 | # profile - REMOVED temporarily (on double-checking, I can't find it in OS X docs?) 59 | #profile 60 | 61 | 62 | #### 63 | # Xcode temporary files that should never be committed 64 | # 65 | # NB: NIB/XIB files still exist even on Storyboard projects, so we want this... 66 | 67 | *~.nib 68 | 69 | 70 | #### 71 | # Xcode build files - 72 | # 73 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "DerivedData" 74 | 75 | DerivedData/ 76 | 77 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "build" 78 | 79 | build/ 80 | 81 | 82 | ##### 83 | # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups) 84 | # 85 | # This is complicated: 86 | # 87 | # SOMETIMES you need to put this file in version control. 88 | # Apple designed it poorly - if you use "custom executables", they are 89 | # saved in this file. 90 | # 99% of projects do NOT use those, so they do NOT want to version control this file. 91 | # ..but if you're in the 1%, comment out the line "*.pbxuser" 92 | 93 | # .pbxuser: http://lists.apple.com/archives/xcode-users/2004/Jan/msg00193.html 94 | 95 | *.pbxuser 96 | 97 | # .mode1v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html 98 | 99 | *.mode1v3 100 | 101 | # .mode2v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html 102 | 103 | *.mode2v3 104 | 105 | # .perspectivev3: http://stackoverflow.com/questions/5223297/xcode-projects-what-is-a-perspectivev3-file 106 | 107 | *.perspectivev3 108 | 109 | # NB: also, whitelist the default ones, some projects need to use these 110 | !default.pbxuser 111 | !default.mode1v3 112 | !default.mode2v3 113 | !default.perspectivev3 114 | 115 | 116 | #### 117 | # Xcode 4 - semi-personal settings 118 | # 119 | # Apple Shared data that Apple put in the wrong folder 120 | # c.f. http://stackoverflow.com/a/19260712/153422 121 | # FROM ANSWER: Apple says "don't ignore it" 122 | # FROM COMMENTS: Apple is wrong; Apple code is too buggy to trust; there are no known negative side-effects to ignoring Apple's unofficial advice and instead doing the thing that actively fixes bugs in Xcode 123 | # Up to you, but ... current advice: ignore it. 124 | *.xccheckout 125 | 126 | # 127 | # 128 | # OPTION 1: --------------------------------- 129 | # throw away ALL personal settings (including custom schemes! 130 | # - unless they are "shared") 131 | # As per build/ and DerivedData/, this ought to have a trailing slash 132 | # 133 | # NB: this is exclusive with OPTION 2 below 134 | xcuserdata/ 135 | 136 | # OPTION 2: --------------------------------- 137 | # get rid of ALL personal settings, but KEEP SOME OF THEM 138 | # - NB: you must manually uncomment the bits you want to keep 139 | # 140 | # NB: this *requires* git v1.8.2 or above; you may need to upgrade to latest OS X, 141 | # or manually install git over the top of the OS X version 142 | # NB: this is exclusive with OPTION 1 above 143 | # 144 | #xcuserdata/**/* 145 | 146 | # (requires option 2 above): Personal Schemes 147 | # 148 | #!xcuserdata/**/xcschemes/* 149 | 150 | #### 151 | # XCode 4 workspaces - more detailed 152 | # 153 | # Workspaces are important! They are a core feature of Xcode - don't exclude them :) 154 | # 155 | # Workspace layout is quite spammy. For reference: 156 | # 157 | # /(root)/ 158 | # /(project-name).xcodeproj/ 159 | # project.pbxproj 160 | # /project.xcworkspace/ 161 | # contents.xcworkspacedata 162 | # /xcuserdata/ 163 | # /(your name)/xcuserdatad/ 164 | # UserInterfaceState.xcuserstate 165 | # /xcshareddata/ 166 | # /xcschemes/ 167 | # (shared scheme name).xcscheme 168 | # /xcuserdata/ 169 | # /(your name)/xcuserdatad/ 170 | # (private scheme).xcscheme 171 | # xcschememanagement.plist 172 | # 173 | # 174 | 175 | #### 176 | # Xcode 4 - Deprecated classes 177 | # 178 | # Allegedly, if you manually "deprecate" your classes, they get moved here. 179 | # 180 | # We're using source-control, so this is a "feature" that we do not want! 181 | 182 | *.moved-aside 183 | 184 | #### 185 | # OPTIONAL: Some well-known tools that people use side-by-side with Xcode / iOS development 186 | # 187 | # NB: I'd rather not include these here, but gitignore's design is weak and doesn't allow 188 | # modular gitignore: you have to put EVERYTHING in one file. 189 | # 190 | # COCOAPODS: 191 | # 192 | # c.f. http://guides.cocoapods.org/using/using-cocoapods.html#what-is-a-podfilelock 193 | # c.f. http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 194 | # 195 | #!Podfile.lock 196 | # 197 | # RUBY: 198 | # 199 | # c.f. http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/ 200 | # 201 | #!Gemfile.lock 202 | # 203 | # IDEA: 204 | # 205 | # c.f. https://www.jetbrains.com/objc/help/managing-projects-under-version-control.html?search=workspace.xml 206 | # 207 | #.idea/workspace.xml 208 | # 209 | # TEXTMATE: 210 | # 211 | # -- UNVERIFIED: c.f. http://stackoverflow.com/a/50283/153422 212 | # 213 | #tm_build_errors 214 | 215 | #### 216 | # UNKNOWN: recommended by others, but I can't discover what these files are 217 | # 218 | 219 | bin/ 220 | objs/ 221 | *.*~ 222 | -------------------------------------------------------------------------------- /SDFont/SDFont/SDFontIOHandler.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | import UniformTypeIdentifiers 4 | 5 | class SDFontIOHandler { 6 | 7 | struct Metrics : Decodable { 8 | let x : Double 9 | let y : Double 10 | let width : Double 11 | let height : Double 12 | } 13 | 14 | static func getFileURL( fileName : String, ext : String, path : String?, usePosixPath : Bool ) -> URL { 15 | #if os(iOS) 16 | var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! 17 | if let path = path { 18 | documentsURL = documentsURL.appendingPathComponent( path ) 19 | } 20 | return documentsURL.appendingPathComponent("\(fileName).\(ext)") 21 | #else 22 | var documentsURL : URL 23 | 24 | if usePosixPath { 25 | documentsURL = URL( fileURLWithPath: (path != nil) ? path! : "./" ) 26 | } 27 | else { 28 | documentsURL = URL( fileURLWithPath: FileManager.default.currentDirectoryPath ) 29 | if let path = path { 30 | documentsURL = documentsURL.appendingPathComponent( path ) 31 | } 32 | } 33 | return documentsURL.appendingPathComponent("\(fileName).\(ext)") 34 | #endif 35 | } 36 | 37 | static func loadMetricsFromJSONFile( fileName : String, path : String?, usePosixPath : Bool, verbose : Bool ) -> [CGRect] { 38 | 39 | var outArray : [CGRect] = [] 40 | 41 | let fileURL = Self.getFileURL( fileName : fileName, ext : "json", path : path, usePosixPath : usePosixPath ) 42 | 43 | if verbose { 44 | print ("INFO: JSON file path to load [\(fileURL)]: ") 45 | } 46 | do { 47 | let rawData = try Data( contentsOf: fileURL ) 48 | let decoder = JSONDecoder() 49 | let jsonData: [Metrics] = try! decoder.decode( [Metrics].self, from: rawData ) 50 | for elem in jsonData { 51 | outArray.append( CGRect( x: CGFloat(elem.x), y: CGFloat(elem.y), width: CGFloat(elem.width), height: CGFloat(elem.height) ) ) 52 | } 53 | } catch { 54 | print ( "ERROR: Cannot read and parse json file \(fileName)." ) 55 | } 56 | return outArray 57 | } 58 | 59 | 60 | static func writeMetricsToJSONFile( fileName : String, path : String?, usePosixPath : Bool, bounds : [CGRect], verbose : Bool ) -> Bool { 61 | var array : [Any] = [] 62 | for bound in bounds { 63 | array.append( ["x" : bound.origin.x, "y" : bound.origin.y, "width" : bound.size.width, "height" : bound.size.height ]) 64 | } 65 | 66 | do { 67 | let jsonData = try JSONSerialization.data( withJSONObject: array ) 68 | let jsonString = String( data: jsonData, encoding: String.Encoding.utf8 ) 69 | let fileURL = Self.getFileURL( fileName : fileName, ext : "json", path : path, usePosixPath : usePosixPath ) 70 | if verbose { 71 | print ("INFO: JSON file path to write [\(fileURL)]: ") 72 | } 73 | try jsonString!.write( to: fileURL, atomically: true, encoding: String.Encoding.utf8 ) 74 | 75 | } catch { 76 | return false 77 | } 78 | return true 79 | } 80 | 81 | static func writeMetricsToTSVFile( fileName : String, path : String?, usePosixPath : Bool, bounds : [CGRect], verbose : Bool ) -> Bool { 82 | 83 | var outStr = "" 84 | for bound in bounds { 85 | let str = String( format : "%f\t%f\t%f\t%f\n", bound.origin.x, bound.origin.y, bound.size.width, bound.size.height ) 86 | outStr += str 87 | } 88 | 89 | do { 90 | let fileURL = Self.getFileURL( fileName : fileName, ext : "txt", path : path, usePosixPath : usePosixPath ) 91 | if verbose { 92 | print ("INFO: TSV file path to write [\(fileURL)]: ") 93 | } 94 | try outStr.write( to: fileURL, atomically: true, encoding: String.Encoding.utf8 ) 95 | 96 | } catch { 97 | return false 98 | } 99 | return true 100 | } 101 | 102 | #if os(iOS) 103 | static func generateUIImage( buf : MTLBuffer, sidesLenPixels : Int ) -> UIImage? { 104 | 105 | let cgImage = generateCGImage(buf : buf, sidesLenPixels : sidesLenPixels ) 106 | let uiImage = UIImage( cgImage: cgImage! ) 107 | return uiImage 108 | } 109 | #endif 110 | 111 | static func writeToPNGFile( fileName : String, path : String?, usePosixPath : Bool, buf : MTLBuffer, sidesLenPixels : Int, verbose : Bool ) -> Bool { 112 | 113 | let cgImage = generateCGImage(buf : buf, sidesLenPixels : sidesLenPixels ) 114 | 115 | let fileURL = Self.getFileURL( fileName : fileName, ext : "png", path : path, usePosixPath : usePosixPath ) 116 | 117 | if verbose { 118 | print ("INFO: PNG file path to write [\(fileURL)]: ") 119 | } 120 | 121 | let dest = CGImageDestinationCreateWithURL( fileURL as CFURL, UTType.png.identifier as CFString, 1, nil ) 122 | 123 | if let dest = dest { 124 | CGImageDestinationAddImage( dest, cgImage!, nil ); 125 | return CGImageDestinationFinalize( dest ) 126 | } 127 | return false 128 | } 129 | 130 | static func loadTextureFromPNGFile( device : MTLDevice, fileName : String, path : String?, usePosixPath : Bool, verbose : Bool ) -> MTLTexture? { 131 | 132 | let loader = MTKTextureLoader( device : device ) 133 | 134 | let fileURL = Self.getFileURL( fileName : fileName, ext : "png", path : path, usePosixPath : usePosixPath ) 135 | 136 | if verbose { 137 | print ("INFO: PNG file path to load [\(fileURL)]: ") 138 | } 139 | do { 140 | let texture = try loader.newTexture( 141 | URL : fileURL, 142 | options : [ .origin: MTKTextureLoader.Origin.bottomLeft, 143 | .SRGB: false, 144 | .generateMipmaps: NSNumber(booleanLiteral: true) 145 | ] ) 146 | return texture 147 | } 148 | catch { 149 | print ("ERROR: Cannot load PNG file \(fileName) for MTLTexture.") 150 | return nil 151 | } 152 | } 153 | 154 | static func generateMTLTexture( device : MTLDevice, buf : MTLBuffer, sidesLenPixels : Int ) -> MTLTexture? { 155 | 156 | let cgImage = generateCGImage(buf : buf, sidesLenPixels : sidesLenPixels ) 157 | let loader = MTKTextureLoader( device: device ) 158 | do { 159 | let texture = try loader.newTexture( 160 | cgImage: cgImage!, 161 | options:[ .origin: MTKTextureLoader.Origin.bottomLeft, 162 | .SRGB: false, 163 | .generateMipmaps: NSNumber(booleanLiteral: true) 164 | ] 165 | ) 166 | return texture 167 | } 168 | catch { 169 | print ("ERROR: Cannot generate MTLTexture.") 170 | return nil 171 | } 172 | } 173 | 174 | static func generateCGImage(buf : MTLBuffer, sidesLenPixels : Int ) -> CGImage? { 175 | 176 | let colorSpace : CGColorSpace = CGColorSpaceCreateDeviceGray(); 177 | let bitmapInfo = CGBitmapInfo( rawValue: CGImageAlphaInfo.none.rawValue | CGImageByteOrderInfo.orderDefault.rawValue | CGImagePixelFormatInfo.packed.rawValue ) 178 | 179 | let context = CGContext( 180 | data: buf.contents(), 181 | width: sidesLenPixels, 182 | height: sidesLenPixels, 183 | bitsPerComponent: 8, 184 | bytesPerRow: sidesLenPixels, 185 | space: colorSpace, 186 | bitmapInfo: bitmapInfo.rawValue 187 | ) 188 | context!.setAllowsAntialiasing(false) 189 | return context!.makeImage() 190 | } 191 | } 192 | 193 | -------------------------------------------------------------------------------- /SWOpeningRoll/shared/SDTextPlane.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | import SDFont 4 | 5 | class SDTextPlane { 6 | 7 | static let TextureMargin : CGFloat = 0.001 // margin from the real glyph box to include the spread part 8 | 9 | let device : MTLDevice 10 | var dimension : CGSize 11 | var sdHelper : SDFontRuntimeHelper 12 | var scaleForTypeSetting : CGFloat 13 | 14 | var vertexBuffer : MTLBuffer? 15 | var numVertices : Int 16 | var indexBuffer : MTLBuffer? 17 | var numIndices : Int 18 | 19 | var localTransform : float4x4 20 | var globalTransform : float4x4 21 | var uniformPerInstance : UniformPerInstance 22 | var perInstanceBuffer : MTLBuffer? 23 | 24 | var uniformSDFont : UniformSDFont 25 | var sdFontUniformBuffer : MTLBuffer? 26 | 27 | var sdFontTexture : MTLTexture? 28 | 29 | var animationSequencer : AnimationSequencer? 30 | 31 | init( 32 | device : MTLDevice, 33 | dimension : CGSize, 34 | sdHelper : SDFontRuntimeHelper, 35 | scaleForTypeSetting : CGFloat, 36 | uniformSDFont : UniformSDFont 37 | ) { 38 | self.device = device 39 | self.dimension = dimension 40 | self.sdHelper = sdHelper 41 | self.scaleForTypeSetting = scaleForTypeSetting 42 | 43 | self.numVertices = 0 44 | self.numIndices = 0 45 | self.localTransform = float4x4.identity() 46 | self.globalTransform = float4x4.identity() 47 | self.uniformPerInstance = UniformPerInstance( modelMatrix : self.globalTransform * self.localTransform ) 48 | self.uniformSDFont = uniformSDFont 49 | 50 | self.perInstanceBuffer = UniformPerInstance.generateMTLBuffer( device : device, instances : [uniformPerInstance] ) 51 | self.sdFontUniformBuffer = UniformSDFont.generateMTLBuffer( device : device, v : &self.uniformSDFont ) 52 | 53 | self.sdFontTexture = sdHelper.texture() 54 | } 55 | 56 | func setLocalTransform( translation : SIMD3 ) { 57 | 58 | localTransform.columns.3.x = translation.x 59 | localTransform.columns.3.y = translation.y 60 | localTransform.columns.3.z = translation.z 61 | self.uniformPerInstance = UniformPerInstance( modelMatrix : self.globalTransform * self.localTransform ) 62 | UniformPerInstance.updateMTLBuffer( buffer : perInstanceBuffer!, instances : [uniformPerInstance] ) 63 | } 64 | 65 | func setGlobalTransform( transform : float4x4 ) { 66 | 67 | self.globalTransform = transform 68 | self.uniformPerInstance = UniformPerInstance( modelMatrix : self.globalTransform * self.localTransform ) 69 | UniformPerInstance.updateMTLBuffer( buffer : perInstanceBuffer!, instances : [uniformPerInstance] ) 70 | } 71 | 72 | func rotate90AroundX() { 73 | 74 | localTransform.columns.0 = SIMD4( 1.0, 0.0, 0.0, 0.0 ) 75 | localTransform.columns.1 = SIMD4( 0.0, 0.0, -1.0, 0.0 ) 76 | localTransform.columns.2 = SIMD4( 0.0, 1.0, 0.0, 0.0 ) 77 | self.uniformPerInstance = UniformPerInstance( modelMatrix : self.globalTransform * self.localTransform ) 78 | UniformPerInstance.updateMTLBuffer( buffer : perInstanceBuffer!, instances : [uniformPerInstance] ) 79 | } 80 | 81 | func setAnimation( 82 | temporalPoints : [TimeInterval], 83 | spacialPoints : [SIMD3], 84 | colors : [SIMD4] 85 | ) { 86 | animationSequencer = AnimationSequencer( 87 | temporalPoints : temporalPoints, 88 | spacialPoints : spacialPoints, 89 | colors : colors 90 | ) 91 | } 92 | 93 | func startAnimation() { 94 | animationSequencer!.startAnimation() 95 | } 96 | 97 | func animationActive()-> Bool { 98 | animationSequencer!.animationActive 99 | } 100 | 101 | func step() { 102 | 103 | animationSequencer!.step() 104 | 105 | let s = animationSequencer!.spacialPointNow 106 | setLocalTransform( translation : s ) 107 | uniformSDFont.foregroundColor = animationSequencer!.colorNow 108 | UniformSDFont.updateMTLBuffer( buffer : sdFontUniformBuffer!, v : &uniformSDFont ) 109 | } 110 | 111 | func setText( textPlain: String, lineAlignment: CTTextAlignment ) { 112 | let rectForTypeSetting = CGRect( 113 | x : -0.5 * dimension.width * scaleForTypeSetting, 114 | y : -0.5 * dimension.height * scaleForTypeSetting, 115 | width : dimension.width * scaleForTypeSetting, 116 | height : dimension.height * scaleForTypeSetting 117 | ) 118 | let scaledBounds = sdHelper.typeset( frameRect: rectForTypeSetting, textPlain: textPlain, lineAlignment: lineAlignment ) 119 | 120 | generateVerticesAndIndices( bounds : scaledBounds ) 121 | } 122 | 123 | func generateVerticesAndIndices( bounds : [SDFontRuntimeHelper.GlyphBound] ) 124 | { 125 | var indexBegin : Int32 = 0 126 | 127 | var positions : [ SIMD4 ] = [] 128 | var uvs : [ SIMD2 ] = [] 129 | var indices : [ Int32 ] = [] 130 | 131 | for bound in bounds { 132 | var factor : CGFloat 133 | if bound.textureBound.size.width > bound.textureBound.size.height { 134 | factor = bound.frameBound.size.width / bound.textureBound.size.width 135 | } 136 | else { 137 | factor = bound.frameBound.size.height / bound.textureBound.size.height 138 | } 139 | let frameMargin = factor * Self.TextureMargin 140 | 141 | let xLeft = Float( ( bound.frameBound.origin.x - frameMargin ) / scaleForTypeSetting ) 142 | let xRight = Float( ( bound.frameBound.origin.x + bound.frameBound.size.width + frameMargin ) / scaleForTypeSetting ) 143 | let yLower = Float( ( bound.frameBound.origin.y - frameMargin ) / scaleForTypeSetting ) 144 | let yUpper = Float( ( bound.frameBound.origin.y + bound.frameBound.size.height + frameMargin ) / scaleForTypeSetting ) 145 | 146 | positions.append( SIMD4( xLeft, yLower, 0.0, 1.0 ) ) 147 | positions.append( SIMD4( xRight, yLower, 0.0, 1.0 ) ) 148 | positions.append( SIMD4( xRight, yUpper, 0.0, 1.0 ) ) 149 | positions.append( SIMD4( xLeft, yUpper, 0.0, 1.0 ) ) 150 | 151 | let uLeft = Float( bound.textureBound.origin.x - Self.TextureMargin ) 152 | let uRight = Float( bound.textureBound.origin.x + bound.textureBound.size.width + Self.TextureMargin ) 153 | let vLower = Float( bound.textureBound.origin.y - Self.TextureMargin ) 154 | let vUpper = Float( bound.textureBound.origin.y + bound.textureBound.size.height + Self.TextureMargin ) 155 | 156 | uvs.append( SIMD2( uLeft, vLower ) ) 157 | uvs.append( SIMD2( uRight, vLower ) ) 158 | uvs.append( SIMD2( uRight, vUpper ) ) 159 | uvs.append( SIMD2( uLeft, vUpper ) ) 160 | 161 | indices.append( indexBegin ) 162 | indices.append( indexBegin + 1 ) 163 | indices.append( indexBegin + 2 ) 164 | indices.append( indexBegin ) 165 | indices.append( indexBegin + 2 ) 166 | indices.append( indexBegin + 3 ) 167 | 168 | indexBegin += 4 169 | } 170 | 171 | vertexBuffer = VertexInPositionUV.generateMTLBuffer( 172 | device : device, 173 | positions : positions, 174 | uvs : uvs 175 | ) 176 | 177 | numVertices = positions.count 178 | numIndices = indices.count 179 | indexBuffer = VertexInIndex.generateMTLBuffer( device: device, indices : indices ) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /SWOpeningRollAR/demo/SDTextPlane.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | import SDFont 4 | 5 | class SDTextPlane { 6 | 7 | static let TextureMargin : CGFloat = 0.001 // margin from the real glyph box to include the spread part 8 | 9 | let device : MTLDevice 10 | var dimension : CGSize 11 | var sdHelper : SDFontRuntimeHelper 12 | var scaleForTypeSetting : CGFloat 13 | 14 | var vertexBuffer : MTLBuffer? 15 | var numVertices : Int 16 | var indexBuffer : MTLBuffer? 17 | var numIndices : Int 18 | 19 | var localTransform : float4x4 20 | var globalTransform : float4x4 21 | var uniformPerInstance : UniformPerInstance 22 | var perInstanceBuffer : MTLBuffer? 23 | 24 | var uniformSDFont : UniformSDFont 25 | var sdFontUniformBuffer : MTLBuffer? 26 | 27 | var sdFontTexture : MTLTexture? 28 | 29 | var animationSequencer : AnimationSequencer? 30 | 31 | init( 32 | device : MTLDevice, 33 | dimension : CGSize, 34 | sdHelper : SDFontRuntimeHelper, 35 | scaleForTypeSetting : CGFloat, 36 | uniformSDFont : UniformSDFont 37 | ) { 38 | self.device = device 39 | self.dimension = dimension 40 | self.sdHelper = sdHelper 41 | self.scaleForTypeSetting = scaleForTypeSetting 42 | 43 | self.numVertices = 0 44 | self.numIndices = 0 45 | self.localTransform = float4x4.identity() 46 | self.globalTransform = float4x4.identity() 47 | self.uniformPerInstance = UniformPerInstance( modelMatrix : self.globalTransform * self.localTransform ) 48 | self.uniformSDFont = uniformSDFont 49 | 50 | self.perInstanceBuffer = UniformPerInstance.generateMTLBuffer( device : device, instances : [uniformPerInstance] ) 51 | self.sdFontUniformBuffer = UniformSDFont.generateMTLBuffer( device : device, v : &self.uniformSDFont ) 52 | 53 | self.sdFontTexture = sdHelper.texture() 54 | } 55 | 56 | func setLocalTransform( translation : SIMD3 ) { 57 | 58 | localTransform.columns.3.x = translation.x 59 | localTransform.columns.3.y = translation.y 60 | localTransform.columns.3.z = translation.z 61 | self.uniformPerInstance = UniformPerInstance( modelMatrix : self.globalTransform * self.localTransform ) 62 | UniformPerInstance.updateMTLBuffer( buffer : perInstanceBuffer!, instances : [uniformPerInstance] ) 63 | } 64 | 65 | func setGlobalTransform( transform : float4x4 ) { 66 | 67 | self.globalTransform = transform 68 | self.uniformPerInstance = UniformPerInstance( modelMatrix : self.globalTransform * self.localTransform ) 69 | UniformPerInstance.updateMTLBuffer( buffer : perInstanceBuffer!, instances : [uniformPerInstance] ) 70 | } 71 | 72 | func rotate90AroundX() { 73 | 74 | localTransform.columns.0 = SIMD4( 1.0, 0.0, 0.0, 0.0 ) 75 | localTransform.columns.1 = SIMD4( 0.0, 0.0, -1.0, 0.0 ) 76 | localTransform.columns.2 = SIMD4( 0.0, 1.0, 0.0, 0.0 ) 77 | self.uniformPerInstance = UniformPerInstance( modelMatrix : self.globalTransform * self.localTransform ) 78 | UniformPerInstance.updateMTLBuffer( buffer : perInstanceBuffer!, instances : [uniformPerInstance] ) 79 | } 80 | 81 | func setAnimation( 82 | temporalPoints : [TimeInterval], 83 | spacialPoints : [SIMD3], 84 | colors : [SIMD4] 85 | ) { 86 | animationSequencer = AnimationSequencer( 87 | temporalPoints : temporalPoints, 88 | spacialPoints : spacialPoints, 89 | colors : colors 90 | ) 91 | } 92 | 93 | func startAnimation() { 94 | animationSequencer!.startAnimation() 95 | } 96 | 97 | func animationActive()-> Bool { 98 | animationSequencer!.animationActive 99 | } 100 | 101 | func step() { 102 | 103 | animationSequencer!.step() 104 | 105 | let s = animationSequencer!.spacialPointNow 106 | setLocalTransform( translation : s ) 107 | uniformSDFont.foregroundColor = animationSequencer!.colorNow 108 | UniformSDFont.updateMTLBuffer( buffer : sdFontUniformBuffer!, v : &uniformSDFont ) 109 | } 110 | 111 | func setText( textPlain: String, lineAlignment: CTTextAlignment ) { 112 | let rectForTypeSetting = CGRect( 113 | x : -0.5 * dimension.width * scaleForTypeSetting, 114 | y : -0.5 * dimension.height * scaleForTypeSetting, 115 | width : dimension.width * scaleForTypeSetting, 116 | height : dimension.height * scaleForTypeSetting 117 | ) 118 | let scaledBounds = sdHelper.typeset( frameRect: rectForTypeSetting, textPlain: textPlain, lineAlignment: lineAlignment ) 119 | 120 | generateVerticesAndIndices( bounds : scaledBounds ) 121 | } 122 | 123 | func generateVerticesAndIndices( bounds : [SDFontRuntimeHelper.GlyphBound] ) 124 | { 125 | var indexBegin : Int32 = 0 126 | 127 | var positions : [ SIMD4 ] = [] 128 | var uvs : [ SIMD2 ] = [] 129 | var indices : [ Int32 ] = [] 130 | 131 | for bound in bounds { 132 | var factor : CGFloat 133 | if bound.textureBound.size.width > bound.textureBound.size.height { 134 | factor = bound.frameBound.size.width / bound.textureBound.size.width 135 | } 136 | else { 137 | factor = bound.frameBound.size.height / bound.textureBound.size.height 138 | } 139 | let frameMargin = factor * Self.TextureMargin 140 | 141 | let xLeft = Float( ( bound.frameBound.origin.x - frameMargin ) / scaleForTypeSetting ) 142 | let xRight = Float( ( bound.frameBound.origin.x + bound.frameBound.size.width + frameMargin ) / scaleForTypeSetting ) 143 | let yLower = Float( ( bound.frameBound.origin.y - frameMargin ) / scaleForTypeSetting ) 144 | let yUpper = Float( ( bound.frameBound.origin.y + bound.frameBound.size.height + frameMargin ) / scaleForTypeSetting ) 145 | 146 | positions.append( SIMD4( xLeft, yLower, 0.0, 1.0 ) ) 147 | positions.append( SIMD4( xRight, yLower, 0.0, 1.0 ) ) 148 | positions.append( SIMD4( xRight, yUpper, 0.0, 1.0 ) ) 149 | positions.append( SIMD4( xLeft, yUpper, 0.0, 1.0 ) ) 150 | 151 | let uLeft = Float( bound.textureBound.origin.x - Self.TextureMargin ) 152 | let uRight = Float( bound.textureBound.origin.x + bound.textureBound.size.width + Self.TextureMargin ) 153 | let vLower = Float( bound.textureBound.origin.y - Self.TextureMargin ) 154 | let vUpper = Float( bound.textureBound.origin.y + bound.textureBound.size.height + Self.TextureMargin ) 155 | 156 | uvs.append( SIMD2( uLeft, vLower ) ) 157 | uvs.append( SIMD2( uRight, vLower ) ) 158 | uvs.append( SIMD2( uRight, vUpper ) ) 159 | uvs.append( SIMD2( uLeft, vUpper ) ) 160 | 161 | indices.append( indexBegin ) 162 | indices.append( indexBegin + 1 ) 163 | indices.append( indexBegin + 2 ) 164 | indices.append( indexBegin ) 165 | indices.append( indexBegin + 2 ) 166 | indices.append( indexBegin + 3 ) 167 | 168 | indexBegin += 4 169 | } 170 | 171 | vertexBuffer = VertexInPositionUV.generateMTLBuffer( 172 | device : device, 173 | positions : positions, 174 | uvs : uvs 175 | ) 176 | 177 | numVertices = positions.count 178 | numIndices = indices.count 179 | indexBuffer = VertexInIndex.generateMTLBuffer( device: device, indices : indices ) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /SWOpeningRollAR/SWOpeningRollAR/PhotoImageRenderer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | import ARKit 4 | 5 | // This is based on Renderer.swift generated for iOS/Augmented Reality App template. 6 | class PhotoImageRenderer { 7 | 8 | let imagePlaneVertexData: [Float] = [ 9 | -1.0, -1.0, 0.0, 1.0, 10 | 1.0, -1.0, 1.0, 1.0, 11 | -1.0, 1.0, 0.0, 0.0, 12 | 1.0, 1.0, 1.0, 0.0, 13 | ] 14 | 15 | var device: MTLDevice! 16 | var imagePlaneVertexBuffer: MTLBuffer! 17 | var capturedImagePipelineState: MTLRenderPipelineState! 18 | var capturedImageDepthState: MTLDepthStencilState! 19 | var capturedImageTextureY: CVMetalTexture? 20 | var capturedImageTextureCbCr: CVMetalTexture? 21 | var capturedImageTextureCache: CVMetalTextureCache! 22 | 23 | init( device : MTLDevice ) { 24 | self.device = device 25 | } 26 | 27 | func arrangeMetalForCameraImage( view: MTKView ) { 28 | 29 | // Create a vertex buffer with our image plane vertex data. 30 | let imagePlaneVertexDataCount = imagePlaneVertexData.count * MemoryLayout.size 31 | imagePlaneVertexBuffer = device.makeBuffer(bytes: imagePlaneVertexData, length: imagePlaneVertexDataCount, options: []) 32 | imagePlaneVertexBuffer.label = "ImagePlaneVertexBuffer" 33 | 34 | // Load all the shader files with a metal file extension in the project 35 | let defaultLibrary = device.makeDefaultLibrary()! 36 | 37 | let capturedImageVertexFunction = defaultLibrary.makeFunction(name: "capturedImageVertexTransform")! 38 | let capturedImageFragmentFunction = defaultLibrary.makeFunction(name: "capturedImageFragmentShader")! 39 | 40 | // Create a vertex descriptor for our image plane vertex buffer 41 | let imagePlaneVertexDescriptor = MTLVertexDescriptor() 42 | 43 | // Positions. 44 | imagePlaneVertexDescriptor.attributes[0].format = .float2 45 | imagePlaneVertexDescriptor.attributes[0].offset = 0 46 | imagePlaneVertexDescriptor.attributes[0].bufferIndex = 0 47 | 48 | // Texture coordinates. 49 | imagePlaneVertexDescriptor.attributes[1].format = .float2 50 | imagePlaneVertexDescriptor.attributes[1].offset = 8 51 | imagePlaneVertexDescriptor.attributes[1].bufferIndex = 0 52 | 53 | // Buffer Layout 54 | imagePlaneVertexDescriptor.layouts[0].stride = 16 55 | imagePlaneVertexDescriptor.layouts[0].stepRate = 1 56 | imagePlaneVertexDescriptor.layouts[0].stepFunction = .perVertex 57 | 58 | // Create a pipeline state for rendering the captured image 59 | let capturedImagePipelineStateDescriptor = MTLRenderPipelineDescriptor() 60 | capturedImagePipelineStateDescriptor.label = "MyCapturedImagePipeline" 61 | capturedImagePipelineStateDescriptor.vertexFunction = capturedImageVertexFunction 62 | capturedImagePipelineStateDescriptor.fragmentFunction = capturedImageFragmentFunction 63 | capturedImagePipelineStateDescriptor.vertexDescriptor = imagePlaneVertexDescriptor 64 | capturedImagePipelineStateDescriptor.colorAttachments[0].pixelFormat = view.colorPixelFormat 65 | capturedImagePipelineStateDescriptor.depthAttachmentPixelFormat = view.depthStencilPixelFormat 66 | capturedImagePipelineStateDescriptor.stencilAttachmentPixelFormat = .invalid 67 | 68 | do { 69 | try capturedImagePipelineState = device.makeRenderPipelineState(descriptor: capturedImagePipelineStateDescriptor) 70 | } catch let error { 71 | print("Failed to create captured image pipeline state, error \(error)") 72 | } 73 | 74 | let capturedImageDepthStateDescriptor = MTLDepthStencilDescriptor() 75 | capturedImageDepthStateDescriptor.depthCompareFunction = .always 76 | capturedImageDepthStateDescriptor.isDepthWriteEnabled = false 77 | capturedImageDepthState = device.makeDepthStencilState(descriptor: capturedImageDepthStateDescriptor) 78 | 79 | // Create captured image texture cache 80 | var textureCache: CVMetalTextureCache? 81 | CVMetalTextureCacheCreate(nil, nil, device, nil, &textureCache) 82 | capturedImageTextureCache = textureCache 83 | } 84 | 85 | func updateCapturedImageTextures(frame: ARFrame) { 86 | // Create two textures (Y and CbCr) from the provided frame's captured image 87 | let pixelBuffer = frame.capturedImage 88 | 89 | if (CVPixelBufferGetPlaneCount(pixelBuffer) < 2) { 90 | return 91 | } 92 | 93 | capturedImageTextureY = createTexture(fromPixelBuffer: pixelBuffer, pixelFormat:.r8Unorm, planeIndex:0) 94 | capturedImageTextureCbCr = createTexture(fromPixelBuffer: pixelBuffer, pixelFormat:.rg8Unorm, planeIndex:1) 95 | } 96 | 97 | func updateImagePlane(frame: ARFrame, viewportSize : CGSize ) { 98 | // Update the texture coordinates of our image plane to aspect fill the viewport 99 | // MARK: - Device Orientation 100 | let displayToCameraTransform = frame.displayTransform(for: .portrait, viewportSize: viewportSize).inverted() 101 | 102 | let vertexData = imagePlaneVertexBuffer.contents().assumingMemoryBound(to: Float.self) 103 | for index in 0...3 { 104 | let textureCoordIndex = 4 * index + 2 105 | let textureCoord = CGPoint(x: CGFloat(imagePlaneVertexData[textureCoordIndex]), y: CGFloat(imagePlaneVertexData[textureCoordIndex + 1])) 106 | let transformedCoord = textureCoord.applying(displayToCameraTransform) 107 | vertexData[textureCoordIndex] = Float(transformedCoord.x) 108 | vertexData[textureCoordIndex + 1] = Float(transformedCoord.y) 109 | } 110 | } 111 | 112 | func createTexture(fromPixelBuffer pixelBuffer: CVPixelBuffer, pixelFormat: MTLPixelFormat, planeIndex: Int) -> CVMetalTexture? { 113 | let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex) 114 | let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex) 115 | 116 | var texture: CVMetalTexture? = nil 117 | let status = CVMetalTextureCacheCreateTextureFromImage(nil, capturedImageTextureCache, pixelBuffer, nil, pixelFormat, width, height, planeIndex, &texture) 118 | 119 | if status != kCVReturnSuccess { 120 | texture = nil 121 | } 122 | 123 | return texture 124 | } 125 | 126 | func draw( in view: MTKView, commandBuffer: MTLCommandBuffer ) { 127 | 128 | let descriptor = view.currentRenderPassDescriptor 129 | 130 | guard 131 | let encoder = commandBuffer.makeRenderCommandEncoder( descriptor: descriptor! ) 132 | else { 133 | return 134 | } 135 | 136 | guard let textureY = capturedImageTextureY, let textureCbCr = capturedImageTextureCbCr else { 137 | return 138 | } 139 | 140 | // Push a debug group allowing us to identify render commands in the GPU Frame Capture tool 141 | encoder.pushDebugGroup("DrawCapturedImage") 142 | 143 | // Set render command encoder state 144 | encoder.setCullMode(.none) 145 | encoder.setRenderPipelineState(capturedImagePipelineState) 146 | encoder.setDepthStencilState(capturedImageDepthState) 147 | 148 | // Set mesh's vertex buffers 149 | encoder.setVertexBuffer(imagePlaneVertexBuffer, offset: 0, index: 0 ) 150 | 151 | // Set any textures read/sampled from our render pipeline 152 | encoder.setFragmentTexture(CVMetalTextureGetTexture(textureY), index: 1 ) 153 | encoder.setFragmentTexture(CVMetalTextureGetTexture(textureCbCr), index: 2 ) 154 | 155 | // Draw each submesh of our mesh 156 | encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) 157 | 158 | encoder.popDebugGroup() 159 | 160 | encoder.endEncoding() 161 | } 162 | } 163 | 164 | -------------------------------------------------------------------------------- /SWOpeningRoll/shared/WorldManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MetalKit 3 | import SwiftUI 4 | import SDFont 5 | 6 | 7 | class WorldManager : ObservableObject { 8 | 9 | var device: MTLDevice! 10 | var colorPixelFormat: MTLPixelFormat 11 | 12 | var camera: Camera 13 | 14 | var scene: UniformPerScene 15 | var sceneBuffer: MTLBuffer? 16 | 17 | var sdHelperHelveticaBold: SDFontRuntimeHelper? 18 | var sdHelperHelvetica: SDFontRuntimeHelper? 19 | 20 | var textPlaneHelper: PerSceneRenderHelper? 21 | 22 | var textPlane1: SDTextPlane? 23 | var textPlane2: SDTextPlane? 24 | var textPlane3: SDTextPlane? 25 | 26 | public init( device : MTLDevice ) { 27 | 28 | self.device = device 29 | self.colorPixelFormat = .invalid 30 | self.camera = Camera() 31 | self.scene = UniformPerScene() 32 | self.sceneBuffer = UniformPerScene.generateMTLBuffer( device: device, uniformPerScene: &scene ) 33 | 34 | // Set this to false after running the App for the first time. 35 | let generatingSDFonts : Bool = true 36 | 37 | if generatingSDFonts { 38 | generateSDFonts( fontName : AnimationConstants.Helvetica, textureSize: AnimationConstants.SDTextureSize ) 39 | generateSDFonts( fontName : AnimationConstants.HelveticaBold, textureSize: AnimationConstants.SDTextureSize ) 40 | } 41 | 42 | sdHelperHelveticaBold = SDFontRuntimeHelper( 43 | device : device, 44 | fontName : AnimationConstants.HelveticaBold, 45 | fontSize : 12, 46 | fileName : AnimationConstants.HelveticaBold, 47 | path : nil, 48 | usePosixPath : false, 49 | verbose : true 50 | ) 51 | 52 | sdHelperHelvetica = SDFontRuntimeHelper( 53 | device : device, 54 | fontName : AnimationConstants.Helvetica, 55 | fontSize : 12, 56 | fileName : AnimationConstants.Helvetica, 57 | path : nil, 58 | usePosixPath : false, 59 | verbose : true 60 | ) 61 | 62 | self.textPlaneHelper = PerSceneRenderHelper( 63 | device : device, 64 | bufferUniformPerScene : self.sceneBuffer! 65 | ) 66 | 67 | print ("Available fonts in the system: ") 68 | for fontName in SDFontRuntimeHelper.listAvailableFonts() { 69 | print (" [\(fontName)]") 70 | } 71 | 72 | // MARK: - Text 1 "Long time ago..." 73 | 74 | self.textPlane1 = SDTextPlane( 75 | device : device, 76 | dimension : AnimationConstants.Dimension1, 77 | sdHelper : sdHelperHelvetica!, 78 | scaleForTypeSetting : AnimationConstants.Scale1, 79 | uniformSDFont : AnimationConstants.SDFontUniform1 80 | ) 81 | self.textPlane1!.setText( textPlain: AnimationConstants.Text1, lineAlignment: .left ) 82 | 83 | self.textPlane1!.setAnimation( 84 | temporalPoints : AnimationConstants.TemporalPoints1, 85 | spacialPoints : AnimationConstants.SpacialPoints1, 86 | colors : AnimationConstants.Colors1 87 | ) 88 | 89 | // MARK: - Text 2 "STAR WARS" 90 | 91 | self.textPlane2 = SDTextPlane( 92 | device : device, 93 | dimension : AnimationConstants.Dimension2, 94 | sdHelper : sdHelperHelveticaBold!, 95 | scaleForTypeSetting : AnimationConstants.Scale2, 96 | uniformSDFont : AnimationConstants.SDFontUniform2 97 | ) 98 | self.textPlane2!.setText( textPlain: AnimationConstants.Text2, lineAlignment: .center ) 99 | 100 | self.textPlane2!.setAnimation( 101 | temporalPoints : AnimationConstants.TemporalPoints2, 102 | spacialPoints : AnimationConstants.SpacialPoints2, 103 | colors : AnimationConstants.Colors2 104 | ) 105 | 106 | // MARK: - Text 3 Main Text 107 | 108 | self.textPlane3 = SDTextPlane( 109 | device : device, 110 | dimension : AnimationConstants.Dimension3, 111 | sdHelper : sdHelperHelvetica!, 112 | scaleForTypeSetting : AnimationConstants.Scale3, 113 | uniformSDFont : AnimationConstants.SDFontUniform3 114 | ) 115 | self.textPlane3!.setText( textPlain: AnimationConstants.Text3, lineAlignment: .center ) 116 | self.textPlane3!.rotate90AroundX() 117 | 118 | self.textPlane3!.setAnimation( 119 | temporalPoints : AnimationConstants.TemporalPoints3, 120 | spacialPoints : AnimationConstants.SpacialPoints3, 121 | colors : AnimationConstants.Colors3 122 | ) 123 | 124 | textPlane1!.startAnimation() 125 | textPlane2!.startAnimation() 126 | textPlane3!.startAnimation() 127 | } 128 | 129 | func generateSDFonts( fontName : String, textureSize: Int) { 130 | 131 | let sdGeneratorHelvetica = SDFontGenerator( 132 | device : device, 133 | fontName : fontName, 134 | outputTextureSideLen : textureSize, 135 | spreadFactor : 0.2, 136 | upSamplingFactor : 4, 137 | glyphNumCutoff : 512, 138 | verbose : true, 139 | usePosixPath : false 140 | ) 141 | 142 | if !sdGeneratorHelvetica.writeToPNGFile(fileName: fontName, path: nil ) { 143 | print ("ERROR: cannot write PNG file for \(fontName).") 144 | } 145 | 146 | if !sdGeneratorHelvetica.writeMetricsToJSONFile(fileName: fontName, path: nil ) { 147 | print ("ERROR: cannot write PNG file for \(fontName).") 148 | } 149 | } 150 | 151 | func createPipelineStates( colorPixelFormat: MTLPixelFormat ) { 152 | 153 | self.colorPixelFormat = colorPixelFormat 154 | self.textPlaneHelper?.createPipelineState( colorPixelFormat: colorPixelFormat ) 155 | } 156 | 157 | func updateScreenSizes(_ view: MTKView ){ 158 | camera.updateCameraDimension( dimension: SIMD2(x: Float(view.frame.size.width), y: Float(view.frame.size.height) ) ) 159 | scene.update( camera: camera ) 160 | UniformPerScene.updateMTLBuffer(buffer: sceneBuffer!, uniformPerScene: &scene ) 161 | } 162 | 163 | func updateWorld() { 164 | 165 | scene.update( camera: camera ) 166 | UniformPerScene.updateMTLBuffer(buffer: sceneBuffer!, uniformPerScene: &scene ) 167 | textPlane1!.step() 168 | textPlane2!.step() 169 | textPlane3!.step() 170 | 171 | if !( textPlane1!.animationActive()) 172 | && !( textPlane2!.animationActive() ) 173 | && !( textPlane3!.animationActive() ) { 174 | 175 | textPlane1!.startAnimation() 176 | textPlane2!.startAnimation() 177 | textPlane3!.startAnimation() 178 | } 179 | } 180 | 181 | func encode( encoder : MTLRenderCommandEncoder ) { 182 | 183 | textPlaneHelper?.renderModelSDFont( 184 | encoder : encoder, 185 | bufferVertexIn : textPlane1!.vertexBuffer!, 186 | bufferUniformPerInstance : textPlane1!.perInstanceBuffer!, 187 | numInstances : 1, 188 | bufferIndices : textPlane1!.indexBuffer!, 189 | numIndices : textPlane1!.numIndices, 190 | bufferUniformSDFont : textPlane1!.sdFontUniformBuffer!, 191 | textureSDFont : textPlane1!.sdFontTexture!, 192 | wireFrame : false 193 | ) 194 | 195 | textPlaneHelper?.renderModelSDFont( 196 | encoder : encoder, 197 | bufferVertexIn : textPlane2!.vertexBuffer!, 198 | bufferUniformPerInstance : textPlane2!.perInstanceBuffer!, 199 | numInstances : 1, 200 | bufferIndices : textPlane2!.indexBuffer!, 201 | numIndices : textPlane2!.numIndices, 202 | bufferUniformSDFont : textPlane2!.sdFontUniformBuffer!, 203 | textureSDFont : textPlane2!.sdFontTexture!, 204 | wireFrame : false 205 | ) 206 | 207 | textPlaneHelper?.renderModelSDFont( 208 | encoder : encoder, 209 | bufferVertexIn : textPlane3!.vertexBuffer!, 210 | bufferUniformPerInstance : textPlane3!.perInstanceBuffer!, 211 | numInstances : 1, 212 | bufferIndices : textPlane3!.indexBuffer!, 213 | numIndices : textPlane3!.numIndices, 214 | bufferUniformSDFont : textPlane3!.sdFontUniformBuffer!, 215 | textureSDFont : textPlane3!.sdFontTexture!, 216 | wireFrame : false 217 | ) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /SDFont/SDFont/SDFontRuntimeHelper.swift: -------------------------------------------------------------------------------- 1 | import MetalKit 2 | 3 | 4 | /// SDFontRuntimerHelper performs the type-setting for the signed distance fonts. 5 | /// At the initialization, it takes a signed distance font in MTLTexture and its accompanying list of bounding boxes. 6 | /// It then takes the text and the drawing area as the inputs, performs typesetting with CoreText, and generates 7 | /// the necessary data in a list of pairs of bounding boxes in order to render the text with Metal. 8 | /// The first part of the pair specifies the rectagular region in the drawing area, and the second specifies the 9 | /// bounding box in the signed distance font texture. 10 | /// Please note that the list of the bounding boxes does not have a 1-to-1 mapping to the character sequence of the input text, as 11 | /// the glyphs can contain ligatures, such as "ff" and "fi". 12 | public class SDFontRuntimeHelper { 13 | 14 | 15 | /// The output from typeset() in the list of pairs of rectangular bounds. 16 | public struct GlyphBound { 17 | 18 | /// The rectangular bounding box in the specified drawing area. 19 | public let frameBound : CGRect 20 | 21 | /// The rectangular bounding box of the glyph in the texture coodinate space. 22 | public let textureBound : CGRect 23 | } 24 | let verbose : Bool 25 | let device : MTLDevice 26 | var fontName : String 27 | var font : CTFont? 28 | var signedDistanceBuffer : MTLTexture? 29 | var textureBounds : [ CGRect ]? 30 | 31 | var contextBuffer : [ UInt8 ] 32 | let context : CGContext 33 | 34 | /// Use this initializer, if the font is generated at runtime with SDFontGenerator. 35 | /// - Parameters: 36 | /// - generator: SDFont generator 37 | /// - fontSize: The size of the font used for type setting. 38 | public init( generator: SDFontGenerator, fontSize : Int, verbose : Bool ) { 39 | self.verbose = verbose 40 | self.device = generator.device 41 | self.fontName = generator.fontName 42 | self.font = CTFontCreateWithName( fontName as CFString, CGFloat(fontSize), nil ) 43 | self.signedDistanceBuffer = generator.generateMTLTexture() 44 | self.textureBounds = generator.textureBounds 45 | self.contextBuffer = [ 0 ] 46 | 47 | // the following context is used as a dummy parameter to CTRunGetImageBounds(). 48 | let colorSpace : CGColorSpace = CGColorSpaceCreateDeviceGray(); 49 | let bitmapInfo = CGBitmapInfo( rawValue: CGImageAlphaInfo.none.rawValue | CGImageByteOrderInfo.orderDefault.rawValue | CGImagePixelFormatInfo.packed.rawValue ) 50 | self.context = CGContext( 51 | data: &contextBuffer, 52 | width: 1, 53 | height: 1, 54 | bitsPerComponent: 8, 55 | bytesPerRow: 1, 56 | space: colorSpace, 57 | bitmapInfo: bitmapInfo.rawValue 58 | )! 59 | } 60 | 61 | /// - Parameters: 62 | /// - device: The metal device 63 | /// - fontName: The name of the font used. This must be the same one used for the generation of the signed distance font. 64 | /// - fontSize: The size of the font used for type setting. 65 | /// - fileName: The file name without extension for the PNG and JSON files to be loaded. 66 | /// - path: The subdirectory of the path in which the JSON file is stored. 67 | /// Use this if the signed distance fonts were generated off-line and stored in the PNG/JSON file pair. 68 | public init( device: MTLDevice, fontName: String, fontSize: Int, fileName : String, path : String?, usePosixPath : Bool, verbose : Bool ) { 69 | self.verbose = verbose 70 | self.device = device 71 | self.fontName = fontName 72 | self.font = CTFontCreateWithName( fontName as CFString, CGFloat(fontSize), nil ) 73 | self.signedDistanceBuffer = SDFontIOHandler.loadTextureFromPNGFile( 74 | device : device, 75 | fileName : fileName, 76 | path : path, 77 | usePosixPath : usePosixPath, 78 | verbose : verbose 79 | ) 80 | self.textureBounds = SDFontIOHandler.loadMetricsFromJSONFile( 81 | fileName : fileName, 82 | path : path, 83 | usePosixPath : usePosixPath, 84 | verbose : verbose 85 | ) 86 | 87 | self.contextBuffer = [ 0 ] 88 | 89 | // the following context is used as a dummy parameter to CTRunGetImageBounds(). 90 | let colorSpace : CGColorSpace = CGColorSpaceCreateDeviceGray(); 91 | let bitmapInfo = CGBitmapInfo( rawValue: CGImageAlphaInfo.none.rawValue | CGImageByteOrderInfo.orderDefault.rawValue | CGImagePixelFormatInfo.packed.rawValue ) 92 | self.context = CGContext( 93 | data: &contextBuffer, 94 | width: 1, 95 | height: 1, 96 | bitsPerComponent: 8, 97 | bytesPerRow: 1, 98 | space: colorSpace, 99 | bitmapInfo: bitmapInfo.rawValue 100 | )! 101 | } 102 | 103 | /// - Returns: The metal texture of type MTLPixelFormatR8Unorm that contains the signed distance values. 104 | public func texture() -> MTLTexture { 105 | return signedDistanceBuffer! 106 | } 107 | 108 | /// Performs type setting and generates the data in a list of bounding boxes for the given text to render it with a Metal shader. 109 | /// - Parameters: 110 | /// - frameRect: The drawing area to which the text is rendered. 111 | /// - textPlain: The plain text to be type-set and drawn to the drawing area. 112 | /// - lineAlignment: .left, .center, or .right 113 | /// - Returns: List of pairs of bounding boxes. The first bounding box of each pair specifies the rectangular resion in the drawing area to which the glyph is drawn. The second bounding box specifies the region in the texture. 114 | public func typeset( frameRect: CGRect, textPlain: String, lineAlignment : CTTextAlignment ) -> [GlyphBound] { 115 | 116 | var alignment : CTTextAlignment = lineAlignment 117 | var settings : CTParagraphStyleSetting? 118 | withUnsafePointer( to: &alignment) { (ptr: UnsafePointer) in 119 | settings = CTParagraphStyleSetting( 120 | spec : .alignment, 121 | valueSize : MemoryLayout.size, 122 | value : ptr 123 | ) 124 | } 125 | let paragraphStyle = CTParagraphStyleCreate([ settings! ], 1) 126 | 127 | let attr = [ kCTFontAttributeName : font!, 128 | kCTParagraphStyleAttributeName : paragraphStyle 129 | ] as CFDictionary 130 | let textAttr = CFAttributedStringCreate( nil, textPlain as CFString, attr ) 131 | let textRange = CFRange( location: 0, length: CFAttributedStringGetLength( textAttr ) ); 132 | let framePath = CGPath(rect: frameRect, transform: nil ) 133 | let frameSetter = CTFramesetterCreateWithAttributedString( textAttr! ) 134 | let ctFrame = CTFramesetterCreateFrame( frameSetter, textRange, framePath, nil ) 135 | 136 | let frameBoundingRect = framePath.boundingBoxOfPath; 137 | let ctLines = CTFrameGetLines( ctFrame ) as [AnyObject] as! [CTLine] 138 | var ctLineOrigins = [CGPoint]( repeating: CGPoint( x:0, y:0 ), count: ctLines.count ) 139 | CTFrameGetLineOrigins( ctFrame, CFRangeMake(0,0), &ctLineOrigins ) 140 | 141 | var outArray : [GlyphBound] = [] 142 | 143 | for i in 0 ..< ctLines.count { 144 | 145 | let ctLine = ctLines[i] 146 | let ctLineOrigin = ctLineOrigins[i] 147 | let ctRuns = CTLineGetGlyphRuns( ctLine ) as [AnyObject] as! [CTRun] 148 | 149 | for ctRun in ctRuns { 150 | 151 | let glyphCount = CTRunGetGlyphCount( ctRun ) 152 | 153 | var glyphs = [CGGlyph]( repeating: CGGlyph(), count: glyphCount ) 154 | CTRunGetGlyphs( ctRun, CFRangeMake(0,0), &glyphs ) 155 | 156 | for j in 0 ..< glyphCount { 157 | 158 | let glyph = glyphs[j] 159 | let glyphBound = CTRunGetImageBounds( ctRun, context, CFRangeMake(j, 1)); 160 | 161 | let frameBound = CGRect( 162 | x : frameBoundingRect.origin.x + ctLineOrigin.x + glyphBound.origin.x, 163 | y : frameBoundingRect.origin.y + ctLineOrigin.y + glyphBound.origin.y, 164 | width : glyphBound.size.width, 165 | height : glyphBound.size.height 166 | ) 167 | let textureBound = textureBounds![ Int(glyph) ] 168 | outArray.append( GlyphBound( frameBound: frameBound, textureBound: textureBound ) ) 169 | } 170 | } 171 | } 172 | 173 | return outArray 174 | } 175 | 176 | /// - Returns: The list of font names available in the system. 177 | /// 178 | /// Please consult https://developer.apple.com/fonts/ for the official information. 179 | public static func listAvailableFonts() -> [String] { 180 | #if os(iOS) 181 | var fontList : [String] = [] 182 | for family in UIFont.familyNames { 183 | for fontName in UIFont.fontNames(forFamilyName: family) { 184 | fontList.append( fontName ) 185 | } 186 | } 187 | return fontList 188 | #else 189 | return NSFontManager.shared.availableFonts 190 | #endif 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /SDFont/SDFont/SDFontGlyphPackingFinder.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | 3 | class SDFontGlyphPackingFinder { 4 | 5 | static let ALPHA : CGFloat = 0.1 6 | static let NUM_TRIALS_IMPROVEMENT : Int = 5 7 | static let MAX_NUM_TRIALS_IMPROVEMENT : Int = 1000 8 | static let FONT_SIZE_FOR_PACKING : CGFloat = 64.0 9 | static let FONT_SIZE_MINIMUM_ALLOWED : CGFloat = 5.0 10 | 11 | let fontName : CFString 12 | let spreadFactor : CGFloat 13 | let drawAreaSideLen : Int 14 | let glyphNumCutoff : Int 15 | let verbose : Bool 16 | var numGlyphs : Int 17 | var referenceCGFont : CGFont? 18 | var spreadThickness : CGFloat 19 | var referenceFontSize : CGFloat 20 | var glyphBoundsArray : [ SignedDistanceFontGlyphBounds ] 21 | var maxOuterGlyphBoundsSideLen : CGFloat 22 | var meanInnerGlyphBoundsSideLen : CGFloat 23 | var occupancyRate : CGFloat 24 | 25 | init( fontName: CFString, spreadFactor: CGFloat, drawAreaSideLen : Int, glyphNumCutoff : Int, verbose : Bool ) { 26 | 27 | self.fontName = fontName 28 | self.spreadFactor = spreadFactor 29 | self.drawAreaSideLen = drawAreaSideLen 30 | self.glyphNumCutoff = glyphNumCutoff 31 | self.verbose = verbose 32 | self.numGlyphs = 0 33 | self.spreadThickness = 0.0 34 | self.referenceFontSize = 0.0 35 | self.glyphBoundsArray = [] 36 | self.maxOuterGlyphBoundsSideLen = 0.0 37 | self.meanInnerGlyphBoundsSideLen = 0.0 38 | self.occupancyRate = 0.0 39 | 40 | getFontAndCheckFontName( fontName ) 41 | 42 | self.meanInnerGlyphBoundsSideLen = findMeanInnerSideLen( fontSize : Self.FONT_SIZE_FOR_PACKING ) 43 | self.spreadThickness = spreadFactor * meanInnerGlyphBoundsSideLen 44 | self.maxOuterGlyphBoundsSideLen = findMaxOuterSideLen( fontSize : Self.FONT_SIZE_FOR_PACKING ) 45 | 46 | let bestArea = findBestPackedArea( fontSize : Self.FONT_SIZE_FOR_PACKING ) 47 | let suggestedFontSize = floor( Self.FONT_SIZE_FOR_PACKING * CGFloat( drawAreaSideLen ) / max( bestArea.width, bestArea.height ) ) 48 | self.referenceFontSize = findBestFontSize( initialSize: suggestedFontSize ) 49 | generateBoundingBoxes( fontSize : self.referenceFontSize ) 50 | 51 | var areaSum : CGFloat = 0.0 52 | 53 | for bound in glyphBoundsArray { 54 | 55 | areaSum += ( bound.outer.size.width * bound.outer.size.height ) 56 | } 57 | self.occupancyRate = areaSum / ( CGFloat(self.drawAreaSideLen) * CGFloat(self.drawAreaSideLen) ) 58 | } 59 | 60 | func findBestFontSize( initialSize : CGFloat ) -> CGFloat { 61 | 62 | var fontSize = initialSize 63 | let allowedAreaSize = CGFloat(self.drawAreaSideLen) 64 | 65 | while fontSize >= Self.FONT_SIZE_MINIMUM_ALLOWED { 66 | 67 | self.meanInnerGlyphBoundsSideLen = findMeanInnerSideLen( fontSize : fontSize ) 68 | self.spreadThickness = spreadFactor * self.meanInnerGlyphBoundsSideLen 69 | self.maxOuterGlyphBoundsSideLen = findMaxOuterSideLen( fontSize : fontSize ) 70 | 71 | let area = findPackedArea( widthLimit: allowedAreaSize, generateBoundingBoxes : false, fontSize : fontSize ) 72 | 73 | if area.width <= allowedAreaSize && area.height <= allowedAreaSize { 74 | return fontSize 75 | } 76 | fontSize -= 1.0 77 | } 78 | 79 | print ( "WARNING: Glyphs can not be fully packed to the texture of size [\(self.drawAreaSideLen)] for the minimum font size [\(Self.FONT_SIZE_FOR_PACKING)]." ) 80 | return fontSize 81 | } 82 | 83 | func getFontAndCheckFontName( _ fontName : CFString ) { 84 | 85 | self.referenceCGFont = CGFont( fontName ) 86 | self.referenceFontSize = Self.FONT_SIZE_FOR_PACKING 87 | 88 | let glyphCount = self.referenceCGFont!.numberOfGlyphs 89 | 90 | self.numGlyphs = min( self.glyphNumCutoff, glyphCount ) 91 | 92 | if verbose { 93 | print ("INFO: Number of glyphs in font \(fontName): [\(glyphCount)].") 94 | print ("INFO: Number of glyphs after cut-off: [\(numGlyphs)].") 95 | } 96 | } 97 | 98 | func findBestPackedArea( fontSize : CGFloat ) -> CGSize { 99 | 100 | let sqNumGlyphs = sqrt( Double(numGlyphs) ) 101 | let initialAreaWidthLimit = ( self.meanInnerGlyphBoundsSideLen + spreadThickness * 2.0 ) * sqNumGlyphs 102 | var area = findPackedArea( widthLimit: initialAreaWidthLimit, generateBoundingBoxes : false, fontSize : fontSize ) 103 | var bestArea = area 104 | var numTimesNotImproved = 0 105 | 106 | for _ in 0 ..< Self.MAX_NUM_TRIALS_IMPROVEMENT { 107 | 108 | if abs(bestArea.width - bestArea.height) <= abs(area.width - area.height) { 109 | 110 | numTimesNotImproved += 1 111 | 112 | if numTimesNotImproved >= Self.NUM_TRIALS_IMPROVEMENT { 113 | return bestArea 114 | } 115 | } 116 | else { 117 | bestArea = area 118 | } 119 | let areaWidthLimit = area.width + (area.height - area.width) * Self.ALPHA 120 | area = findPackedArea( widthLimit: areaWidthLimit, generateBoundingBoxes : false, fontSize : fontSize ) 121 | } 122 | 123 | print ( "WARNING: Feasible packing area for font [\(fontName)] can not be found. Using [\(bestArea)].") 124 | return bestArea 125 | } 126 | 127 | func findMeanInnerSideLen( fontSize : CGFloat ) -> CGFloat { 128 | 129 | var total : CGFloat = 0.0 130 | 131 | for i in 0 ..< self.numGlyphs { 132 | 133 | var g = CGGlyph(i) 134 | var bboxes = UnsafeMutablePointer.allocate( capacity: 1 ) 135 | 136 | if ( !referenceCGFont!.getGlyphBBoxes( glyphs: [g], count: 1, bboxes: bboxes ) ) { 137 | print ( "WARNING: Cant't retrieve BBox for glyph [\(g)].") 138 | return 0.0 139 | } 140 | 141 | let fontScaleFactor = fontSize / CGFloat( referenceCGFont!.unitsPerEm ) 142 | 143 | let rect = CGRect( 144 | origin: CGPoint( x: bboxes[0].origin.x * fontScaleFactor, y: bboxes[0].origin.y * fontScaleFactor ), 145 | size: CGSize( width: bboxes[0].width * fontScaleFactor, height: bboxes[0].height * fontScaleFactor ) 146 | ) 147 | 148 | total += ( rect.width + rect.height ) 149 | } 150 | 151 | return total / CGFloat( 2 * self.numGlyphs ) 152 | } 153 | 154 | func findMaxOuterSideLen( fontSize : CGFloat ) -> CGFloat { 155 | 156 | var maxSideLen : CGFloat = 0.0 157 | 158 | for i in 0 ..< numGlyphs { 159 | var g = CGGlyph(i) 160 | 161 | var bboxes = UnsafeMutablePointer.allocate( capacity: 1 ) 162 | 163 | if ( !referenceCGFont!.getGlyphBBoxes( glyphs: [g], count: 1, bboxes: bboxes ) ) { 164 | print ( "WARNING: Cant't retrieve BBox for glyph [\(g)].") 165 | return 0.0 166 | } 167 | 168 | let fontScaleFactor = fontSize / CGFloat( referenceCGFont!.unitsPerEm ) 169 | 170 | let rect = CGRect( 171 | origin: CGPoint( x: bboxes[0].origin.x * fontScaleFactor, y: bboxes[0].origin.y * fontScaleFactor ), 172 | size: CGSize( width: bboxes[0].width * fontScaleFactor, height: bboxes[0].height * fontScaleFactor ) 173 | ) 174 | 175 | maxSideLen = max( maxSideLen, rect.width, rect.height ) 176 | } 177 | 178 | return maxSideLen + (self.spreadThickness * 2.0) 179 | } 180 | 181 | 182 | func generateBoundingBoxes( fontSize : CGFloat ) { 183 | 184 | let _ = findPackedArea( widthLimit: CGFloat(self.drawAreaSideLen), generateBoundingBoxes : true, fontSize : fontSize ) 185 | } 186 | 187 | // pack the glyph left-to-right and then bottom-to-top in the area with the specified width. 188 | func findPackedArea( widthLimit: CGFloat, generateBoundingBoxes : Bool, fontSize : CGFloat ) -> CGSize { 189 | 190 | var xAdvance : CGFloat = 0.0 191 | var yAdvance : CGFloat = 0.0 192 | var maxHeightCurrentRow : CGFloat = 0.0 193 | var maxWidth : CGFloat = 0.0 194 | 195 | for i in 0 ..< self.numGlyphs { 196 | 197 | var g = CGGlyph(i) 198 | 199 | var bboxes = UnsafeMutablePointer.allocate( capacity: 1 ) 200 | 201 | if ( !referenceCGFont!.getGlyphBBoxes( glyphs: [g], count: 1, bboxes: bboxes ) ) { 202 | print ( "WARNING: Cant't retrieve BBox for glyph [\(g)].") 203 | return CGSize( width: 0, height: 0 ) 204 | } 205 | 206 | let fontScaleFactor = fontSize / CGFloat( referenceCGFont!.unitsPerEm ) 207 | 208 | let rect = CGRect( 209 | origin: CGPoint( x: bboxes[0].origin.x * fontScaleFactor, y: bboxes[0].origin.y * fontScaleFactor ), 210 | size: CGSize( width: bboxes[0].width * fontScaleFactor, height: bboxes[0].height * fontScaleFactor ) 211 | ) 212 | 213 | // outer sizes rounded up to the integer 214 | let outerWidth = ceil( rect.width + ( spreadThickness * 2.0 ) ) 215 | let outerHeight = ceil( rect.height + ( spreadThickness * 2.0 ) ) 216 | 217 | if xAdvance + outerWidth > widthLimit { 218 | 219 | maxWidth = max( maxWidth, xAdvance ) 220 | xAdvance = 0.0 221 | yAdvance += maxHeightCurrentRow 222 | maxHeightCurrentRow = outerHeight 223 | } 224 | else { 225 | maxHeightCurrentRow = max( maxHeightCurrentRow, outerHeight ) 226 | } 227 | 228 | if generateBoundingBoxes { 229 | let bounds = SignedDistanceFontGlyphBounds( 230 | outerOrigin : CGPoint( x: xAdvance, y: yAdvance ), 231 | outerSize : CGSize( width: outerWidth, height: outerHeight ), 232 | spreadThickness : spreadThickness, 233 | innerSize : rect.size, 234 | textureSideLen : CGFloat(self.drawAreaSideLen) 235 | ) 236 | glyphBoundsArray.append( bounds ) 237 | } 238 | 239 | xAdvance += outerWidth 240 | 241 | } 242 | if xAdvance > 0.0 { 243 | 244 | maxWidth = max( maxWidth, xAdvance ) 245 | xAdvance = 0.0 246 | yAdvance += maxHeightCurrentRow 247 | maxHeightCurrentRow = 0.0 248 | } 249 | 250 | return CGSize( width: maxWidth, height: yAdvance + maxHeightCurrentRow ) 251 | } 252 | } 253 | --------------------------------------------------------------------------------