├── .gitignore ├── LICENSE ├── README.md ├── Shared ├── BlueMarble.png ├── Grid.png ├── Particle-b.png ├── Particle-w.png ├── Particle.png ├── Pencil.png ├── PencilTexture-1.png ├── PencilTexture-2.png ├── SampleCanvas.swift ├── SampleCanvasBackdropLayer.swift ├── SampleCanvasScribbleLayer.swift ├── SampleCanvasViewController.swift ├── SampleScene.swift ├── SampleSceneViewController.swift └── test.png ├── Silvershadow.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Silvershadow ├── BezierRenderer.swift ├── BezierShaders.metal ├── CGPath+Z.swift ├── Canvas.swift ├── CanvasLayer.swift ├── ColorRenderable.swift ├── ColorRenderer.swift ├── ColorShaders.metal ├── CoreGraphics+Z.swift ├── CrossPlatform.swift ├── Float16.swift ├── GeoUtils.swift ├── ImageRenderable.swift ├── ImageRenderer.swift ├── ImageShaders.metal ├── Kernel.swift ├── MTLDevice+Z.swift ├── MTLHeap+Z.swift ├── MetalBuffer.swift ├── PatternRenderer.swift ├── PatternShaders.metal ├── PointsRenderable.swift ├── PointsRenderer.swift ├── PointsShaders.metal ├── RenderCanvasContext.swift ├── RenderContentView.swift ├── RenderContext.swift ├── RenderDrawView.swift ├── RenderView.swift ├── Renderable.swift ├── Renderer.swift ├── Scene.swift ├── Sequence+Z.swift ├── VertexBuffer.swift └── XView+Z.swift ├── SilvershadowApp_ios ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard └── Info.plist └── SilvershadowApp_mac ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── Base.lproj └── Main.storyboard └── Info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *~.nib 4 | build/ 5 | */build/* 6 | *.pbxuser 7 | *.perspective 8 | *.perspectivev3 9 | *.mode1v3 10 | *.mode2v3 11 | xcuserdata/ 12 | DerivedData 13 | *.xccheckout 14 | *.lock 15 | *.moved-aside 16 | .Trashes 17 | ._* 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Electricwoods LLC, Kaz Yoshikawa. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Silver Shadow 2 | 3 | ## What is Silver Shadow? 4 | 5 | Silvershadow is a Swift based zoomable, scrollable Metal 2D Tool Kit for iOS and macOS. 6 | Since, MTKView or Metal backed view is not suitable placing within UIScrollView, NSScrollView. It is hard to provide similar user experiences by handling mouses or touches manually. Silver Shadow provides UIScrollView or NSScrollView associated with RenderView and uses it's coordinate system for applying Metal rendering. 7 | 8 | 9 | TODO: Documentation 10 | -------------------------------------------------------------------------------- /Shared/BlueMarble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelynx/silvershadow/f30668067c54a48b92ede0436cd7f84d21434fd2/Shared/BlueMarble.png -------------------------------------------------------------------------------- /Shared/Grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelynx/silvershadow/f30668067c54a48b92ede0436cd7f84d21434fd2/Shared/Grid.png -------------------------------------------------------------------------------- /Shared/Particle-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelynx/silvershadow/f30668067c54a48b92ede0436cd7f84d21434fd2/Shared/Particle-b.png -------------------------------------------------------------------------------- /Shared/Particle-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelynx/silvershadow/f30668067c54a48b92ede0436cd7f84d21434fd2/Shared/Particle-w.png -------------------------------------------------------------------------------- /Shared/Particle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelynx/silvershadow/f30668067c54a48b92ede0436cd7f84d21434fd2/Shared/Particle.png -------------------------------------------------------------------------------- /Shared/Pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelynx/silvershadow/f30668067c54a48b92ede0436cd7f84d21434fd2/Shared/Pencil.png -------------------------------------------------------------------------------- /Shared/PencilTexture-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelynx/silvershadow/f30668067c54a48b92ede0436cd7f84d21434fd2/Shared/PencilTexture-1.png -------------------------------------------------------------------------------- /Shared/PencilTexture-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelynx/silvershadow/f30668067c54a48b92ede0436cd7f84d21434fd2/Shared/PencilTexture-2.png -------------------------------------------------------------------------------- /Shared/SampleCanvas.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleCanvas.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/28/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | 12 | 13 | class SampleCanvas: Canvas { 14 | 15 | var drawingLayer = SampleCanvasScribbleLayer() 16 | var interactiveLayer = SampleCanvasScribbleLayer() 17 | 18 | override init?(device: MTLDevice, contentSize: CGSize) { 19 | super.init(device: device, contentSize: contentSize) 20 | 21 | let backdropLayer = SampleCanvasBackdropLayer() 22 | self.addLayer(backdropLayer) 23 | 24 | // having problem of compositing layers, so comment out this part for now 25 | self.addLayer(drawingLayer) 26 | } 27 | 28 | override func render(in context: RenderContext) { 29 | super.render(in: context) 30 | } 31 | 32 | override func didMove(to renderView: RenderView) { 33 | super.didMove(to: renderView) 34 | self.overlayCanvasLayer = interactiveLayer 35 | 36 | self.interactiveLayer.name = "overlay" 37 | self.drawingLayer.name = "drawing" 38 | } 39 | 40 | #if os(macOS) 41 | var activePath: CGMutablePath? 42 | 43 | override func mouseDown(with event: NSEvent) { 44 | if let location = self.locationInScene(event) { 45 | let activePath = CGMutablePath() 46 | activePath.move(to: location) 47 | self.interactiveLayer.strokePaths.append(activePath) 48 | self.activePath = activePath 49 | } 50 | } 51 | 52 | override func mouseDragged(with event: NSEvent) { 53 | if let activePath = self.activePath, let location = self.locationInScene(event) { 54 | activePath.addLine(to: location) 55 | self.setNeedsDisplay() 56 | } 57 | } 58 | 59 | override func mouseUp(with event: NSEvent) { 60 | if let activePath = self.activePath { 61 | if let location = self.locationInScene(event) { 62 | activePath.addLine(to: location) 63 | } 64 | drawingLayer.strokePaths.append(activePath) 65 | self.interactiveLayer.strokePaths = [] 66 | self.setNeedsUpdate() 67 | self.setNeedsDisplay() 68 | } 69 | self.activePath = nil 70 | } 71 | 72 | #elseif os(iOS) 73 | var activeTouchPath: (touch: UITouch, path: CGMutablePath)? 74 | 75 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 76 | if let touch = touches.first, touches.count == 1, let location = self.locationInScene(touch) { 77 | let activePath = CGMutablePath() 78 | activePath.move(to: location) 79 | self.interactiveLayer.strokePaths.append(activePath) 80 | self.activeTouchPath = (touch, activePath) 81 | } 82 | } 83 | 84 | override func touchesMoved(_ touches: Set, with event: UIEvent?) { 85 | if let (touch, activePath) = self.activeTouchPath, touches.contains(touch) { 86 | if let event = event, let coalescedTouches = event.coalescedTouches(for: touch) { 87 | for coalescedTouch in coalescedTouches { 88 | if let location = self.locationInScene(coalescedTouch) { 89 | activePath.addLine(to: location) 90 | } 91 | } 92 | } 93 | self.setNeedsDisplay() 94 | } 95 | } 96 | 97 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 98 | if let (touch, activePath) = self.activeTouchPath, touches.contains(touch) { 99 | if let event = event, let coalescedTouches = event.coalescedTouches(for: touch) { 100 | for coalescedTouch in coalescedTouches { 101 | if let location = self.locationInScene(coalescedTouch) { 102 | activePath.addLine(to: location) 103 | } 104 | } 105 | } 106 | drawingLayer.strokePaths.append(activePath) 107 | self.interactiveLayer.strokePaths = [] 108 | self.setNeedsUpdate() 109 | self.setNeedsDisplay() 110 | } 111 | } 112 | 113 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 114 | self.interactiveLayer.strokePaths = [] 115 | self.setNeedsUpdate() 116 | self.setNeedsDisplay() 117 | self.activeTouchPath = nil 118 | } 119 | 120 | #endif 121 | 122 | } 123 | -------------------------------------------------------------------------------- /Shared/SampleCanvasBackdropLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleCanvasLayer.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/28/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | 12 | 13 | class SampleCanvasBackdropLayer: CanvasLayer { 14 | 15 | lazy var imageRenderable: ImageRenderable? = { 16 | guard let device = self.device else { return nil } 17 | guard let image = XImage(named: "Grid") else { fatalError("not found") } 18 | return ImageRenderable(device: device, image: image, frame: Rect(0, 0, 2048, 1024))! 19 | }() 20 | 21 | lazy var brushPatternTexture: MTLTexture! = { 22 | return self.device?.texture(of: XImage(named: "test")!)! 23 | }() 24 | 25 | 26 | var samplePoints: [(CGFloat, CGFloat)] = [ 27 | (342.0, 611.5), (328.0, 616.0), (319.0, 616.0), (307.5, 617.5), (293.5, 619.5), (278.5, 620.5), (262.0, 621.5), (246.5, 621.5), (230.5, 621.5), 28 | (212.0, 619.5), (195.0, 615.5), (179.5, 610.0), (165.0, 603.0), (151.0, 595.0), (138.0, 585.5), (127.0, 575.0), (117.0, 564.0), (109.0, 552.0), 29 | (103.0, 539.5), (100.0, 526.5), (99.5, 511.0), (100.0, 492.5), (107.0, 474.5), (118.5, 453.5), (132.0, 434.0), (149.0, 415.5), (169.5, 396.5), 30 | (194.0, 378.0), (221.5, 361.5), (251.0, 348.5), (280.0, 339.5), (307.5, 333.5), (336.0, 332.5), (365.0, 333.0), (393.5, 340.0), (418.5, 352.0), 31 | (442.0, 367.0), (463.0, 384.0), (481.0, 402.5), (495.5, 422.5), (506.5, 443.0), (513.5, 464.0), (517.0, 483.0), (518.5, 503.0), (518.5, 522.5), 32 | (513.0, 541.0), (502.0, 560.0), (488.0, 576.0), (470.5, 591.0), (451.5, 604.5), (429.5, 616.0), (405.5, 625.0), (381.0, 632.5), (357.0, 638.5), 33 | (333.0, 642.5), (308.5, 644.0), (286.5, 644.5), (263.5, 644.5), (241.5, 642.5), (221.5, 637.0), (204.5, 631.5), (191.5, 625.5), (181.5, 621.0), 34 | (174.5, 614.5) 35 | ] 36 | 37 | func samplePoints(_ transform: CGAffineTransform) -> [CGPoint] { 38 | return self.samplePoints.map { CGPoint(x: $0.0, y: $0.1).applying(transform) } 39 | } 40 | 41 | func samplePath(_ transform: CGAffineTransform = .identity) -> CGPath { 42 | let cgPath = CGMutablePath() 43 | var lastPoint: CGPoint? 44 | for point in self.samplePoints(transform) { 45 | if let lastPoint = lastPoint { 46 | let midPoint = (lastPoint + point) * 0.5 47 | cgPath.addQuadCurve(to: midPoint, control: lastPoint) 48 | } 49 | else { 50 | cgPath.move(to: point) 51 | } 52 | lastPoint = point 53 | } 54 | return cgPath 55 | } 56 | 57 | override func render(context: RenderContext) { 58 | guard let device = self.device else { return } 59 | 60 | self.imageRenderable?.render(context: context) 61 | 62 | let bezierRenderer = device.renderer() as BezierRenderer 63 | context.brushPattern = self.brushPatternTexture 64 | bezierRenderer.render(context: context, cgPaths: [self.samplePath()]) 65 | } 66 | 67 | } 68 | 69 | -------------------------------------------------------------------------------- /Shared/SampleCanvasScribbleLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleCanvasLayer2.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/29/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | 12 | 13 | class SampleCanvasScribbleLayer: CanvasLayer { 14 | 15 | lazy var brushSapeTexture: MTLTexture? = { 16 | return self.device?.texture(of: XImage(named: "Particle")!)! 17 | }() 18 | 19 | 20 | lazy var brushPatternTexture: MTLTexture! = { 21 | return self.device?.texture(of: XImage(named: "Pencil")!)! 22 | }() 23 | 24 | lazy var strokePaths: [CGPath] = { 25 | return [] 26 | }() 27 | 28 | override func render(context: RenderContext) { 29 | guard let device = self.device else { return } 30 | 31 | let bezierRenderer = device.renderer() as BezierRenderer 32 | 33 | context.brushPattern = self.brushPatternTexture 34 | bezierRenderer.render(context: context, cgPaths: strokePaths) 35 | } 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /Shared/SampleCanvasViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SilvershadowApp 4 | // 5 | // Created by Kaz Yoshikawa on 12/25/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import Cocoa 13 | #endif 14 | 15 | 16 | class SampleCanvasViewController: XViewController { 17 | 18 | var sampleScene: SampleCanvas! 19 | 20 | @IBOutlet var renderView: RenderView! 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | let device = self.renderView.device 26 | let contentSize = CGSize(2048, 1024) 27 | 28 | self.sampleScene = SampleCanvas(device: device, contentSize: contentSize) 29 | self.renderView.scene = self.sampleScene 30 | } 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Shared/SampleScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleScene.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/25/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import Cocoa 13 | #endif 14 | 15 | import CoreGraphics 16 | import MetalKit 17 | 18 | 19 | class SampleScene: Scene { 20 | 21 | lazy var colorTriangle: ColorTriangleRenderable? = { 22 | let pt1 = ColorRenderer.Vertex(x: 0, y: 0, z: 0, w: 1, r: 1, g: 0, b: 0, a: 0.5) 23 | let pt2 = ColorRenderer.Vertex(x: 1024, y: 1024, z: 0, w: 1, r: 0, g: 1, b: 0, a: 0.5) 24 | let pt3 = ColorRenderer.Vertex(x: 2048, y: 0, z: 0, w: 1, r: 0, g: 0, b: 1, a: 0.5) 25 | return ColorTriangleRenderable(device: self.device, point1: pt1, point2: pt2, point3: pt3) 26 | }() 27 | 28 | lazy var image1Texture: MTLTexture = { 29 | return self.device.texture(of: XImage(named: "BlueMarble")!)! 30 | }() 31 | 32 | // MRAK: - 33 | 34 | var samplePoints: [(CGFloat, CGFloat)] = [ 35 | (342.0, 611.5), (328.0, 616.0), (319.0, 616.0), (307.5, 617.5), (293.5, 619.5), (278.5, 620.5), (262.0, 621.5), (246.5, 621.5), (230.5, 621.5), 36 | (212.0, 619.5), (195.0, 615.5), (179.5, 610.0), (165.0, 603.0), (151.0, 595.0), (138.0, 585.5), (127.0, 575.0), (117.0, 564.0), (109.0, 552.0), 37 | (103.0, 539.5), (100.0, 526.5), (99.5, 511.0), (100.0, 492.5), (107.0, 474.5), (118.5, 453.5), (132.0, 434.0), (149.0, 415.5), (169.5, 396.5), 38 | (194.0, 378.0), (221.5, 361.5), (251.0, 348.5), (280.0, 339.5), (307.5, 333.5), (336.0, 332.5), (365.0, 333.0), (393.5, 340.0), (418.5, 352.0), 39 | (442.0, 367.0), (463.0, 384.0), (481.0, 402.5), (495.5, 422.5), (506.5, 443.0), (513.5, 464.0), (517.0, 483.0), (518.5, 503.0), (518.5, 522.5), 40 | (513.0, 541.0), (502.0, 560.0), (488.0, 576.0), (470.5, 591.0), (451.5, 604.5), (429.5, 616.0), (405.5, 625.0), (381.0, 632.5), (357.0, 638.5), 41 | (333.0, 642.5), (308.5, 644.0), (286.5, 644.5), (263.5, 644.5), (241.5, 642.5), (221.5, 637.0), (204.5, 631.5), (191.5, 625.5), (181.5, 621.0), 42 | (174.5, 614.5) 43 | ] 44 | 45 | func samplePoints(_ transform: CGAffineTransform) -> [CGPoint] { 46 | return self.samplePoints.map { CGPoint(x: $0.0, y: $0.1).applying(transform) } 47 | } 48 | 49 | func samplePath(_ transform: CGAffineTransform = .identity) -> CGPath { 50 | let cgPath = CGMutablePath() 51 | var lastPoint: CGPoint? 52 | for point in self.samplePoints(transform) { 53 | if let lastPoint = lastPoint { 54 | let midPoint = (lastPoint + point) * 0.5 55 | cgPath.addQuadCurve(to: midPoint, control: lastPoint) 56 | } 57 | else { 58 | cgPath.move(to: point) 59 | } 60 | lastPoint = point 61 | } 62 | return cgPath 63 | } 64 | 65 | // MARK: - 66 | 67 | override func didMove(to renderView: RenderView) { 68 | super.didMove(to: renderView) 69 | 70 | self.renderView?.mtkView.isPaused = false 71 | } 72 | 73 | override func draw(in context: CGContext) { 74 | /* 75 | context.saveGState() 76 | context.setFillColor(XColor.orange.cgColor) 77 | context.strokeEllipse(in: self.bounds.insetBy(dx: 100, dy: 100)) 78 | context.restoreGState() 79 | 80 | XColor.red.set() 81 | let bezier = XBezierPath(ovalIn: self.bounds) 82 | bezier.stroke() 83 | 84 | XColor.red.set() 85 | XBezierPath(ovalIn: CGRect(0, 0, self.bounds.width * 0.5, self.bounds.height * 0.5)).stroke() 86 | */ 87 | } 88 | 89 | override func render(in context: RenderContext) { 90 | context.render(texture: self.image1Texture, in: Rect(0, 0, 2048, 1024)) 91 | self.colorTriangle?.render(context: context) 92 | 93 | let bezierRenderer = device.renderer() as BezierRenderer 94 | bezierRenderer.render(context: context, cgPaths: [self.samplePath()]) 95 | 96 | context.widthCGContext { (cgContext) in 97 | let paragraphStyle : NSMutableParagraphStyle = .makeParagraphStyle() 98 | paragraphStyle.alignment = .center 99 | let attributes = [ 100 | convertFromNSAttributedStringKey(NSAttributedString.Key.font): XFont.boldSystemFont(ofSize: 32), 101 | convertFromNSAttributedStringKey(NSAttributedString.Key.foregroundColor): XColor.white, 102 | convertFromNSAttributedStringKey(NSAttributedString.Key.paragraphStyle): paragraphStyle 103 | ] 104 | let formatter = DateFormatter() 105 | formatter.dateFormat = "HH:mm:ss" 106 | (formatter.string(from: Date()) as NSString).draw(at: CGPoint(x: 758, y: 320), withAttributes: convertToOptionalNSAttributedStringKeyDictionary(attributes)) 107 | } 108 | } 109 | 110 | #if os(macOS) 111 | override func mouseDown(with event: NSEvent) { 112 | Swift.print("mouseDown:") 113 | if let location = self.locationInScene(event) { 114 | Swift.print("location=\(location)") 115 | } 116 | } 117 | #endif 118 | 119 | } 120 | 121 | 122 | // Helper function inserted by Swift 4.2 migrator. 123 | fileprivate func convertFromNSAttributedStringKey(_ input: NSAttributedString.Key) -> String { 124 | return input.rawValue 125 | } 126 | 127 | // Helper function inserted by Swift 4.2 migrator. 128 | fileprivate func convertToOptionalNSAttributedStringKeyDictionary(_ input: [String: Any]?) -> [NSAttributedString.Key: Any]? { 129 | guard let input = input else { return nil } 130 | return Dictionary(uniqueKeysWithValues: input.map { key, value in (NSAttributedString.Key(rawValue: key), value)}) 131 | } 132 | -------------------------------------------------------------------------------- /Shared/SampleSceneViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SilvershadowApp 4 | // 5 | // Created by Kaz Yoshikawa on 12/25/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import Cocoa 13 | #endif 14 | 15 | 16 | class SampleSceneViewController: XViewController { 17 | 18 | var sampleScene: SampleScene! 19 | 20 | @IBOutlet var renderView: RenderView! 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | let device = self.renderView.device 26 | let contentSize = CGSize(2048, 1024) 27 | 28 | self.sampleScene = SampleScene(device: device, contentSize: contentSize) 29 | self.renderView.scene = self.sampleScene 30 | } 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Shared/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codelynx/silvershadow/f30668067c54a48b92ede0436cd7f84d21434fd2/Shared/test.png -------------------------------------------------------------------------------- /Silvershadow.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Silvershadow.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Silvershadow/BezierRenderer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BezierRenderer.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 1/7/17. 6 | // Copyright © 2017 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | import QuartzCore 12 | import MetalKit 13 | import GLKit 14 | import simd 15 | 16 | 17 | extension CGPoint { 18 | static let nan = CGPoint(CGFloat.nan, CGFloat.nan) 19 | } 20 | 21 | class BezierRenderer: Renderer { 22 | 23 | typealias VertexType = Vertex 24 | 25 | // TODO: needs refactoring 26 | 27 | enum ElementType: UInt8 { 28 | case lineTo = 2 29 | case quadCurveTo = 3 30 | case curveTo = 4 31 | }; 32 | 33 | struct BezierPathElement { 34 | var type: UInt8 // 0 35 | var unused1: UInt8 // 1 36 | var unused2: UInt8 // 2 37 | var unused3: UInt8 // 3 38 | 39 | var numberOfVertexes: UInt16 // 4 40 | var vertexIndex: UInt16 // 6 41 | 42 | var width1: UInt16 // 8 43 | var width2: UInt16 // 10 44 | var unused4: UInt16 // 12 .. somehow needed 45 | var unused5: UInt16 // 14 .. somehow needed 46 | 47 | var p0: Point // 16 48 | var p1: Point // 24 49 | var p2: Point // 32 50 | var p3: Point // 40 51 | // 48 52 | 53 | init(type: ElementType, numberOfVertexes: Int, vertexIndex: Int, w1: Int, w2: Int, p0: Point, p1: Point, p2: Point, p3: Point) { 54 | self.type = type.rawValue 55 | self.unused1 = 0 56 | self.unused2 = 0 57 | self.unused3 = 0 58 | self.numberOfVertexes = UInt16(numberOfVertexes) 59 | self.vertexIndex = UInt16(vertexIndex) 60 | self.width1 = UInt16(w1) 61 | self.width2 = UInt16(w2) 62 | self.unused4 = 0 63 | self.unused5 = 0 64 | self.p0 = p0 65 | self.p1 = p1 66 | self.p2 = p2 67 | self.p3 = p3 68 | } 69 | } 70 | 71 | 72 | struct Vertex { 73 | var x: Float16 74 | var y: Float16 75 | var width: Float16 76 | var unused: Float16 = Float16(0.0) 77 | 78 | init(x: Float, y: Float, width: Float) { 79 | self.x = Float16(x) 80 | self.y = Float16(y) 81 | self.width = Float16(width) 82 | } 83 | 84 | init(point: Point, width: Float) { 85 | self.x = Float16(point.x) 86 | self.y = Float16(point.y) 87 | self.width = Float16(width) 88 | } 89 | 90 | } 91 | 92 | struct Uniforms { 93 | var transform: GLKMatrix4 94 | var zoomScale: Float 95 | var unused2: Float = 0 96 | var unused3: Float = 0 97 | var unused4: Float = 0 98 | 99 | init(transform: GLKMatrix4, zoomScale: Float) { 100 | self.transform = transform 101 | self.zoomScale = zoomScale 102 | } 103 | } 104 | 105 | let device: MTLDevice 106 | 107 | 108 | // MARK: - 109 | 110 | required init(device: MTLDevice) { 111 | self.device = device 112 | } 113 | 114 | var library: MTLLibrary { 115 | return self.device.makeDefaultLibrary()! 116 | } 117 | 118 | lazy var computePipelineState: MTLComputePipelineState = { 119 | let function = self.library.makeFunction(name: "bezier_kernel")! 120 | return try! self.device.makeComputePipelineState(function: function) 121 | }() 122 | 123 | var vertexDescriptor: MTLVertexDescriptor { 124 | let vertexDescriptor = MTLVertexDescriptor() 125 | 126 | vertexDescriptor.attributes[0].offset = 0 127 | vertexDescriptor.attributes[0].format = .half2 128 | vertexDescriptor.attributes[0].bufferIndex = 0 129 | 130 | vertexDescriptor.attributes[1].offset = MemoryLayout.size * 2 131 | vertexDescriptor.attributes[1].format = .half2 132 | vertexDescriptor.attributes[1].bufferIndex = 0 133 | 134 | vertexDescriptor.layouts[0].stepFunction = .perVertex 135 | vertexDescriptor.layouts[0].stride = MemoryLayout.stride 136 | 137 | return vertexDescriptor 138 | } 139 | 140 | lazy var renderPipelineState: MTLRenderPipelineState = { 141 | let renderPipelineDescriptor = MTLRenderPipelineDescriptor() 142 | renderPipelineDescriptor.vertexDescriptor = self.vertexDescriptor 143 | renderPipelineDescriptor.vertexFunction = self.library.makeFunction(name: "bezier_vertex")! 144 | renderPipelineDescriptor.fragmentFunction = self.library.makeFunction(name: "bezier_fragment")! 145 | 146 | renderPipelineDescriptor.colorAttachments[0].pixelFormat = .`default` 147 | renderPipelineDescriptor.colorAttachments[0].isBlendingEnabled = true 148 | renderPipelineDescriptor.colorAttachments[0].rgbBlendOperation = .add 149 | renderPipelineDescriptor.colorAttachments[0].alphaBlendOperation = .add 150 | 151 | // I don't believe this but this is what it is... 152 | renderPipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = .one 153 | renderPipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .one 154 | renderPipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha 155 | renderPipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha 156 | 157 | return try! self.device.makeRenderPipelineState(descriptor: renderPipelineDescriptor) 158 | }() 159 | 160 | lazy var shapeSamplerState: MTLSamplerState = { 161 | return self.device.makeSamplerState(descriptor: .`default`)! 162 | }() 163 | 164 | lazy var patternSamplerState: MTLSamplerState = { 165 | return self.device.makeSamplerState(descriptor: .`default`)! 166 | }() 167 | 168 | private typealias LineSegment = (type: ElementType, length: CGFloat, p0: CGPoint, p1: CGPoint, p2: CGPoint, p3: CGPoint) 169 | 170 | private func lineSegments(cgPaths: [CGPath]) -> [LineSegment] { 171 | 172 | return cgPaths.flatMap { (cgPath) -> [LineSegment] in 173 | 174 | var origin: CGPoint? 175 | var lastPoint: CGPoint? 176 | 177 | return cgPath.pathElements.compactMap { 178 | switch $0 { 179 | case let .moveTo(p1): 180 | origin = p1 181 | lastPoint = p1 182 | case let .lineTo(p1): 183 | guard let p0 = lastPoint else { return nil } 184 | let length = (p0 - p1).length 185 | lastPoint = p1 186 | return (.lineTo, length, p0, p1, .nan, .nan) 187 | case let .quadCurveTo(p1, p2): 188 | guard let p0 = lastPoint else { return nil } 189 | let length = CGPath.quadraticCurveLength(p0, p1, p2) 190 | lastPoint = p2 191 | return (.quadCurveTo, length, p0, p1, p2, .nan) 192 | case let .curveTo(p1, p2, p3): 193 | guard let p0 = lastPoint else { return nil } 194 | let length = CGPath.approximateCubicCurveLength(p0, p1, p2, p3) 195 | lastPoint = p3 196 | return (.curveTo, length, p0, p1, p2, p3) 197 | case .closeSubpath: 198 | guard let p0 = lastPoint, let p1 = origin else { return nil } 199 | let length = (p0 - p1).length 200 | lastPoint = nil 201 | origin = nil 202 | return (.lineTo, length, p0, p1, .nan, .nan) 203 | } 204 | return nil 205 | } 206 | } 207 | } 208 | 209 | // MARK: - 210 | 211 | #if os(iOS) 212 | lazy var heap: XHeap = { 213 | return self.device.makeHeap(size: 1024 * 1024 * 64) // ?? 214 | }() 215 | #endif 216 | 217 | #if os(macOS) 218 | var heap: XHeap { return self.device } 219 | #endif 220 | 221 | 222 | func makeElementBuffer(elements: [BezierPathElement]) -> MetalBuffer { 223 | return MetalBuffer(heap: heap, vertices: elements) 224 | } 225 | 226 | func makeVertexBuffer(vertices: [Vertex]?, capacity: Int) -> MetalBuffer { 227 | return MetalBuffer(heap: heap, vertices: vertices, capacity: capacity) 228 | } 229 | 230 | let vertexCapacity = 40_000 231 | let elementsCapacity = 4_000 232 | 233 | // MARK: - 234 | 235 | func render(context: RenderContext, cgPaths: [CGPath]) { 236 | guard cgPaths.count > 0 else { return } 237 | 238 | var elementsArray = [[BezierPathElement]]() 239 | let (w1, w2) = (8, 8) 240 | 241 | let segments = lineSegments(cgPaths: cgPaths) 242 | let totalLength = segments.reduce(CGFloat(0)) { $0 + $1.length } 243 | print("totalLength=\(totalLength)") 244 | 245 | var elements = [BezierPathElement]() 246 | var vertexCount: Int = 0 247 | var elementCount: Int = 0 248 | 249 | // due to limited memory resource, all vertexes may not be able to render at a time, but on the other hand, it should not render segment 250 | // by segment because of performance. Following code sprits line segments by vertex buffer's capacity. 251 | 252 | for segment in segments { 253 | let count = Int(ceil(segment.length)) 254 | if vertexCount + count > vertexCapacity || elementCount + 1 > elementsCapacity { 255 | elementsArray.append(elements) 256 | elements = [BezierPathElement]() 257 | vertexCount = 0 258 | elementCount = 0 259 | } 260 | let element = BezierPathElement(type: segment.type, numberOfVertexes: count, vertexIndex: vertexCount, w1: w1, w2: w2, 261 | p0: Point(segment.p0), p1: Point(segment.p1), p2: Point(segment.p2), p3: Point(segment.p3)) 262 | elements.append(element) 263 | vertexCount += count 264 | elementCount += 1 265 | } 266 | if elements.count > 0 { 267 | elementsArray.append(elements) 268 | } 269 | 270 | 271 | // now, elements are sprited 272 | 273 | 274 | var uniforms = Uniforms(transform: context.transform, 275 | zoomScale: Float(context.zoomScale)) 276 | let uniformsBuffer = device.makeBuffer(bytes: &uniforms, 277 | length: MemoryLayout.size, options: [])! 278 | 279 | 280 | // Now shading brush stroke on shadingTexture 281 | 282 | let shadingRenderPassDescriptor = MTLRenderPassDescriptor() 283 | shadingRenderPassDescriptor.colorAttachments[0].texture = context.shadingTexture 284 | shadingRenderPassDescriptor.colorAttachments[0].clearColor = .init() 285 | shadingRenderPassDescriptor.colorAttachments[0].loadAction = .clear 286 | shadingRenderPassDescriptor.colorAttachments[0].storeAction = .store 287 | 288 | print("elementsArray=\(elementsArray.count)") 289 | 290 | for elements in elementsArray { 291 | 292 | let vertexCount = elements.map { $0.numberOfVertexes }.reduce (0, +) 293 | guard vertexCount > 0 else { continue } 294 | 295 | let commandBuffer = context.makeCommandBuffer() 296 | let elementBuffer = makeElementBuffer(elements: elements) 297 | let vertexBuffer = makeVertexBuffer(vertices: [], capacity: Int(vertexCount)) 298 | 299 | // build contiguous vertexes using computing shader from PathElement 300 | 301 | do { 302 | let encoder = commandBuffer.makeComputeCommandEncoder()! 303 | encoder.pushDebugGroup("bezier - kernel") 304 | encoder.setComputePipelineState(computePipelineState) 305 | 306 | elementBuffer.set(elements) 307 | encoder.setBuffer(elementBuffer.buffer, offset: 0, index: 0) 308 | encoder.setBuffer(vertexBuffer.buffer, offset: 0, index: 1) 309 | let threadgroupsPerGrid = MTLSizeMake(elements.count, 1, 1) 310 | let threadsPerThreadgroup = MTLSizeMake(1, 1, 1) 311 | encoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup) 312 | 313 | encoder.popDebugGroup() 314 | encoder.endEncoding() 315 | } 316 | 317 | // vertex buffer should be filled with vertexes then draw it 318 | 319 | do { 320 | let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: shadingRenderPassDescriptor)! 321 | encoder.pushDebugGroup("bezier - brush shaping") 322 | encoder.setRenderPipelineState(renderPipelineState) 323 | 324 | encoder.setFrontFacing(.clockwise) 325 | 326 | encoder.setVertexBuffer(vertexBuffer.buffer, offset: 0, index: 0) 327 | encoder.setVertexBuffer(uniformsBuffer, offset: 0, index: 1) 328 | 329 | encoder.setFragmentTexture(context.brushShape, index: 0) 330 | encoder.setFragmentSamplerState(shapeSamplerState, index: 0) 331 | 332 | encoder.setFragmentTexture(context.brushPattern, index: 1) 333 | encoder.setFragmentSamplerState(patternSamplerState, index: 1) 334 | 335 | encoder.drawPrimitives(type: .point, vertexStart: 0, vertexCount: Int(vertexCount)) 336 | encoder.popDebugGroup() 337 | encoder.endEncoding() 338 | } 339 | 340 | commandBuffer.commit() 341 | 342 | shadingRenderPassDescriptor.colorAttachments[0].loadAction = .load 343 | } 344 | 345 | /* 346 | let p1 = float4x4(context.transform.invert) * float4(-1, -1, 0, 1) 347 | let p2 = float4x4(context.transform.invert) * float4(+1, +1, 0, 1) 348 | let (l, r, t, b) = (p1.x, p2.x, min(p1.y, p2.y), max(p1.y, p2.y)) 349 | */ 350 | 351 | // offscreen buffer does not require transform ?? 352 | context.pushContext() 353 | context.transform = GLKMatrix4Identity 354 | let renderer = context.device.renderer() as PatternRenderer 355 | renderer.renderPattern(context: context, in: Rect(-1, -1, 2, 2)) // ?? 356 | context.popContext() 357 | } 358 | 359 | } 360 | 361 | 362 | -------------------------------------------------------------------------------- /Silvershadow/BezierShaders.metal: -------------------------------------------------------------------------------- 1 | // 2 | // BezierKernel.metal 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 1/7/17. 6 | // Copyright © 2017 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | #include 10 | using namespace metal; 11 | 12 | enum PathElementType { 13 | PathElementTypeLineTo = 2, 14 | PathElementTypeQuadCurveTo = 3, 15 | PathElementTypeCurveTo = 4 16 | }; 17 | 18 | 19 | struct PathElement { 20 | unsigned char type; 21 | unsigned char unused1; 22 | unsigned char unused2; 23 | unsigned char unused3; 24 | 25 | unsigned short numberOfVertexes; // number of vertexes to produce (32 bit) 26 | unsigned short vertexIndex; // index to store (32-bit?) 27 | 28 | unsigned short width1; // width start 29 | unsigned short width2; // width end 30 | unsigned short unused4; // width start 31 | unsigned short unused5; // width end 32 | 33 | float2 p0; 34 | float2 p1; 35 | float2 p2; // may be nan 36 | float2 p3; // may be nan 37 | }; 38 | 39 | struct Vertex { 40 | half2 position; 41 | half2 width_unused; 42 | 43 | Vertex(half2 position, half width) { 44 | this->position = position; 45 | this->width_unused = half2(width, 0.0); 46 | } 47 | }; 48 | 49 | kernel void bezier_kernel( 50 | constant PathElement* elements [[ buffer(0) ]], 51 | device Vertex* outVertexes [[ buffer(1) ]], 52 | uint id [[ thread_position_in_grid ]] 53 | ) { 54 | PathElement element = elements[id]; 55 | int numberOfVertexes = element.numberOfVertexes; 56 | 57 | float2 p0 = element.p0; 58 | float2 p1 = element.p1; 59 | float2 p2 = element.p2; 60 | float2 p3 = element.p3; 61 | float w1 = float(element.width1); 62 | float w2 = float(element.width2); 63 | 64 | // Vertex v = Vertex(p0, float(element.numberOfVertexes)); 65 | // outVertexes[0] = v; 66 | // return; 67 | 68 | switch (element.type) { 69 | case PathElementTypeLineTo: 70 | for (int index = 0 ; index < numberOfVertexes ; index++) { 71 | float t = float(index) / float(numberOfVertexes); // 0.0 ... 1.0 72 | float2 q = p0 + (p1 - p0) * t; 73 | float w = w1 + (w2 - w1) * t; 74 | Vertex v = Vertex(half2(q.x, q.y), half(w)); 75 | outVertexes[element.vertexIndex + index] = v; 76 | } 77 | break; 78 | case PathElementTypeQuadCurveTo: 79 | for (int index = 0 ; index < numberOfVertexes ; index++) { 80 | float t = float(index) / float(numberOfVertexes); // 0.0 ... 1.0 81 | float2 q1 = p0 + (p1 - p0) * t; 82 | float2 q2 = p1 + (p2 - p1) * t; 83 | float2 r = q1 + (q2 - q1) * t; 84 | float w = w1 + (w2 - w1) * t; 85 | Vertex v = Vertex(half2(r.x, r.y), half(w)); 86 | outVertexes[element.vertexIndex + index] = v; 87 | } 88 | break; 89 | case PathElementTypeCurveTo: 90 | for (int index = 0 ; index < numberOfVertexes ; index++) { 91 | float t = float(index) / float(numberOfVertexes); // 0.0 ... 1.0 92 | float2 q1 = p0 + (p1 - p0) * t; 93 | float2 q2 = p1 + (p2 - p1) * t; 94 | float2 q3 = p2 + (p3 - p2) * t; 95 | float2 r1 = q1 + (q2 - q1) * t; 96 | float2 r2 = q2 + (q3 - q2) * t; 97 | float2 s = r1 + (r2 - r1) * t; 98 | float w = w1 + (w2 - w1) * t; 99 | Vertex v = Vertex(half2(s.x, s.y), half(w)); 100 | outVertexes[element.vertexIndex + index] = v; 101 | } 102 | break; 103 | } 104 | } 105 | 106 | struct VertexIn { // should be same as Vertex 107 | half2 position [[ attribute(0) ]]; 108 | half2 width_unused [[ attribute(1) ]]; 109 | }; 110 | 111 | struct VertexOut { 112 | float4 position [[ position ]]; 113 | float pointSize [[ point_size ]]; 114 | }; 115 | 116 | struct Uniforms { 117 | float4x4 transform; 118 | float zoomScale; 119 | float unused2; 120 | float unused3; 121 | float unused4; 122 | }; 123 | 124 | vertex VertexOut bezier_vertex( 125 | const device VertexIn * vertices [[ buffer(0) ]], 126 | constant Uniforms & uniforms [[ buffer(1) ]], 127 | uint vid [[ vertex_id ]] 128 | ) { 129 | VertexIn inVertex = vertices[vid]; 130 | VertexOut outVertex; 131 | 132 | outVertex.position = uniforms.transform * float4(float2(inVertex.position), 0.0, 1.0); 133 | outVertex.pointSize = vertices->width_unused[0] * uniforms.zoomScale; 134 | return outVertex; 135 | } 136 | 137 | fragment float4 bezier_fragment( 138 | VertexOut vertexIn [[ stage_in ]], 139 | texture2d shapeTexture [[ texture(0) ]], 140 | sampler shapeSampler [[ sampler(0) ]], 141 | 142 | texture2d patternTexture [[ texture(1) ]], 143 | sampler patternSampler [[ sampler(1) ]], 144 | 145 | float2 texcoord [[ point_coord ]] 146 | ) { 147 | float4 shapeColor = shapeTexture.sample(shapeSampler, texcoord); 148 | float4 patternColor = patternTexture.sample(patternSampler, float2(vertexIn.position.xy) + texcoord); 149 | float4 color = float4(patternColor.rgb, shapeColor.a); 150 | return color; 151 | } 152 | -------------------------------------------------------------------------------- /Silvershadow/CGPath+Z.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPath+Z.swift [swift3.0] 3 | // ZKit 4 | // 5 | // The MIT License (MIT) 6 | // 7 | // Copyright (c) 2016 Electricwoods LLC, Kaz Yoshikawa. 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | // 27 | 28 | 29 | import Foundation 30 | import CoreGraphics 31 | 32 | // 33 | // PathElement 34 | // 35 | 36 | public enum PathElement { 37 | case moveTo(CGPoint) 38 | case lineTo(CGPoint) 39 | case quadCurveTo(CGPoint, CGPoint) 40 | case curveTo(CGPoint, CGPoint, CGPoint) 41 | case closeSubpath 42 | 43 | // 44 | // operator == 45 | // 46 | 47 | static public func ==(lhs: PathElement, rhs: PathElement) -> Bool { 48 | switch (lhs, rhs) { 49 | case let (.moveTo(l), .moveTo(r)), 50 | let (.lineTo(l), .lineTo(r)): 51 | return l == r 52 | case let (.quadCurveTo(l), .quadCurveTo(r)): 53 | return l == r 54 | case let (.curveTo(l), .curveTo(r)): 55 | return l == r 56 | case (.closeSubpath, .closeSubpath): 57 | return true 58 | default: 59 | return false 60 | } 61 | } 62 | } 63 | 64 | 65 | // 66 | // CGPathRef 67 | // 68 | 69 | public extension CGPath { 70 | 71 | private class Elements { 72 | var pathElements = [PathElement]() 73 | } 74 | 75 | var pathElements: [PathElement] { 76 | var elements = Elements() 77 | 78 | self.apply(info: &elements) { (info, element) -> () in 79 | guard let infoPointer = UnsafeMutablePointer(OpaquePointer(info)) else { return } 80 | switch element.pointee.type { 81 | case .moveToPoint: 82 | let pt = element.pointee.points[0] 83 | infoPointer.pointee.pathElements.append(.moveTo(pt)) 84 | case .addLineToPoint: 85 | let pt = element.pointee.points[0] 86 | infoPointer.pointee.pathElements.append(.lineTo(pt)) 87 | case .addQuadCurveToPoint: 88 | let pt1 = element.pointee.points[0] 89 | let pt2 = element.pointee.points[1] 90 | infoPointer.pointee.pathElements.append(.quadCurveTo(pt1, pt2)) 91 | case .addCurveToPoint: 92 | let pt1 = element.pointee.points[0] 93 | let pt2 = element.pointee.points[1] 94 | let pt3 = element.pointee.points[2] 95 | infoPointer.pointee.pathElements.append(.curveTo(pt1, pt2, pt3)) 96 | case .closeSubpath: 97 | infoPointer.pointee.pathElements.append(.closeSubpath) 98 | @unknown default: 99 | break 100 | } 101 | } 102 | 103 | let pathelements = elements.pathElements 104 | return pathelements 105 | } 106 | } 107 | 108 | 109 | // 110 | // 111 | // 112 | 113 | extension CGPath { 114 | 115 | static func quadraticCurveLength(_ p0: CGPoint, _ p1: CGPoint, _ p2: CGPoint) -> CGFloat { 116 | 117 | // cf. http://www.malczak.linuxpl.com/blog/quadratic-bezier-curve-length/ 118 | 119 | let a = CGPoint(p0.x - 2 * p1.x + p2.x, p0.y - 2 * p1.y + p2.y) 120 | let b = CGPoint(2 * p1.x - 2 * p0.x, 2 * p1.y - 2 * p0.y) 121 | let A = 4 * (a.x * a.x + a.y * a.y) 122 | let B = 4 * (a.x * b.x + a.y * b.y) 123 | let C = b.x * b.x + b.y * b.y 124 | let Sabc = 2 * sqrt(A + B + C) 125 | let A_2 = sqrt(A) 126 | let A_32 = 2 * A * A_2 127 | let C_2 = 2 * sqrt(C) 128 | let BA = B / A_2 129 | let L = (A_32 * Sabc + A_2 * B * (Sabc - C_2) + (4 * C * A - B * B) * log((2 * A_2 + BA + Sabc) / (BA + C_2))) / (4 * A_32) 130 | return L.isNaN ? 0 : L 131 | } 132 | 133 | static func approximateCubicCurveLength(_ p0: CGPoint, _ p1: CGPoint, _ p2: CGPoint, _ p3: CGPoint) -> CGFloat { 134 | let n = 32 135 | var length: CGFloat = 0 136 | var point: CGPoint? = nil 137 | for i in 0 ..< n { 138 | let t = CGFloat(i) / CGFloat(n) 139 | 140 | let q1 = p0 + (p1 - p0) * t 141 | let q2 = p1 + (p2 - p1) * t 142 | let q3 = p2 + (p3 - p2) * t 143 | 144 | let r1 = q1 + (q2 - q1) * t 145 | let r2 = q2 + (q3 - q2) * t 146 | 147 | let s = r1 + (r2 - r1) * t 148 | 149 | if let point = point { 150 | length += (point - s).length 151 | } 152 | point = s 153 | } 154 | return length 155 | } 156 | 157 | } 158 | 159 | 160 | -------------------------------------------------------------------------------- /Silvershadow/Canvas.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Canvas.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/28/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import MetalKit 10 | import GLKit 11 | 12 | #if os(iOS) 13 | import UIKit 14 | #elseif os(macOS) 15 | import Cocoa 16 | #endif 17 | 18 | 19 | // Canvas 20 | // 21 | // Canvas is designed for rendering on offscreen bitmap target. Whereas, Scene is for rendering 22 | // on screen (MTKView) directly. Beaware of content size affects big impact to the memory usage. 23 | // 24 | 25 | extension MTLTextureDescriptor { 26 | static func texture2DDescriptor(size: CGSize, mipmapped: Bool, usage: MTLTextureUsage = []) -> MTLTextureDescriptor { 27 | let desc = texture2DDescriptor(pixelFormat: .`default`, 28 | width: Int(size.width), 29 | height: Int(size.height), 30 | mipmapped: mipmapped) 31 | desc.usage = usage 32 | return desc 33 | } 34 | } 35 | 36 | extension MTLDevice { 37 | func makeTexture2D(size: CGSize, mipmapped: Bool, usage: MTLTextureUsage) -> MTLTexture { 38 | return makeTexture(descriptor: .texture2DDescriptor(size: size, 39 | mipmapped: mipmapped, 40 | usage: usage))! 41 | } 42 | } 43 | 44 | class Canvas: Scene { 45 | 46 | // master texture of canvas 47 | lazy var canvasTexture: MTLTexture = { 48 | return self.device.makeTexture2D(size: self.contentSize, 49 | mipmapped: self.mipmapped, 50 | usage: [.shaderRead, .shaderWrite, .renderTarget]) 51 | }() 52 | 53 | lazy var canvasRenderer: ImageRenderer = { 54 | return self.device.renderer() 55 | }() 56 | 57 | fileprivate (set) var canvasLayers: [CanvasLayer] 58 | 59 | var overlayCanvasLayer: CanvasLayer? { 60 | didSet { 61 | overlayCanvasLayer?.canvas = self 62 | setNeedsDisplay() 63 | } 64 | } 65 | 66 | override init?(device: MTLDevice, contentSize: CGSize) { 67 | canvasLayers = [] 68 | super.init(device: device, contentSize: contentSize) 69 | } 70 | 71 | override func didMove(to renderView: RenderView) { 72 | super.didMove(to: renderView) 73 | } 74 | 75 | lazy var sublayerTexture: MTLTexture = { 76 | return self.device.makeTexture2D(size: self.contentSize, 77 | mipmapped: self.mipmapped, 78 | usage: [.shaderRead, .renderTarget]) 79 | }() 80 | 81 | lazy var subcomandQueue: MTLCommandQueue = { 82 | return self.device.makeCommandQueue()! 83 | }() 84 | 85 | override func update() { 86 | 87 | let date = Date() 88 | defer { Swift.print("Canvas: update", -date.timeIntervalSinceNow * 1000, " ms") } 89 | 90 | let commandQueue = subcomandQueue 91 | let canvasTexture = self.canvasTexture 92 | 93 | let renderPassDescriptor = MTLRenderPassDescriptor() 94 | renderPassDescriptor.colorAttachments[0].texture = canvasTexture 95 | renderPassDescriptor.colorAttachments[0].clearColor = .Red 96 | renderPassDescriptor.colorAttachments[0].storeAction = .store 97 | 98 | // clear canvas texture 99 | renderPassDescriptor.colorAttachments[0].loadAction = .clear 100 | let commandBuffer = commandQueue.makeCommandBuffer()! 101 | let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)! 102 | commandEncoder.endEncoding() 103 | commandBuffer.commit() 104 | renderPassDescriptor.colorAttachments[0].loadAction = .load 105 | 106 | 107 | // build an image per layer then flatten that image to the canvas texture 108 | let subtexture = self.sublayerTexture 109 | let subtransform = GLKMatrix4(transform) 110 | 111 | let subrenderPassDescriptor = MTLRenderPassDescriptor() 112 | subrenderPassDescriptor.colorAttachments[0].texture = subtexture 113 | subrenderPassDescriptor.colorAttachments[0].clearColor = .init() 114 | subrenderPassDescriptor.colorAttachments[0].storeAction = .store 115 | 116 | 117 | for canvasLayer in canvasLayers where !canvasLayer.isHidden { 118 | 119 | // clear subtexture 120 | 121 | subrenderPassDescriptor.colorAttachments[0].loadAction = .clear 122 | let commandBuffer = commandQueue.makeCommandBuffer()! 123 | let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: subrenderPassDescriptor)! 124 | commandEncoder.endEncoding() 125 | commandBuffer.commit() 126 | subrenderPassDescriptor.colorAttachments[0].loadAction = .load 127 | 128 | let subrenderContext = RenderContext(renderPassDescriptor: subrenderPassDescriptor, 129 | commandQueue: commandQueue, 130 | contentSize: contentSize, 131 | deviceSize: contentSize, 132 | transform: subtransform) 133 | 134 | // render a layer 135 | canvasLayer.render(context: subrenderContext) 136 | 137 | // flatten image 138 | let renderContext = RenderContext(renderPassDescriptor: renderPassDescriptor, 139 | commandQueue: commandQueue, 140 | contentSize: contentSize, 141 | deviceSize: contentSize, 142 | transform: .identity) 143 | renderContext.render(texture: subtexture, in: Rect(-1, -1, 2, 2)) 144 | 145 | } 146 | 147 | 148 | // let commandBuffer = commandQueue.makeCommandBuffer() 149 | // commandBuffer.commit() 150 | // commandBuffer.waitUntilCompleted() 151 | 152 | // drawing in offscreen (canvasTexture) is done, 153 | setNeedsDisplay() 154 | } 155 | 156 | var threadSize: MTLSize { 157 | var size = 32 158 | while (canvasTexture.width / size) * (canvasTexture.height / size) > 1024 { 159 | size *= 2 160 | } 161 | return MTLSize(width: size, height: size, depth: 1) 162 | } 163 | 164 | var threadsPerThreadgroup: MTLSize { 165 | let threadSize = self.threadSize 166 | return MTLSize(width: canvasTexture.width / threadSize.width, height: canvasTexture.height / threadSize.height, depth: 1) 167 | } 168 | 169 | override func render(in context: RenderContext) { 170 | 171 | let date = Date() 172 | defer { Swift.print("Canvas: render", -date.timeIntervalSinceNow * 1000, " ms") } 173 | 174 | // build rendering overlay canvas layer 175 | 176 | //let commandBuffer = context.makeCommandBuffer() 177 | 178 | guard let overlayCanvasLayer = overlayCanvasLayer else { return } 179 | print("render: \(Date()), \(String(describing: overlayCanvasLayer.name))") 180 | 181 | let subtexture = self.sublayerTexture 182 | let subtransform = GLKMatrix4(transform) 183 | 184 | let subrenderPassDescriptor = MTLRenderPassDescriptor() 185 | subrenderPassDescriptor.colorAttachments[0].texture = subtexture 186 | subrenderPassDescriptor.colorAttachments[0].clearColor = .init() 187 | subrenderPassDescriptor.colorAttachments[0].loadAction = .clear 188 | subrenderPassDescriptor.colorAttachments[0].storeAction = .store 189 | 190 | // clear subtexture 191 | let commandBuffer = context.makeCommandBuffer() 192 | let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: subrenderPassDescriptor)! 193 | commandEncoder.endEncoding() 194 | commandBuffer.commit() 195 | 196 | subrenderPassDescriptor.colorAttachments[0].loadAction = .load 197 | let subrenderContext = RenderContext(renderPassDescriptor: subrenderPassDescriptor, 198 | commandQueue: context.commandQueue, 199 | contentSize: contentSize, 200 | deviceSize: contentSize, 201 | transform: subtransform, 202 | zoomScale: 1) 203 | overlayCanvasLayer.render(context: subrenderContext) 204 | 205 | // render canvas texture 206 | 207 | canvasRenderer.renderTexture(context: context, texture: canvasTexture, in: Rect(bounds)) 208 | 209 | // render overlay canvas layer 210 | 211 | context.render(texture: subtexture, in: Rect(bounds)) 212 | } 213 | 214 | func addLayer(_ layer: CanvasLayer) { 215 | canvasLayers.append(layer) 216 | layer.didMoveTo(canvas: self) 217 | setNeedsUpdate() 218 | } 219 | 220 | func bringLayer(toFront: CanvasLayer) { 221 | assert(false, "not yet") 222 | } 223 | 224 | func sendLayer(toBack: CanvasLayer) { 225 | assert(false, "not yet") 226 | } 227 | 228 | } 229 | 230 | extension RangeReplaceableCollection where Iterator.Element : Equatable { 231 | mutating func remove(_ element: Iterator.Element) -> Index? { 232 | return firstIndex(of: element).map { self.remove(at: $0); return $0 } 233 | } 234 | } 235 | 236 | extension CanvasLayer { 237 | func removeFromCanvas() { 238 | _ = canvas?.canvasLayers.remove(self) 239 | } 240 | } 241 | 242 | -------------------------------------------------------------------------------- /Silvershadow/CanvasLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CanvasLayer.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/28/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import MetalKit 10 | 11 | class CanvasLayer: Equatable { 12 | 13 | var name: String? 14 | 15 | weak var canvas: Canvas? 16 | var isHidden: Bool = false 17 | 18 | var device: MTLDevice? { return self.canvas?.device } 19 | 20 | var contentSize: CGSize? { return self.canvas?.contentSize } 21 | 22 | var bounds: CGRect? { 23 | return contentSize.map { CGRect(size: $0) } 24 | } 25 | 26 | init() { 27 | self.canvas = nil 28 | } 29 | 30 | func didMoveTo(canvas: Canvas) { 31 | self.canvas = canvas 32 | } 33 | 34 | func render(context: RenderContext) { 35 | } 36 | 37 | static func == (lhs: CanvasLayer, rhs: CanvasLayer) -> Bool { 38 | return lhs === rhs 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Silvershadow/ColorRenderable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorRenderable.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/13/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | import MetalKit 12 | 13 | class ColorRectRenderable: Renderable { 14 | 15 | typealias RendererType = ColorRenderer 16 | 17 | let device: MTLDevice 18 | lazy var vertexBuffer: VertexBuffer = { 19 | let renderer = self.renderer 20 | let vertices = renderer.vertices(for: self.frame, color: self.color) 21 | return renderer.vertexBuffer(for: vertices)! 22 | }() 23 | var frame: Rect 24 | var color: XColor 25 | 26 | init?(device: MTLDevice, frame: Rect, color: XColor) { 27 | self.device = device 28 | self.frame = frame 29 | self.color = color 30 | let vertices = renderer.vertices(for: frame, color: color) 31 | guard let vertexBuffer = renderer.vertexBuffer(for: vertices) else { return nil } 32 | self.vertexBuffer = vertexBuffer 33 | } 34 | 35 | func render(context: RenderContext) { 36 | self.renderer.render(context: context, vertexBuffer: vertexBuffer) 37 | } 38 | 39 | } 40 | 41 | 42 | class ColorTriangleRenderable: Renderable { 43 | 44 | typealias RendererType = ColorRenderer 45 | 46 | let device: MTLDevice 47 | var point1: ColorVertex 48 | var point2: ColorVertex 49 | var point3: ColorVertex 50 | 51 | lazy var vertexBuffer: VertexBuffer = { 52 | let vertices: [ColorVertex] = [ self.point1, self.point2, self.point3 ] 53 | return self.renderer.vertexBuffer(for: vertices)! 54 | }() 55 | 56 | init?(device: MTLDevice, point1: ColorVertex, point2: ColorVertex, point3: ColorVertex) { 57 | self.device = device 58 | self.point1 = point1 59 | self.point2 = point2 60 | self.point3 = point3 61 | } 62 | 63 | func render(context: RenderContext) { 64 | self.renderer.render(context: context, vertexBuffer: vertexBuffer) 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Silvershadow/ColorRenderer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SolidRenderer.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 11/12/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | import GLKit 12 | 13 | typealias ColorVertex = ColorRenderer.Vertex 14 | 15 | class ColorRenderer: Renderer { 16 | 17 | typealias VertexType = Vertex 18 | 19 | // MARK: - 20 | 21 | var device: MTLDevice 22 | 23 | // MARK: - 24 | 25 | required init(device: MTLDevice) { 26 | self.device = device 27 | } 28 | 29 | deinit { 30 | } 31 | 32 | struct Vertex { 33 | var x, y, z, w, r, g, b, a: Float 34 | } 35 | 36 | struct Uniforms { 37 | var transform: GLKMatrix4 38 | } 39 | 40 | var vertexDescriptor: MTLVertexDescriptor { 41 | let vertexDescriptor = MTLVertexDescriptor() 42 | vertexDescriptor.attributes[0].offset = 0 43 | vertexDescriptor.attributes[0].format = .float2 44 | vertexDescriptor.attributes[0].bufferIndex = 0 45 | 46 | vertexDescriptor.attributes[1].offset = MemoryLayout.size * 4 47 | vertexDescriptor.attributes[1].format = .float4 48 | vertexDescriptor.attributes[1].bufferIndex = 0 49 | 50 | vertexDescriptor.layouts[0].stepFunction = .perVertex 51 | vertexDescriptor.layouts[0].stride = MemoryLayout.size 52 | return vertexDescriptor 53 | } 54 | 55 | lazy var library: MTLLibrary = { 56 | return self.device.makeDefaultLibrary()! 57 | }() 58 | 59 | lazy var renderPipelineState: MTLRenderPipelineState = { 60 | let renderPipelineDescriptor = MTLRenderPipelineDescriptor() 61 | renderPipelineDescriptor.vertexDescriptor = self.vertexDescriptor 62 | renderPipelineDescriptor.vertexFunction = self.library.makeFunction(name: "color_vertex")! 63 | renderPipelineDescriptor.fragmentFunction = self.library.makeFunction(name: "color_fragment")! 64 | 65 | renderPipelineDescriptor.colorAttachments[0].pixelFormat = .`default` 66 | renderPipelineDescriptor.colorAttachments[0].isBlendingEnabled = true 67 | renderPipelineDescriptor.colorAttachments[0].rgbBlendOperation = .add 68 | renderPipelineDescriptor.colorAttachments[0].alphaBlendOperation = .add 69 | 70 | renderPipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = .one 71 | renderPipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .one 72 | renderPipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha 73 | renderPipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha 74 | 75 | let renderPipelineState = try! self.device.makeRenderPipelineState(descriptor: renderPipelineDescriptor) 76 | return renderPipelineState 77 | }() 78 | 79 | lazy var colorSamplerState: MTLSamplerState = { 80 | return self.device.makeSamplerState(descriptor: .`default`)! 81 | }() 82 | 83 | func render(context: RenderContext, vertexBuffer: VertexBuffer) { 84 | var uniforms = Uniforms(transform: context.transform) 85 | let uniformsBuffer = device.makeBuffer(bytes: &uniforms, length: MemoryLayout.size, options: [])! 86 | 87 | let commandBuffer = context.makeCommandBuffer() 88 | 89 | let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: context.renderPassDescriptor)! 90 | encoder.setRenderPipelineState(self.renderPipelineState) 91 | encoder.setVertexBuffer(vertexBuffer.buffer, offset: 0, index: 0) 92 | encoder.setVertexBuffer(uniformsBuffer, offset: 0, index: 1) 93 | 94 | encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertexBuffer.count) 95 | 96 | encoder.endEncoding() 97 | 98 | commandBuffer.commit() 99 | } 100 | 101 | func vertexBuffer(for vertices: [Vertex]) -> VertexBuffer? { 102 | return VertexBuffer(device: device, vertices: vertices) 103 | } 104 | 105 | func vertices(for rect: Rect, color: XColor) -> [Vertex] { 106 | let l = rect.minX, r = rect.maxX, t = rect.minY, b = rect.maxY 107 | let rgba = color.rgba 108 | let (_r, _g, _b, _a) = (Float(rgba.r), Float(rgba.g), Float(rgba.b), Float(rgba.a)) 109 | return [ 110 | Vertex(x: l, y: t, z: 0, w: 1, r: _r, g: _g, b: _b, a: _a), 111 | Vertex(x: l, y: b, z: 0, w: 1, r: _r, g: _g, b: _b, a: _a), 112 | Vertex(x: r, y: b, z: 0, w: 1, r: _r, g: _g, b: _b, a: _a), 113 | Vertex(x: l, y: t, z: 0, w: 1, r: _r, g: _g, b: _b, a: _a), 114 | Vertex(x: r, y: b, z: 0, w: 1, r: _r, g: _g, b: _b, a: _a), 115 | Vertex(x: r, y: t, z: 0, w: 1, r: _r, g: _g, b: _b, a: _a), 116 | ] 117 | } 118 | 119 | func render(context: RenderContext, rect: Rect, color: XColor) { 120 | guard let vertexBuffer = self.vertexBuffer(for: self.vertices(for: rect, color: color)) else { return } 121 | render(context: context, vertexBuffer: vertexBuffer) 122 | } 123 | 124 | } 125 | 126 | 127 | extension RenderContext { 128 | 129 | 130 | func render(triangles: [(ColorVertex, ColorVertex, ColorVertex)]) { 131 | 132 | let renderer: ColorRenderer = self.device.renderer() 133 | let vertexes: [ColorVertex] = triangles.flatMap { [$0.0, $0.1, $0.2] } 134 | if let vertexBuffer = renderer.vertexBuffer(for: vertexes) { 135 | renderer.render(context: self, vertexBuffer: vertexBuffer) 136 | } 137 | } 138 | 139 | func render(triangles: [(Point, Point, Point)], color: XColor) { 140 | let rgba = color.rgba 141 | let (r, g, b, a) = (Float(rgba.r), Float(rgba.g), Float(rgba.b), Float(rgba.a)) 142 | let renderer: ColorRenderer = self.device.renderer() 143 | let vertexes: [ColorVertex] = triangles.flatMap { 144 | [$0.0, $0.1, $0.2].map { ColorVertex(x: $0.x, y: $0.y, z: 0, w: 1, r: r, g: g, b: b, a: a) } 145 | } 146 | if let vertexBuffer = renderer.vertexBuffer(for: vertexes) { 147 | renderer.render(context: self, vertexBuffer: vertexBuffer) 148 | } 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /Silvershadow/ColorShaders.metal: -------------------------------------------------------------------------------- 1 | // 2 | // ColorShaders.metal 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/22/15. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | #include 10 | using namespace metal; 11 | 12 | 13 | struct VertexIn { 14 | float4 position [[ attribute(0) ]]; 15 | float4 color [[ attribute(1) ]]; 16 | }; 17 | 18 | struct VertexOut { 19 | float4 position [[ position ]]; 20 | float4 color; 21 | }; 22 | 23 | struct Uniforms { 24 | float4x4 transform; 25 | }; 26 | 27 | vertex VertexOut color_vertex( 28 | const device VertexIn * vertices [[ buffer(0) ]], 29 | constant Uniforms & uniforms [[ buffer(1) ]], 30 | uint vid [[ vertex_id ]] 31 | ) { 32 | VertexOut outVertex; 33 | VertexIn inVertex = vertices[vid]; 34 | outVertex.position = uniforms.transform * float4(inVertex.position); 35 | outVertex.color = float4(inVertex.color); 36 | return outVertex; 37 | } 38 | 39 | fragment float4 color_fragment( 40 | VertexOut vertexIn [[ stage_in ]] 41 | ) { 42 | return vertexIn.color; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Silvershadow/CoreGraphics+Z.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreGraphics+Z.swift 3 | // ZKit 4 | // 5 | // Created by Kaz Yoshikawa on 12/12/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | 12 | 13 | extension CGRect { 14 | init(size: CGSize) { 15 | self.init(origin: .zero, size: size) 16 | } 17 | 18 | var cgPath: CGPath { 19 | return CGPath(rect: self, transform: nil) 20 | } 21 | 22 | func cgPath(cornerRadius: CGFloat) -> CGPath { 23 | 24 | // +7-------------6+ 25 | // 0 5 26 | // | | 27 | // 1 4 28 | // +2-------------3+ 29 | 30 | let cornerRadius = min(size.width * 0.5, size.height * 0.5, cornerRadius) 31 | let path = CGMutablePath() 32 | path.move(to: minXmidY + CGPoint(x: 0, y: cornerRadius)) // (0) 33 | path.addLine(to: minXmaxY - CGPoint(x: 0, y: cornerRadius)) // (1) 34 | path.addQuadCurve(to: minXmaxY + CGPoint(x: cornerRadius, y: 0), control: minXmaxY) // (2) 35 | path.addLine(to: maxXmaxY - CGPoint(x: cornerRadius, y: 0)) // (3) 36 | path.addQuadCurve(to: maxXmaxY - CGPoint(x: 0, y: cornerRadius), control: maxXmaxY) // (4) 37 | path.addLine(to: maxXminY + CGPoint(x: 0, y: cornerRadius)) // (5) 38 | path.addQuadCurve(to: maxXminY - CGPoint(x: cornerRadius, y: 0), control: maxXminY) // (6) 39 | path.addLine(to: minXminY + CGPoint(x: cornerRadius, y: 0)) // (7) 40 | path.addQuadCurve(to: minXminY + CGPoint(x: 0, y: cornerRadius), control: minXminY) // (0) 41 | path.closeSubpath() 42 | return path 43 | } 44 | 45 | var minXminY: CGPoint { return CGPoint(x: minX, y: minY) } 46 | var midXminY: CGPoint { return CGPoint(x: midX, y: minY) } 47 | var maxXminY: CGPoint { return CGPoint(x: maxX, y: minY) } 48 | 49 | var minXmidY: CGPoint { return CGPoint(x: minX, y: midY) } 50 | var midXmidY: CGPoint { return CGPoint(x: midX, y: midY) } 51 | var maxXmidY: CGPoint { return CGPoint(x: maxX, y: midY) } 52 | 53 | var minXmaxY: CGPoint { return CGPoint(x: minX, y: maxY) } 54 | var midXmaxY: CGPoint { return CGPoint(x: midX, y: maxY) } 55 | var maxXmaxY: CGPoint { return CGPoint(x: maxX, y: maxY) } 56 | 57 | func aspectFill(_ size: CGSize) -> CGRect { 58 | let result: CGRect 59 | let margin: CGFloat 60 | let horizontalRatioToFit = size.width / size.width 61 | let verticalRatioToFit = size.height / size.height 62 | let imageHeightWhenItFitsHorizontally = horizontalRatioToFit * size.height 63 | let imageWidthWhenItFitsVertically = verticalRatioToFit * size.width 64 | if (imageHeightWhenItFitsHorizontally > size.height) { 65 | margin = (imageHeightWhenItFitsHorizontally - size.height) * 0.5 66 | result = CGRect(x: minX, y: minY - margin, width: size.width * horizontalRatioToFit, height: size.height * horizontalRatioToFit) 67 | } 68 | else { 69 | margin = (imageWidthWhenItFitsVertically - size.width) * 0.5 70 | result = CGRect(x: minX - margin, y: minY, width: size.width * verticalRatioToFit, height: size.height * verticalRatioToFit) 71 | } 72 | return result 73 | } 74 | 75 | func aspectFit(_ size: CGSize) -> CGRect { 76 | let widthRatio = self.size.width / size.width 77 | let heightRatio = self.size.height / size.height 78 | let ratio = min(widthRatio, heightRatio) 79 | let width = size.width * ratio 80 | let height = size.height * ratio 81 | let xmargin = (self.size.width - width) / 2.0 82 | let ymargin = (self.size.height - height) / 2.0 83 | return CGRect(x: minX + xmargin, y: minY + ymargin, width: width, height: height) 84 | } 85 | 86 | func transform(to rect: CGRect) -> CGAffineTransform { 87 | var t = CGAffineTransform.identity 88 | t = t.translatedBy(x: -minX, y: -minY) 89 | t = t.scaledBy(x: 1 / width, y: 1 / height) 90 | t = t.scaledBy(x: rect.width, y: rect.height) 91 | t = t.translatedBy(x: rect.minX * width / rect.width, y: rect.minY * height / rect.height) 92 | return t 93 | } 94 | 95 | } 96 | 97 | extension CGSize { 98 | 99 | func aspectFit(_ size: CGSize) -> CGSize { 100 | let widthRatio = self.width / size.width 101 | let heightRatio = self.height / size.height 102 | let ratio = (widthRatio < heightRatio) ? widthRatio : heightRatio 103 | let width = size.width * ratio 104 | let height = size.height * ratio 105 | return CGSize(width: width, height: height) 106 | } 107 | 108 | static func - (lhs: CGSize, rhs: CGSize) -> CGSize { 109 | return CGSize(width: lhs.width - rhs.width, height: lhs.height - rhs.height) 110 | } 111 | } 112 | 113 | 114 | extension CGAffineTransform { 115 | 116 | static func * (lhs: CGAffineTransform, rhs: CGAffineTransform) -> CGAffineTransform { 117 | return lhs.concatenating(rhs) 118 | } 119 | 120 | } 121 | 122 | -------------------------------------------------------------------------------- /Silvershadow/CrossPlatform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrossPlatform.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/25/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | #if os(iOS) 13 | 14 | import UIKit 15 | typealias XView = UIView 16 | typealias XImage = UIImage 17 | typealias XColor = UIColor 18 | typealias XBezierPath = UIBezierPath 19 | typealias XScrollView = UIScrollView 20 | typealias XScrollViewDelegate = UIScrollViewDelegate 21 | typealias XViewController = UIViewController 22 | typealias XFont = UIFont 23 | 24 | extension UIBezierPath { 25 | func line(to point: CGPoint) { 26 | addLine(to: point) 27 | } 28 | } 29 | 30 | 31 | #elseif os(macOS) 32 | 33 | import Cocoa 34 | typealias XView = NSView 35 | typealias XImage = NSImage 36 | typealias XColor = NSColor 37 | typealias XBezierPath = NSBezierPath 38 | typealias XScrollView = NSScrollView 39 | typealias XViewController = NSViewController 40 | typealias XFont = NSFont 41 | 42 | protocol XScrollViewDelegate {} 43 | 44 | 45 | extension NSBezierPath { 46 | 47 | func addLine(to point: CGPoint) { 48 | line(to: point) 49 | } 50 | 51 | public var cgPath: CGPath { 52 | let path = CGMutablePath() 53 | var points = [CGPoint](repeating: .zero, count: 3) 54 | for i in 0 ..< elementCount { 55 | let type = element(at: i, associatedPoints: &points) 56 | switch type { 57 | case .moveTo: path.move(to: points[0]) 58 | case .lineTo: path.addLine(to: points[0]) 59 | case .curveTo: path.addCurve(to: points[2], control1: points[0], control2: points[1]) 60 | case .closePath: path.closeSubpath() 61 | @unknown default: break 62 | } 63 | } 64 | return path 65 | } 66 | 67 | } 68 | 69 | extension NSView { 70 | 71 | func setNeedsLayout() { 72 | layout() 73 | } 74 | 75 | func setNeedsDisplay() { 76 | setNeedsDisplay(bounds) 77 | } 78 | 79 | func sendSubview(toBack: NSView) { 80 | var subviews = self.subviews 81 | guard let index = subviews.firstIndex(of: toBack) else { return } 82 | subviews.remove(at: index) 83 | subviews.insert(toBack, at: 0) 84 | self.subviews = subviews 85 | } 86 | 87 | func bringSubview(toFront: NSView) { 88 | var subviews = self.subviews 89 | guard let index = subviews.firstIndex(of: toFront) else { return } 90 | subviews.remove(at: index) 91 | subviews.append(toFront) 92 | self.subviews = subviews 93 | } 94 | 95 | func replaceSubview(subview: NSView, with other: NSView) { 96 | var subviews = self.subviews 97 | guard let index = subviews.firstIndex(of: subview) else { return } 98 | subviews.remove(at: index) 99 | subviews.insert(other, at: index) 100 | self.subviews = subviews 101 | } 102 | } 103 | 104 | extension NSImage { 105 | // somehow OSX does not provide CGImage property 106 | 107 | var cgImage: CGImage? { 108 | return cgImage(forProposedRect: nil, context: nil, hints: nil) 109 | } 110 | } 111 | 112 | extension CGContext { 113 | static var current : CGContext? { 114 | return NSGraphicsContext.current?.cgContext 115 | } 116 | } 117 | 118 | extension NSScrollView { 119 | var zoomScale : CGFloat { 120 | return magnification 121 | } 122 | } 123 | 124 | #endif 125 | 126 | struct XRGBA { 127 | let r,g,b,a: CGFloat 128 | 129 | init(r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) { 130 | self.r = r 131 | self.g = g 132 | self.b = b 133 | self.a = a 134 | } 135 | 136 | init(ciColor : CIColor) { 137 | self.init(r: ciColor.red, g: ciColor.green, b: ciColor.blue, a: ciColor.alpha) 138 | } 139 | 140 | init() { 141 | self.init(r: 0, g: 0, b: 0, a: 0) 142 | } 143 | 144 | init(color: XColor) { 145 | #if os(iOS) 146 | self.init(ciColor: CIColor(color: color)) 147 | #elseif os(macOS) 148 | self.init(ciColor: CIColor(color: color)!) 149 | #endif 150 | } 151 | } 152 | 153 | extension XColor { 154 | 155 | var rgba: XRGBA { 156 | return .init(color: self) 157 | } 158 | } 159 | 160 | extension NSMutableParagraphStyle { 161 | 162 | static func makeParagraphStyle() -> NSMutableParagraphStyle { 163 | #if os(iOS) 164 | return NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle 165 | #elseif os(macOS) 166 | return NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle 167 | #endif 168 | } 169 | } 170 | 171 | 172 | -------------------------------------------------------------------------------- /Silvershadow/Float16.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Float16.swift 3 | // ZKit 4 | // 5 | // Created by Kaz Yoshikawa on 2/2/17. 6 | // Copyright © 2017 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Accelerate 11 | 12 | struct Float16: CustomStringConvertible { 13 | 14 | var rawValue: UInt16 15 | 16 | static func float_to_float16(value: Float) -> UInt16 { 17 | var input: [Float] = [value] 18 | var output: [UInt16] = [0] 19 | var sourceBuffer = vImage_Buffer(data: &input, height: 1, width: 1, rowBytes: MemoryLayout.size) 20 | var destinationBuffer = vImage_Buffer(data: &output, height: 1, width: 1, rowBytes: MemoryLayout.size) 21 | vImageConvert_PlanarFtoPlanar16F(&sourceBuffer, &destinationBuffer, 0) 22 | return output[0] 23 | } 24 | 25 | static func float16_to_float(value: UInt16) -> Float { 26 | var input: [UInt16] = [value] 27 | var output: [Float] = [0] 28 | var sourceBuffer = vImage_Buffer(data: &input, height: 1, width: 1, rowBytes: MemoryLayout.size) 29 | var destinationBuffer = vImage_Buffer(data: &output, height: 1, width: 1, rowBytes: MemoryLayout.size) 30 | vImageConvert_Planar16FtoPlanarF(&sourceBuffer, &destinationBuffer, 0) 31 | return output[0] 32 | } 33 | 34 | static func floats_to_float16s(values: [Float]) -> [UInt16] { 35 | var inputs = values 36 | var outputs = Array(repeating: 0, count: values.count) 37 | let width = vImagePixelCount(values.count) 38 | var sourceBuffer = vImage_Buffer(data: &inputs, height: 1, width: width, rowBytes: MemoryLayout.size * values.count) 39 | var destinationBuffer = vImage_Buffer(data: &outputs, height: 1, width: width, rowBytes: MemoryLayout.size * values.count) 40 | vImageConvert_PlanarFtoPlanar16F(&sourceBuffer, &destinationBuffer, 0) 41 | return outputs 42 | } 43 | 44 | static func float16s_to_floats(values: [UInt16]) -> [Float] { 45 | var inputs: [UInt16] = values 46 | var outputs: [Float] = Array(repeating: 0, count: values.count) 47 | let width = vImagePixelCount(values.count) 48 | var sourceBuffer = vImage_Buffer(data: &inputs, height: 1, width: width, rowBytes: MemoryLayout.size * values.count) 49 | var destinationBuffer = vImage_Buffer(data: &outputs, height: 1, width: width, rowBytes: MemoryLayout.size * values.count) 50 | vImageConvert_Planar16FtoPlanarF(&sourceBuffer, &destinationBuffer, 0) 51 | return outputs 52 | } 53 | 54 | init(_ value: Float) { 55 | self.rawValue = Float16.float_to_float16(value: value) 56 | } 57 | 58 | var floatValue: Float { 59 | return Float16.float16_to_float(value: self.rawValue) 60 | } 61 | 62 | var description: String { 63 | return floatValue.description 64 | } 65 | 66 | static func + (lhs: Float16, rhs: Float16) -> Float16 { 67 | return Float16(lhs.floatValue + rhs.floatValue) 68 | } 69 | 70 | static func - (lhs: Float16, rhs: Float16) -> Float16 { 71 | return Float16(lhs.floatValue - rhs.floatValue) 72 | } 73 | 74 | static func * (lhs: Float16, rhs: Float16) -> Float16 { 75 | return Float16(lhs.floatValue * rhs.floatValue) 76 | } 77 | 78 | static func / (lhs: Float16, rhs: Float16) -> Float16 { 79 | return Float16(lhs.floatValue / rhs.floatValue) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Silvershadow/GeoUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeoUtils.swift 3 | // ZKit 4 | // 5 | // Created by Kaz Yoshikawa on 1/4/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | import QuartzCore 12 | import GLKit 13 | import simd 14 | 15 | infix operator • 16 | infix operator × 17 | 18 | 19 | // MARK: - 20 | 21 | protocol FloatCovertible { 22 | var floatValue: Float { get } 23 | } 24 | 25 | extension CGFloat: FloatCovertible { 26 | var floatValue: Float { return Float(self) } 27 | } 28 | 29 | extension Int: FloatCovertible { 30 | var floatValue: Float { return Float(self) } 31 | } 32 | 33 | extension Float: FloatCovertible { 34 | var floatValue: Float { return self } 35 | } 36 | 37 | // MARK: - 38 | 39 | protocol CGFloatCovertible { 40 | var cgFloatValue: CGFloat { get } 41 | } 42 | 43 | extension CGFloat: CGFloatCovertible { 44 | var cgFloatValue: CGFloat { return self } 45 | } 46 | 47 | extension Int: CGFloatCovertible { 48 | var cgFloatValue: CGFloat { return CGFloat(self) } 49 | } 50 | 51 | extension Float: CGFloatCovertible { 52 | var cgFloatValue: CGFloat { return CGFloat(self) } 53 | } 54 | 55 | 56 | 57 | // MARK: - 58 | 59 | struct Point: Hashable, CustomStringConvertible { 60 | 61 | var x: Float 62 | var y: Float 63 | 64 | static func - (lhs: Point, rhs: Point) -> Point { 65 | return Point(x: lhs.x - rhs.x, y: lhs.y - rhs.y) 66 | } 67 | 68 | static func + (lhs: Point, rhs: Point) -> Point { 69 | return Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 70 | } 71 | 72 | static func * (lhs: Point, rhs: Float) -> Point { 73 | return Point(x: lhs.x * rhs, y: lhs.y * rhs) 74 | } 75 | 76 | static func / (lhs: Point, rhs: Float) -> Point { 77 | return Point(x: lhs.x / rhs, y: lhs.y / rhs) 78 | } 79 | 80 | static func • (lhs: Point, rhs: Point) -> Float { // dot product 81 | return lhs.x * rhs.x + lhs.y * rhs.y 82 | } 83 | 84 | static func × (lhs: Point, rhs: Point) -> Float { // cross product 85 | return lhs.x * rhs.y - lhs.y * rhs.x 86 | } 87 | 88 | init(_ x: X, _ y: Y) { 89 | self.x = x.floatValue 90 | self.y = y.floatValue 91 | } 92 | init(x: X, y: Y) { 93 | self.x = x.floatValue 94 | self.y = y.floatValue 95 | } 96 | init(_ point: CGPoint) { 97 | self.x = Float(point.x) 98 | self.y = Float(point.y) 99 | } 100 | 101 | var length²: Float { 102 | return (x * x) + (y * y) 103 | } 104 | 105 | var length: Float { 106 | return sqrt(self.length²) 107 | } 108 | 109 | var normalized: Point { 110 | let length = self.length 111 | return Point(x: x/length, y: y/length) 112 | } 113 | 114 | func angle(to: Point) -> Float { 115 | return atan2(to.y - self.y, to.x - self.x) 116 | } 117 | 118 | func angle(from: Point) -> Float { 119 | return atan2(self.y - from.y, self.x - from.x) 120 | } 121 | 122 | func hash(into hasher: inout Hasher) { 123 | hasher.combine(self.x) 124 | hasher.combine(self.y) 125 | } 126 | 127 | static func == (lhs: Point, rhs: Point) -> Bool { 128 | return lhs.x == rhs.y && lhs.y == rhs.y 129 | } 130 | 131 | var description: String { 132 | return "(x:\(x), y:\(y))" 133 | } 134 | } 135 | 136 | 137 | struct Size: CustomStringConvertible { 138 | var width: Float 139 | var height: Float 140 | 141 | init(_ width: W, _ height: H) { 142 | self.width = width.floatValue 143 | self.height = height.floatValue 144 | } 145 | 146 | init(width: W, height: H) { 147 | self.width = width.floatValue 148 | self.height = height.floatValue 149 | } 150 | init(_ size: CGSize) { 151 | self.width = Float(size.width) 152 | self.height = Float(size.height) 153 | } 154 | var description: String { 155 | return "(w:\(width), h:\(height))" 156 | } 157 | } 158 | 159 | 160 | struct Rect: CustomStringConvertible { 161 | var origin: Point 162 | var size: Size 163 | 164 | init(origin: Point, size: Size) { 165 | self.origin = origin; self.size = size 166 | } 167 | init(_ origin: Point, _ size: Size) { 168 | self.origin = origin; self.size = size 169 | } 170 | init(_ x: X, _ y: Y, _ width: W, _ height: H) { 171 | self.origin = Point(x: x, y: y) 172 | self.size = Size(width: width, height: height) 173 | } 174 | init(x: X, y: Y, width: W, height: H) { 175 | self.origin = Point(x: x, y: y) 176 | self.size = Size(width: width, height: height) 177 | } 178 | init(_ rect: CGRect) { 179 | self.origin = Point(rect.origin) 180 | self.size = Size(rect.size) 181 | } 182 | 183 | var minX: Float { return min(origin.x, origin.x + size.width) } 184 | var maxX: Float { return max(origin.x, origin.x + size.width) } 185 | var midX: Float { return (origin.x + origin.x + size.width) / 2.0 } 186 | var minY: Float { return min(origin.y, origin.y + size.height) } 187 | var maxY: Float { return max(origin.y, origin.y + size.height) } 188 | var midY: Float { return (origin.y + origin.y + size.height) / 2.0 } 189 | 190 | var cgRectValue: CGRect { return CGRect(x: CGFloat(origin.x), y: CGFloat(origin.y), width: CGFloat(size.width), height: CGFloat(size.height)) } 191 | var description: String { return "{Rect: (\(origin.x),\(origin.y))-(\(size.width), \(size.height))}" } 192 | } 193 | 194 | // MARK: - 195 | 196 | protocol PointConvertible { 197 | var pointValue: Point { get } 198 | } 199 | 200 | extension Point: PointConvertible { 201 | var pointValue: Point { return self } 202 | } 203 | 204 | extension CGPoint: PointConvertible { 205 | var pointValue: Point { return Point(self) } 206 | } 207 | 208 | 209 | // MARK: - 210 | 211 | extension CGPoint { 212 | 213 | init(_ point: Point) { 214 | self.init(x: CGFloat(point.x), y: CGFloat(point.y)) 215 | } 216 | 217 | static func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 218 | return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) 219 | } 220 | 221 | static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 222 | return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 223 | } 224 | 225 | static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint { 226 | return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs) 227 | } 228 | 229 | static func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint { 230 | return CGPoint(x: lhs.x / rhs, y: lhs.y / rhs) 231 | } 232 | 233 | static func • (lhs: CGPoint, rhs: CGPoint) -> CGFloat { // dot product 234 | return lhs.x * rhs.x + lhs.y * rhs.y 235 | } 236 | 237 | static func × (lhs: CGPoint, rhs: CGPoint) -> CGFloat { // cross product 238 | return lhs.x * rhs.y - lhs.y * rhs.x 239 | } 240 | 241 | var length²: CGFloat { 242 | return (x * x) + (y * y) 243 | } 244 | 245 | var length: CGFloat { 246 | return sqrt(self.length²) 247 | } 248 | 249 | var normalized: CGPoint { 250 | return self / length 251 | } 252 | } 253 | 254 | extension CGPoint { 255 | init(_ x: X, _ y: Y) { 256 | self.init() 257 | self.x = x.cgFloatValue 258 | self.y = y.cgFloatValue 259 | } 260 | } 261 | 262 | 263 | extension CGSize { 264 | init(_ size: Size) { 265 | self.init(width: CGFloat(size.width), height: CGFloat(size.height)) 266 | } 267 | 268 | init(_ width: W, _ height: H) { 269 | self.init() 270 | self.width = width.cgFloatValue 271 | self.height = height.cgFloatValue 272 | } 273 | } 274 | 275 | extension CGRect { 276 | init(_ rect: Rect) { 277 | self.init(origin: CGPoint(rect.origin), size: CGSize(rect.size)) 278 | } 279 | 280 | init(_ x: X, _ y: Y, _ width: W, _ height: H) { 281 | self.init() 282 | self.origin = CGPoint(x, y) 283 | self.size = CGSize(width, height) 284 | } 285 | } 286 | 287 | extension GLKMatrix4: CustomStringConvertible, Collection { 288 | public typealias Index = Int 289 | 290 | static let identity : GLKMatrix4 = GLKMatrix4Identity 291 | 292 | public var startIndex : Index { 293 | return 0 294 | } 295 | 296 | public var endIndex : Index { 297 | return 16 298 | } 299 | 300 | public func index(after i: Index) -> Index { 301 | return i + 1 302 | } 303 | 304 | init(_ transform: CGAffineTransform) { 305 | let t = CATransform3DMakeAffineTransform(transform) 306 | self.init(m: (Float(t.m11), Float(t.m12), Float(t.m13), Float(t.m14), 307 | Float(t.m21), Float(t.m22), Float(t.m23), Float(t.m24), 308 | Float(t.m31), Float(t.m32), Float(t.m33), Float(t.m34), 309 | Float(t.m41), Float(t.m42), Float(t.m43), Float(t.m44))) 310 | } 311 | 312 | var scaleFactor : Float { 313 | return sqrt(m00 * m00 + m01 * m01 + m02 * m02) 314 | } 315 | 316 | var invert: GLKMatrix4? { 317 | var invertible: Bool = true 318 | let t = GLKMatrix4Invert(self, &invertible) 319 | return invertible ? t : nil 320 | } 321 | 322 | public var description: String { 323 | return map { "\($0)" }.joined(separator: ",") 324 | } 325 | 326 | static func * (l: GLKMatrix4, r: GLKMatrix4) -> GLKMatrix4 { 327 | return GLKMatrix4Multiply(l, r) 328 | } 329 | } 330 | 331 | extension GLKVector2: CustomStringConvertible { 332 | init(_ point: CGPoint) { 333 | self.init(v: (Float(point.x), Float(point.y))) 334 | } 335 | public var description: String { 336 | return "[ \(self.x), \(self.y) ]" 337 | } 338 | static func + (l: GLKVector2, r: GLKVector2) -> GLKVector2 { 339 | return GLKVector2Add(l, r) 340 | } 341 | } 342 | 343 | 344 | extension GLKVector4: CustomStringConvertible { 345 | public var description: String { 346 | return "[ \(self.x), \(self.y), \(self.z), \(self.w) ]" 347 | } 348 | } 349 | 350 | 351 | func * (l: GLKMatrix4, r: GLKVector2) -> GLKVector2 { 352 | let vector4 = GLKMatrix4MultiplyVector4(l, GLKVector4Make(r.x, r.y, 0.0, 1.0)) 353 | return GLKVector2Make(vector4.x, vector4.y) 354 | } 355 | 356 | extension SIMD2 { 357 | init(_ vector: GLKVector2) { 358 | var value = SIMD2() 359 | value.x = vector.x as! Scalar 360 | value.y = vector.y as! Scalar 361 | self = value 362 | } 363 | } 364 | 365 | extension SIMD3 { 366 | init(_ vector: GLKVector3) { 367 | var value = SIMD3() 368 | value.x = vector.x as! Scalar 369 | value.y = vector.y as! Scalar 370 | value.z = vector.z as! Scalar 371 | self = value 372 | } 373 | } 374 | 375 | extension SIMD4 { 376 | init(_ vector: GLKVector4) { 377 | var value = SIMD4() 378 | value.x = vector.x as! Scalar 379 | value.y = vector.y as! Scalar 380 | value.z = vector.z as! Scalar 381 | value.w = vector.w as! Scalar 382 | self = value 383 | } 384 | } 385 | 386 | extension float2x2 { 387 | init(_ matrix: GLKMatrix2) { 388 | self = unsafeBitCast(matrix, to: float2x2.self) 389 | } 390 | } 391 | 392 | extension float3x3 { 393 | init(_ matrix: GLKMatrix3) { 394 | self = unsafeBitCast(matrix, to: float3x3.self) 395 | } 396 | } 397 | 398 | extension float4x4 { 399 | init(_ matrix: GLKMatrix4) { 400 | self = unsafeBitCast(matrix, to: float4x4.self) 401 | } 402 | } 403 | 404 | extension MTLClearColor : Equatable { 405 | static let Red = MTLClearColor(red: 1, green: 0, blue: 0, alpha: 0) 406 | 407 | init(color: XRGBA) { 408 | self.init(red: Double(color.r), 409 | green: Double(color.g), 410 | blue: Double(color.b), 411 | alpha: Double(color.a)) 412 | } 413 | 414 | public static func ==(lhs: MTLClearColor, rhs: MTLClearColor) -> Bool { 415 | return lhs.red == rhs.red && lhs.green == rhs.green && 416 | lhs.blue == rhs.blue && lhs.alpha == rhs.alpha 417 | } 418 | } 419 | 420 | -------------------------------------------------------------------------------- /Silvershadow/ImageRenderable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageRenderable.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 1/11/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | import GLKit 12 | 13 | 14 | // 15 | // ImageNode 16 | // 17 | 18 | class ImageRenderable: Renderable { 19 | 20 | typealias RendererType = ImageRenderer 21 | 22 | let device: MTLDevice 23 | var transform : GLKMatrix4 = .identity 24 | 25 | var image: XImage 26 | var frame: Rect 27 | let texture: MTLTexture 28 | 29 | lazy var vertexBuffer: VertexBuffer = { 30 | let vertexes = self.renderer.vertices(for: self.frame) 31 | return self.renderer.vertexBuffer(for: vertexes)! 32 | }() 33 | 34 | init?(device: MTLDevice, image: XImage, frame: Rect) { 35 | guard let texture = device.texture(of: image) else { return nil } 36 | self.device = device 37 | self.image = image 38 | self.frame = frame 39 | self.texture = texture 40 | } 41 | 42 | func render(context: RenderContext) { 43 | self.renderer.renderTexture(context: context, texture: texture, in: frame) 44 | } 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Silvershadow/ImageRenderer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageRenderer.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/22/15. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | import MetalKit 12 | import GLKit 13 | 14 | typealias ImageVertex = ImageRenderer.Vertex 15 | 16 | // 17 | // ImageRenderer 18 | // 19 | 20 | class ImageRenderer: Renderer { 21 | 22 | typealias VertexType = Vertex 23 | 24 | // MARK: - 25 | 26 | struct Vertex { 27 | var x, y, z, w, u, v: Float 28 | var padding: SIMD2 29 | init(x: Float, y: Float, z: Float, w: Float, u: Float, v: Float) { 30 | self.x = x 31 | self.y = y 32 | self.z = z 33 | self.w = w 34 | self.u = u 35 | self.v = v 36 | self.padding = .init() 37 | } 38 | } 39 | 40 | struct Uniforms { 41 | var transform: GLKMatrix4 42 | } 43 | 44 | 45 | let device: MTLDevice 46 | 47 | 48 | required init(device: MTLDevice) { 49 | self.device = device 50 | } 51 | 52 | func vertices(for rect: Rect) -> [Vertex] { 53 | let (l, r, t, b) = (rect.minX, rect.maxX, rect.maxY, rect.minY) 54 | 55 | // vertex (y) texture (v) 56 | // 1---4 (1) a---d (0) 57 | // | | | | 58 | // 2---3 (0) b---c (1) 59 | // 60 | 61 | return [ 62 | Vertex(x: l, y: t, z: 0, w: 1, u: 0, v: 0), // 1, a 63 | Vertex(x: l, y: b, z: 0, w: 1, u: 0, v: 1), // 2, b 64 | Vertex(x: r, y: b, z: 0, w: 1, u: 1, v: 1), // 3, c 65 | 66 | Vertex(x: l, y: t, z: 0, w: 1, u: 0, v: 0), // 1, a 67 | Vertex(x: r, y: b, z: 0, w: 1, u: 1, v: 1), // 3, c 68 | Vertex(x: r, y: t, z: 0, w: 1, u: 1, v: 0), // 4, d 69 | ] 70 | } 71 | 72 | var vertexDescriptor: MTLVertexDescriptor { 73 | let vertexDescriptor = MTLVertexDescriptor() 74 | vertexDescriptor.attributes[0].offset = 0 75 | vertexDescriptor.attributes[0].format = .float4 76 | vertexDescriptor.attributes[0].bufferIndex = 0 77 | 78 | vertexDescriptor.attributes[1].offset = 0 79 | vertexDescriptor.attributes[1].format = .float2 80 | vertexDescriptor.attributes[1].bufferIndex = 0 81 | 82 | vertexDescriptor.layouts[0].stepFunction = .perVertex 83 | vertexDescriptor.layouts[0].stride = MemoryLayout.size 84 | return vertexDescriptor 85 | } 86 | 87 | lazy var renderPipelineState: MTLRenderPipelineState = { 88 | let renderPipelineDescriptor = MTLRenderPipelineDescriptor() 89 | renderPipelineDescriptor.vertexDescriptor = self.vertexDescriptor 90 | renderPipelineDescriptor.vertexFunction = self.library.makeFunction(name: "image_vertex")! 91 | renderPipelineDescriptor.fragmentFunction = self.library.makeFunction(name: "image_fragment")! 92 | 93 | renderPipelineDescriptor.colorAttachments[0].pixelFormat = .`default` 94 | renderPipelineDescriptor.colorAttachments[0].isBlendingEnabled = true 95 | renderPipelineDescriptor.colorAttachments[0].rgbBlendOperation = .add 96 | renderPipelineDescriptor.colorAttachments[0].alphaBlendOperation = .add 97 | 98 | renderPipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = .one 99 | renderPipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .one 100 | renderPipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha 101 | renderPipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha 102 | 103 | 104 | return try! self.device.makeRenderPipelineState(descriptor: renderPipelineDescriptor) 105 | }() 106 | 107 | lazy var colorSamplerState: MTLSamplerState = { 108 | return self.device.makeSamplerState(descriptor: .`default`)! 109 | }() 110 | 111 | func vertexBuffer(for vertices: [Vertex]) -> VertexBuffer? { 112 | return VertexBuffer(device: device, vertices: vertices) 113 | } 114 | 115 | func vertexBuffer(for rect: Rect) -> VertexBuffer? { 116 | return VertexBuffer(device: device, vertices: vertices(for: rect)) 117 | } 118 | 119 | func texture(of image: XImage) -> MTLTexture? { 120 | guard let cgImage: CGImage = image.cgImage else { return nil } 121 | var options: [String : NSObject] = [convertFromMTKTextureLoaderOption(MTKTextureLoader.Option.SRGB): false as NSNumber] 122 | if #available(iOS 10.0, *) { 123 | options[convertFromMTKTextureLoaderOption(MTKTextureLoader.Option.origin)] = true as NSNumber 124 | } 125 | return try? device.textureLoader.newTexture(cgImage: cgImage, options: convertToOptionalMTKTextureLoaderOptionDictionary(options)) 126 | } 127 | 128 | // MARK: - 129 | 130 | // prepare triple reusable buffers for avoid race condition 131 | lazy var uniformTripleBuffer: [MTLBuffer] = { 132 | return [ 133 | self.device.makeBuffer(length: MemoryLayout.size, options: [.storageModeShared])!, 134 | self.device.makeBuffer(length: MemoryLayout.size, options: [.storageModeShared])!, 135 | self.device.makeBuffer(length: MemoryLayout.size, options: [.storageModeShared])! 136 | ] 137 | }() 138 | 139 | let rectangularVertexCount = 6 140 | 141 | lazy var rectangularVertexTripleBuffer: [VertexBuffer] = { 142 | let count = self.rectangularVertexCount 143 | return [ 144 | VertexBuffer(device: self.device, vertices: [], capacity: count), 145 | VertexBuffer(device: self.device, vertices: [], capacity: count), 146 | VertexBuffer(device: self.device, vertices: [], capacity: count) 147 | ] 148 | }() 149 | 150 | var tripleBufferIndex = 0 151 | 152 | func renderTexture(context: RenderContext, texture: MTLTexture, in rect: Rect) { 153 | defer { tripleBufferIndex = (tripleBufferIndex + 1) % uniformTripleBuffer.count } 154 | 155 | let uniformsBuffer = uniformTripleBuffer[tripleBufferIndex] 156 | let uniformsBufferPtr = UnsafeMutablePointer(OpaquePointer(uniformsBuffer.contents())) 157 | uniformsBufferPtr.pointee.transform = context.transform 158 | 159 | let vertices = self.vertices(for: rect) 160 | let vertexBuffer = rectangularVertexTripleBuffer[tripleBufferIndex] 161 | vertexBuffer.set(vertices) 162 | 163 | let commandBuffer = context.makeCommandBuffer() 164 | let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: context.renderPassDescriptor)! 165 | encoder.pushDebugGroup("image") 166 | encoder.setRenderPipelineState(self.renderPipelineState) 167 | 168 | encoder.setFrontFacing(.clockwise) 169 | // commandEncoder.setCullMode(.back) 170 | encoder.setVertexBuffer(vertexBuffer.buffer, offset: 0, index: 0) 171 | encoder.setVertexBuffer(uniformsBuffer, offset: 0, index: 1) 172 | 173 | encoder.setFragmentTexture(texture, index: 0) 174 | encoder.setFragmentSamplerState(self.colorSamplerState, index: 0) 175 | 176 | encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertexBuffer.count) 177 | encoder.popDebugGroup() 178 | encoder.endEncoding() 179 | 180 | commandBuffer.commit() 181 | } 182 | } 183 | 184 | 185 | extension RenderContext { 186 | 187 | func render(texture: MTLTexture?, in rect: Rect) { 188 | guard let texture = texture else { return } 189 | let renderer = self.device.renderer() as ImageRenderer 190 | renderer.renderTexture(context: self, texture: texture, in: rect) 191 | } 192 | 193 | func render(image: XImage?, in rect: Rect) { 194 | guard let image = image else { return } 195 | let device = self.device 196 | guard let texture = device.texture(of: image) else { return } 197 | self.render(texture: texture, in: rect) 198 | } 199 | 200 | } 201 | 202 | 203 | // Helper function inserted by Swift 4.2 migrator. 204 | fileprivate func convertFromMTKTextureLoaderOption(_ input: MTKTextureLoader.Option) -> String { 205 | return input.rawValue 206 | } 207 | 208 | // Helper function inserted by Swift 4.2 migrator. 209 | fileprivate func convertToOptionalMTKTextureLoaderOptionDictionary(_ input: [String: Any]?) -> [MTKTextureLoader.Option: Any]? { 210 | guard let input = input else { return nil } 211 | return Dictionary(uniqueKeysWithValues: input.map { key, value in (MTKTextureLoader.Option(rawValue: key), value)}) 212 | } 213 | -------------------------------------------------------------------------------- /Silvershadow/ImageShaders.metal: -------------------------------------------------------------------------------- 1 | // 2 | // ImageShaders.metal 3 | // SilverShadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/22/15. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | #include 10 | using namespace metal; 11 | 12 | 13 | struct VertexIn { 14 | float4 position [[ attribute(0) ]]; 15 | float2 texcoords [[ attribute(1) ]]; 16 | float2 padding; 17 | }; 18 | 19 | struct VertexOut { 20 | float4 position [[ position ]]; 21 | float2 texcoords; 22 | }; 23 | 24 | struct Uniforms { 25 | float4x4 transform; 26 | }; 27 | 28 | vertex VertexOut image_vertex( 29 | const device VertexIn * vertices [[ buffer(0) ]], 30 | constant Uniforms & uniforms [[ buffer(1) ]], 31 | uint vid [[ vertex_id ]] 32 | ) { 33 | VertexOut outVertex; 34 | VertexIn inVertex = vertices[vid]; 35 | outVertex.position = uniforms.transform * float4(inVertex.position); 36 | outVertex.texcoords = inVertex.texcoords; 37 | return outVertex; 38 | } 39 | 40 | fragment float4 image_fragment( 41 | VertexOut vertexIn [[ stage_in ]], 42 | texture2d colorTexture [[ texture(0) ]], 43 | sampler colorSampler [[ sampler(0) ]] 44 | ) { 45 | return colorTexture.sample(colorSampler, vertexIn.texcoords).rgba; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Silvershadow/Kernel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Kernel.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/30/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | 12 | protocol Kernel: class { 13 | var device: MTLDevice { get } 14 | init(device: MTLDevice) 15 | } 16 | 17 | 18 | extension Kernel { 19 | var library: MTLLibrary { 20 | return self.device.makeDefaultLibrary()! 21 | } 22 | } 23 | 24 | final class KernelRegistry : DictLike { } 25 | 26 | final class KernelMap : NSMapTable { 27 | static let shared = KernelMap.weakToStrongObjects() 28 | } 29 | 30 | extension MTLDevice { 31 | 32 | func kernel() -> T { 33 | let key = NSStringFromClass(T.self) 34 | let registry = KernelMap.shared.object(forKey: self) ?? KernelRegistry() 35 | let renderer = registry[key] ?? T(device: self) 36 | registry[key] = renderer 37 | KernelMap.shared.setObject(registry, forKey: self) 38 | return renderer as! T 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Silvershadow/MTLDevice+Z.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MTLDevice+Z.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 1/10/17. 6 | // Copyright © 2017 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | 12 | extension MTLDevice { 13 | 14 | var textureLoader: MTKTextureLoader { 15 | return MTKTextureLoader(device: self) 16 | } 17 | 18 | func texture(of image: CGImage) -> MTLTexture? { 19 | 20 | let textureUsage : MTLTextureUsage = [.pixelFormatView, .shaderRead] 21 | var options: [String : NSObject] = [ 22 | convertFromMTKTextureLoaderOption(MTKTextureLoader.Option.SRGB): false as NSNumber, 23 | convertFromMTKTextureLoaderOption(MTKTextureLoader.Option.textureUsage): textureUsage.rawValue as NSNumber 24 | ] 25 | if #available(iOS 10.0, *) { 26 | options[convertFromMTKTextureLoaderOption(MTKTextureLoader.Option.origin)] = true as NSNumber 27 | } 28 | 29 | guard let texture = try? self.textureLoader.newTexture(cgImage: image, options: convertToOptionalMTKTextureLoaderOptionDictionary(options)) else { return nil } 30 | 31 | if texture.pixelFormat == .bgra8Unorm { return texture } 32 | else { return texture.makeTextureView(pixelFormat: .bgra8Unorm) } 33 | } 34 | 35 | func texture(of image: XImage) -> MTLTexture? { 36 | return image.cgImage.flatMap { self.texture(of: $0) } 37 | } 38 | 39 | func texture(named name: String) -> MTLTexture? { 40 | var options = [String: NSObject]() 41 | options[convertFromMTKTextureLoaderOption(MTKTextureLoader.Option.SRGB)] = false as NSNumber 42 | if #available(iOS 10.0, *) { 43 | options[convertFromMTKTextureLoaderOption(MTKTextureLoader.Option.origin)] = convertFromMTKTextureLoaderOrigin(MTKTextureLoader.Origin.topLeft) as NSObject 44 | } 45 | do { return try self.textureLoader.newTexture(name: name, scaleFactor: 1.0, bundle: nil, options: convertToOptionalMTKTextureLoaderOptionDictionary(options)) } 46 | catch { fatalError("\(error)") } 47 | } 48 | 49 | #if os(iOS) 50 | func makeHeap(size: Int) -> MTLHeap { 51 | let descriptor = MTLHeapDescriptor() 52 | descriptor.storageMode = .shared 53 | descriptor.size = size 54 | return self.makeHeap(descriptor: descriptor)! 55 | } 56 | #endif 57 | } 58 | 59 | // Helper function inserted by Swift 4.2 migrator. 60 | fileprivate func convertFromMTKTextureLoaderOption(_ input: MTKTextureLoader.Option) -> String { 61 | return input.rawValue 62 | } 63 | 64 | // Helper function inserted by Swift 4.2 migrator. 65 | fileprivate func convertToOptionalMTKTextureLoaderOptionDictionary(_ input: [String: Any]?) -> [MTKTextureLoader.Option: Any]? { 66 | guard let input = input else { return nil } 67 | return Dictionary(uniqueKeysWithValues: input.map { key, value in (MTKTextureLoader.Option(rawValue: key), value)}) 68 | } 69 | 70 | // Helper function inserted by Swift 4.2 migrator. 71 | fileprivate func convertFromMTKTextureLoaderOrigin(_ input: MTKTextureLoader.Origin) -> String { 72 | return input.rawValue 73 | } 74 | -------------------------------------------------------------------------------- /Silvershadow/MTLHeap+Z.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 2/28/17. 6 | // Copyright © 2017 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | 12 | 13 | #if os(iOS) 14 | extension MTLHeap { 15 | 16 | func makeBuffer(bytes pointer: UnsafeRawPointer, length: Int, options: MTLResourceOptions = [.storageModeShared]) -> MTLBuffer { 17 | let buffer = self.makeBuffer(length: length, options: options)! 18 | let destinationArrayPtr = UnsafeMutableRawPointer(OpaquePointer(buffer.contents())) 19 | destinationArrayPtr.copyMemory(from: pointer, byteCount: length) 20 | return buffer 21 | } 22 | 23 | } 24 | #endif 25 | -------------------------------------------------------------------------------- /Silvershadow/MetalBuffer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VertexBuffer.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 1/11/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | 12 | 13 | #if os(iOS) 14 | typealias XHeap = MTLHeap 15 | #elseif os(macOS) 16 | typealias XHeap = MTLDevice 17 | #endif 18 | 19 | 20 | // 21 | // VertexBuffer 22 | // 23 | 24 | class MetalBuffer { 25 | 26 | let heap: XHeap 27 | var buffer: MTLBuffer 28 | var count: Int 29 | var capacity: Int 30 | 31 | init(heap: XHeap, vertices: [T]? = nil, capacity: Int? = nil) { 32 | self.heap = heap 33 | let count = vertices?.count ?? 0 34 | let capacity = capacity ?? count 35 | assert(capacity > 0) 36 | let length = MemoryLayout.stride * capacity 37 | self.count = count 38 | self.capacity = capacity 39 | let buffer = self.heap.makeBuffer(length: length, options: [.storageModeShared])! 40 | if let vertices = vertices { 41 | let destinationArrayPtr = UnsafeMutablePointer(OpaquePointer(buffer.contents())) 42 | let destinationArray = UnsafeMutableBufferPointer(start: destinationArrayPtr, count: vertices.count) 43 | (0 ..< vertices.count).forEach { destinationArray[$0] = vertices[$0] } 44 | } 45 | self.buffer = buffer 46 | } 47 | 48 | deinit { 49 | // buffer.setPurgeableState(.empty) 50 | } 51 | 52 | func append(_ items: [T]) { 53 | assert(buffer.storageMode == .shared) 54 | if self.count + items.count < self.capacity { 55 | let vertexArray = UnsafeMutablePointer(OpaquePointer(self.buffer.contents())) 56 | for index in 0 ..< items.count { 57 | vertexArray[self.count + index] = items[index] 58 | } 59 | self.count += items.count 60 | } 61 | else { 62 | let count = self.count 63 | let length = MemoryLayout.stride * (count + items.count) 64 | let buffer = self.heap.makeBuffer(length: length, options: [.storageModeShared])! 65 | let sourceArrayPtr = UnsafeMutablePointer(OpaquePointer(self.buffer.contents())) 66 | let sourceArray = UnsafeMutableBufferPointer(start: sourceArrayPtr, count: count) 67 | let destinationArrayPtr = UnsafeMutablePointer(OpaquePointer(buffer.contents())) 68 | let destinationArray = UnsafeMutableBufferPointer(start: destinationArrayPtr, count: count + items.count) 69 | 70 | (0 ..< count).forEach { destinationArray[$0] = sourceArray[$0] } 71 | (0 ..< items.count).forEach { destinationArray[count + $0] = items[$0] } 72 | 73 | self.count = count + items.count 74 | self.capacity = self.count 75 | 76 | self.buffer = buffer 77 | } 78 | } 79 | 80 | func set(_ items: [T]) { 81 | assert(buffer.storageMode == .shared) 82 | if items.count < self.capacity { 83 | let destinationArrayPtr = UnsafeMutablePointer(OpaquePointer(buffer.contents())) 84 | let destinationArray = UnsafeMutableBufferPointer(start: destinationArrayPtr, count: count + items.count) 85 | (0 ..< items.count).forEach { destinationArray[$0] = items[$0] } 86 | self.count = items.count 87 | } 88 | else { 89 | let bytes = MemoryLayout.size * items.count 90 | #if os(iOS) 91 | let buffer = self.heap.makeBuffer(bytes: items, length: bytes, options: [.storageModeShared]) 92 | #elseif os(macOS) 93 | let buffer = self.heap.makeBuffer(bytes: items, length: bytes, options: [.storageModeShared])! 94 | #endif 95 | self.count = items.count 96 | self.capacity = items.count 97 | self.buffer = buffer 98 | } 99 | } 100 | 101 | var items: [T] { 102 | let vertexArray = UnsafeMutablePointer(OpaquePointer(self.buffer.contents())) 103 | return (0 ..< count).map { vertexArray[$0] } 104 | } 105 | 106 | subscript(index: Int) -> T { 107 | get { 108 | assert(buffer.storageMode == .shared) 109 | guard index < self.count else { fatalError("Buffer Overrun") } 110 | let itemsArray = UnsafeMutablePointer(OpaquePointer(self.buffer.contents())) 111 | return itemsArray[index] 112 | } 113 | set { 114 | assert(buffer.storageMode == .shared) 115 | guard index < self.count else { fatalError("Buffer Overrun") } 116 | let itemsArray = UnsafeMutablePointer(OpaquePointer(self.buffer.contents())) 117 | itemsArray[index] = newValue 118 | } 119 | } 120 | 121 | var item: T { 122 | get { return self[0] } 123 | set { self[0] = newValue } 124 | } 125 | 126 | } 127 | 128 | -------------------------------------------------------------------------------- /Silvershadow/PatternRenderer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageRenderer.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/22/15. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | import MetalKit 12 | import GLKit 13 | import simd 14 | 15 | 16 | // 17 | // ImageRenderer 18 | // 19 | 20 | class PatternRenderer: Renderer { 21 | 22 | typealias VertexType = Vertex 23 | 24 | // MARK: - 25 | 26 | struct Vertex { 27 | var x, y, z, w, u, v: Float 28 | var padding: SIMD2 29 | init(x: Float, y: Float, z: Float, w: Float, u: Float, v: Float) { 30 | self.x = x 31 | self.y = y 32 | self.z = z 33 | self.w = w 34 | self.u = u 35 | self.v = v 36 | self.padding = .init() 37 | } 38 | } 39 | 40 | struct Uniforms { 41 | var transform: GLKMatrix4 42 | var contentSize: SIMD2 43 | var patternSize: SIMD2 44 | } 45 | 46 | let device: MTLDevice 47 | 48 | 49 | required init(device: MTLDevice) { 50 | self.device = device 51 | } 52 | 53 | func vertices(for rect: Rect) -> [Vertex] { 54 | let (l, r, t, b) = (rect.minX, rect.maxX, rect.maxY, rect.minY) 55 | 56 | // vertex (y) texture (v) 57 | // 1---4 (1) a---d (0) 58 | // | | | | 59 | // 2---3 (0) b---c (1) 60 | // 61 | 62 | return [ 63 | Vertex(x: l, y: t, z: 0, w: 1, u: 0, v: 0), // 1, a 64 | Vertex(x: l, y: b, z: 0, w: 1, u: 0, v: 1), // 2, b 65 | Vertex(x: r, y: b, z: 0, w: 1, u: 1, v: 1), // 3, c 66 | 67 | Vertex(x: l, y: t, z: 0, w: 1, u: 0, v: 0), // 1, a 68 | Vertex(x: r, y: b, z: 0, w: 1, u: 1, v: 1), // 3, c 69 | Vertex(x: r, y: t, z: 0, w: 1, u: 1, v: 0), // 4, d 70 | ] 71 | } 72 | 73 | var vertexDescriptor: MTLVertexDescriptor { 74 | let vertexDescriptor = MTLVertexDescriptor() 75 | vertexDescriptor.attributes[0].offset = 0 76 | vertexDescriptor.attributes[0].format = .float4 77 | vertexDescriptor.attributes[0].bufferIndex = 0 78 | 79 | vertexDescriptor.attributes[1].offset = 0 80 | vertexDescriptor.attributes[1].format = .float2 81 | vertexDescriptor.attributes[1].bufferIndex = 0 82 | 83 | vertexDescriptor.layouts[0].stepFunction = .perVertex 84 | vertexDescriptor.layouts[0].stride = MemoryLayout.size 85 | return vertexDescriptor 86 | } 87 | 88 | lazy var renderPipelineState: MTLRenderPipelineState = { 89 | let renderPipelineDescriptor = MTLRenderPipelineDescriptor() 90 | renderPipelineDescriptor.vertexDescriptor = self.vertexDescriptor 91 | renderPipelineDescriptor.vertexFunction = self.library.makeFunction(name: "pattern_vertex")! 92 | renderPipelineDescriptor.fragmentFunction = self.library.makeFunction(name: "pattern_fragment")! 93 | 94 | renderPipelineDescriptor.colorAttachments[0].pixelFormat = .`default` 95 | renderPipelineDescriptor.colorAttachments[0].isBlendingEnabled = true 96 | renderPipelineDescriptor.colorAttachments[0].rgbBlendOperation = .add 97 | renderPipelineDescriptor.colorAttachments[0].alphaBlendOperation = .add 98 | 99 | renderPipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = .one 100 | renderPipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .one 101 | renderPipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha 102 | renderPipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha 103 | 104 | return try! self.device.makeRenderPipelineState(descriptor: renderPipelineDescriptor) 105 | }() 106 | 107 | lazy var shadingSamplerState: MTLSamplerState = { 108 | return self.device.makeSamplerState(descriptor: .`default`)! 109 | }() 110 | 111 | lazy var patternSamplerState: MTLSamplerState = { 112 | return self.device.makeSamplerState(descriptor: .`default`)! 113 | }() 114 | 115 | func vertexBuffer(for vertices: [Vertex]) -> VertexBuffer? { 116 | return VertexBuffer(device: device, vertices: vertices) 117 | } 118 | 119 | func vertexBuffer(for rect: Rect) -> VertexBuffer? { 120 | return VertexBuffer(device: device, vertices: vertices(for: rect)) 121 | } 122 | 123 | func texture(of image: XImage) -> MTLTexture? { 124 | guard let cgImage: CGImage = image.cgImage else { return nil } 125 | var options: [String : NSObject] = [convertFromMTKTextureLoaderOption(MTKTextureLoader.Option.SRGB): false as NSNumber] 126 | if #available(iOS 10.0, *) { 127 | options[convertFromMTKTextureLoaderOption(MTKTextureLoader.Option.origin)] = true as NSNumber 128 | } 129 | return try? device.textureLoader.newTexture(cgImage: cgImage, options: convertToOptionalMTKTextureLoaderOptionDictionary(options)) 130 | } 131 | 132 | // MARK: - 133 | 134 | 135 | // prepare triple reusable buffers for avoid race condition 136 | 137 | lazy var uniformTripleBuffer: [MTLBuffer] = { 138 | return [ 139 | self.device.makeBuffer(length: MemoryLayout.size, options: [.storageModeShared])!, 140 | self.device.makeBuffer(length: MemoryLayout.size, options: [.storageModeShared])!, 141 | self.device.makeBuffer(length: MemoryLayout.size, options: [.storageModeShared])! 142 | ] 143 | }() 144 | 145 | let rectangularVertexCount = 6 146 | 147 | lazy var rectVertexTripleBuffer: [MTLBuffer] = { 148 | let len = MemoryLayout.size * self.rectangularVertexCount 149 | return [ 150 | self.device.makeBuffer(length: len, options: [.storageModeShared])!, 151 | self.device.makeBuffer(length: len, options: [.storageModeShared])!, 152 | self.device.makeBuffer(length: len, options: [.storageModeShared])! 153 | ] 154 | }() 155 | 156 | var tripleBufferIndex = 0 157 | 158 | func renderPattern(context: RenderContext, in rect: Rect) { 159 | defer { tripleBufferIndex = (tripleBufferIndex + 1) % uniformTripleBuffer.count } 160 | 161 | let uniformsBuffer = uniformTripleBuffer[tripleBufferIndex] 162 | let uniformsBufferPtr = UnsafeMutablePointer(OpaquePointer(uniformsBuffer.contents())) 163 | uniformsBufferPtr.pointee.transform = context.transform 164 | 165 | uniformsBufferPtr.pointee.contentSize = SIMD2(Float(context.deviceSize.width), Float(context.deviceSize.height)) 166 | uniformsBufferPtr.pointee.patternSize = SIMD2(Float(context.brushPattern.width), Float(context.brushPattern.height)) 167 | 168 | let vertexes = self.vertices(for: rect) 169 | assert(vertexes.count == rectangularVertexCount) 170 | let vertexBuffer = rectVertexTripleBuffer[tripleBufferIndex] 171 | let vertexArrayPtr = UnsafeMutablePointer(OpaquePointer(vertexBuffer.contents())) 172 | let vertexArray = UnsafeMutableBufferPointer(start: vertexArrayPtr, count: vertexes.count) 173 | (0 ..< vertexes.count).forEach { vertexArray[$0] = vertexes[$0] } 174 | 175 | let commandBuffer = context.makeCommandBuffer() 176 | let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: context.renderPassDescriptor)! 177 | encoder.pushDebugGroup("pattern filling") 178 | 179 | encoder.setRenderPipelineState(self.renderPipelineState) 180 | 181 | encoder.setFrontFacing(.clockwise) 182 | // commandEncoder.setCullMode(.back) 183 | encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) 184 | encoder.setVertexBuffer(uniformsBuffer, offset: 0, index: 1) 185 | 186 | encoder.setFragmentTexture(context.shadingTexture, index: 0) 187 | encoder.setFragmentTexture(context.brushPattern, index: 1) 188 | encoder.setFragmentSamplerState(self.shadingSamplerState, index: 0) 189 | encoder.setFragmentSamplerState(self.patternSamplerState, index: 1) 190 | encoder.setFragmentBuffer(uniformsBuffer, offset: 0, index: 0) 191 | 192 | encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertexes.count) 193 | 194 | encoder.popDebugGroup() 195 | encoder.endEncoding() 196 | commandBuffer.commit() 197 | } 198 | } 199 | 200 | 201 | // Helper function inserted by Swift 4.2 migrator. 202 | fileprivate func convertFromMTKTextureLoaderOption(_ input: MTKTextureLoader.Option) -> String { 203 | return input.rawValue 204 | } 205 | 206 | // Helper function inserted by Swift 4.2 migrator. 207 | fileprivate func convertToOptionalMTKTextureLoaderOptionDictionary(_ input: [String: Any]?) -> [MTKTextureLoader.Option: Any]? { 208 | guard let input = input else { return nil } 209 | return Dictionary(uniqueKeysWithValues: input.map { key, value in (MTKTextureLoader.Option(rawValue: key), value)}) 210 | } 211 | -------------------------------------------------------------------------------- /Silvershadow/PatternShaders.metal: -------------------------------------------------------------------------------- 1 | // 2 | // PatternShaders.metal 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/22/15. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | #include 10 | using namespace metal; 11 | 12 | 13 | 14 | struct VertexIn { 15 | float4 position [[ attribute(0) ]]; 16 | float2 texcoords [[ attribute(1) ]]; 17 | float2 padding; 18 | }; 19 | 20 | struct VertexOut { 21 | float4 position [[ position ]]; 22 | float2 texcoords; 23 | }; 24 | 25 | #define FragmentIn VertexOut 26 | 27 | struct Uniforms { 28 | float4x4 transform; 29 | float2 contentSize; 30 | float2 patternSize; 31 | }; 32 | 33 | vertex VertexOut pattern_vertex( 34 | const device VertexIn * vertices [[ buffer(0) ]], 35 | constant Uniforms & uniforms [[ buffer(1) ]], 36 | uint vid [[ vertex_id ]] 37 | ) { 38 | VertexOut outVertex; 39 | VertexIn inVertex = vertices[vid]; 40 | outVertex.position = uniforms.transform * float4(inVertex.position); 41 | outVertex.texcoords = inVertex.texcoords; 42 | return outVertex; 43 | } 44 | 45 | fragment float4 pattern_fragment( 46 | FragmentIn fragmentIn [[ stage_in ]], 47 | texture2d shadingTexture [[ texture(0) ]], 48 | sampler shadingSampler [[ sampler(0) ]], 49 | texture2d patternTexture [[ texture(1) ]], 50 | sampler patternSampler [[ sampler(1) ]], 51 | constant Uniforms & uniforms [[ buffer(0) ]] 52 | ) { 53 | 54 | float4 shape = shadingTexture.sample(shadingSampler, fragmentIn.texcoords).a; 55 | float2 ratio = uniforms.contentSize / uniforms.patternSize; 56 | float4 patternColor = patternTexture.sample(patternSampler, (fragmentIn.position * uniforms.transform).xy * ratio); 57 | if (patternColor.a * shape.a > 0.0) { 58 | return float4(patternColor.rgb, patternColor.a * shape.a); 59 | } 60 | else { 61 | return float4(0); 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Silvershadow/PointsRenderable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PointsRenderable.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 1/11/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | import GLKit 12 | 13 | 14 | // 15 | // PointsRenderable 16 | // 17 | 18 | class PointsRenderable: Renderable { 19 | 20 | typealias RendererType = PointsRenderer 21 | 22 | let device: MTLDevice 23 | var texture: MTLTexture 24 | var vertices: [PointVertex] 25 | 26 | lazy var vertexBuffer: VertexBuffer = { 27 | return self.renderer.vertexBuffer(for: self.vertices, capacity: 4096) 28 | }() 29 | 30 | init?(device: MTLDevice, texture: MTLTexture, vertices: [PointVertex]) { 31 | self.device = device 32 | self.texture = texture 33 | self.vertices = vertices 34 | } 35 | 36 | init?(device: MTLDevice, texture: MTLTexture, cgPath: CGPath, width: CGFloat) { 37 | self.device = device 38 | self.texture = texture 39 | self.vertices = PointsRenderer.vertexes(of: cgPath, width: width) 40 | } 41 | 42 | 43 | func render(context: RenderContext) { 44 | renderer.render(context: context, texture: texture, vertexBuffer: vertexBuffer) 45 | } 46 | 47 | func append(_ vertices: [PointVertex]) { 48 | self.vertices += vertices 49 | if self.vertices.count < vertexBuffer.count { 50 | self.vertexBuffer.append(vertices) 51 | } 52 | else { 53 | let vertexBuffer = renderer.vertexBuffer(for: self.vertices, capacity: 4096) 54 | self.vertexBuffer = vertexBuffer 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Silvershadow/PointsRenderer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PointsRenderer.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 1/11/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | import MetalKit 12 | import QuartzCore 13 | import GLKit 14 | 15 | typealias PointVertex = PointsRenderer.Vertex 16 | 17 | extension MTLSamplerDescriptor { 18 | 19 | convenience init(min: MTLSamplerMinMagFilter, 20 | max: MTLSamplerMinMagFilter, 21 | s: MTLSamplerAddressMode, 22 | t: MTLSamplerAddressMode) { 23 | self.init() 24 | minFilter = min 25 | magFilter = max 26 | sAddressMode = s 27 | tAddressMode = t 28 | } 29 | 30 | static let `default` = MTLSamplerDescriptor(min: .nearest, 31 | max: .linear, 32 | s: .repeat, 33 | t: .repeat) 34 | } 35 | 36 | // 37 | // PointsRenderer 38 | // 39 | 40 | class PointsRenderer: Renderer { 41 | 42 | typealias VertexType = Vertex 43 | 44 | // TODO: needs refactoring 45 | 46 | struct Vertex { 47 | var x: Float 48 | var y: Float 49 | var width: Float 50 | var unused: Float = 0 51 | 52 | init(x: Float, y: Float, width: Float) { 53 | self.x = x 54 | self.y = y 55 | self.width = width 56 | } 57 | 58 | init(_ point: Point, _ width: Float) { 59 | self.x = point.x 60 | self.y = point.y 61 | self.width = width 62 | } 63 | 64 | } 65 | 66 | struct Uniforms { 67 | var transform: GLKMatrix4 68 | var zoomScale: Float 69 | var unused2: Float = 0 70 | var unused3: Float = 0 71 | var unused4: Float = 0 72 | 73 | init(transform: GLKMatrix4, zoomScale: Float) { 74 | self.transform = transform 75 | self.zoomScale = zoomScale 76 | } 77 | } 78 | 79 | let device: MTLDevice 80 | 81 | 82 | // MARK: - 83 | 84 | required init(device: MTLDevice) { 85 | self.device = device 86 | } 87 | 88 | var library: MTLLibrary { 89 | return self.device.makeDefaultLibrary()! 90 | } 91 | 92 | var vertexDescriptor: MTLVertexDescriptor { 93 | let vertexDescriptor = MTLVertexDescriptor() 94 | vertexDescriptor.attributes[0].offset = 0 95 | vertexDescriptor.attributes[0].format = .float2 96 | vertexDescriptor.attributes[0].bufferIndex = 0 97 | 98 | vertexDescriptor.attributes[1].offset = MemoryLayout>.size 99 | vertexDescriptor.attributes[1].format = .float2 100 | vertexDescriptor.attributes[1].bufferIndex = 0 101 | 102 | vertexDescriptor.layouts[0].stepFunction = .perVertex 103 | vertexDescriptor.layouts[0].stride = MemoryLayout.size 104 | 105 | return vertexDescriptor 106 | } 107 | 108 | lazy var renderPipelineState: MTLRenderPipelineState = { 109 | let renderPipelineDescriptor = MTLRenderPipelineDescriptor() 110 | renderPipelineDescriptor.vertexDescriptor = self.vertexDescriptor 111 | renderPipelineDescriptor.vertexFunction = self.library.makeFunction(name: "points_vertex")! 112 | renderPipelineDescriptor.fragmentFunction = self.library.makeFunction(name: "points_fragment")! 113 | 114 | renderPipelineDescriptor.colorAttachments[0].pixelFormat = .`default` 115 | renderPipelineDescriptor.colorAttachments[0].isBlendingEnabled = true 116 | renderPipelineDescriptor.colorAttachments[0].rgbBlendOperation = .add 117 | renderPipelineDescriptor.colorAttachments[0].alphaBlendOperation = .add 118 | 119 | // I don't believe this but this is what it is... 120 | renderPipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = .one 121 | renderPipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .one 122 | renderPipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha 123 | renderPipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha 124 | 125 | return try! self.device.makeRenderPipelineState(descriptor: renderPipelineDescriptor) 126 | }() 127 | 128 | lazy var colorSamplerState: MTLSamplerState = { 129 | return self.device.makeSamplerState(descriptor: .`default`)! 130 | }() 131 | 132 | func vertexBuffer(for vertices: [Vertex], capacity: Int? = nil) -> VertexBuffer { 133 | return VertexBuffer(device: self.device, vertices: vertices, capacity: capacity) 134 | } 135 | 136 | func render(context: RenderContext, texture: MTLTexture, vertexBuffer: VertexBuffer) { 137 | let transform = context.transform 138 | var uniforms = Uniforms(transform: transform, zoomScale: Float(context.zoomScale)) 139 | let uniformsBuffer = device.makeBuffer(bytes: &uniforms, length: MemoryLayout.size, options: []) 140 | 141 | let commandBuffer = context.makeCommandBuffer() 142 | let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: context.renderPassDescriptor)! 143 | encoder.setRenderPipelineState(self.renderPipelineState) 144 | 145 | encoder.setVertexBuffer(vertexBuffer.buffer, offset: 0, index: 0) 146 | encoder.setVertexBuffer(uniformsBuffer, offset: 0, index: 1) 147 | 148 | encoder.setFragmentTexture(texture, index: 0) 149 | encoder.setFragmentSamplerState(self.colorSamplerState, index: 0) 150 | 151 | encoder.drawPrimitives(type: .point, vertexStart: 0, vertexCount: vertexBuffer.count) 152 | encoder.endEncoding() 153 | commandBuffer.commit() 154 | } 155 | 156 | func render(context: RenderContext, texture: MTLTexture, vertexes: [Vertex]) { 157 | let vertexBuffer = self.vertexBuffer(for: vertexes) 158 | self.render(context: context, texture: texture, vertexBuffer: vertexBuffer) 159 | } 160 | 161 | func render(context: RenderContext, texture: MTLTexture, points: [Point], width: Float) { 162 | var vertexes = [Vertex]() 163 | points.pair { (p1, p2) in 164 | vertexes += self.vertexes(from: p1, to: p2, width: width) 165 | } 166 | self.render(context: context, texture: texture, vertexes: vertexes) 167 | } 168 | 169 | func vertexes(from: Point, to: Point, width: Float) -> [Vertex] { 170 | let vector = (to - from) 171 | let numberOfPoints = Int(ceil(vector.length / 2)) 172 | let step = vector / Float(numberOfPoints) 173 | return (0 ..< numberOfPoints).map { 174 | Vertex(from + step * Float($0), width) 175 | } 176 | } 177 | 178 | class func vertexes(of cgPath: CGPath, width: CGFloat) -> [Vertex] { 179 | var vertexes = [Vertex]() 180 | var startPoint: CGPoint? 181 | var lastPoint: CGPoint? 182 | 183 | for pathElement in cgPath.pathElements { 184 | switch pathElement { 185 | case let .moveTo(p1): 186 | startPoint = p1 187 | lastPoint = p1 188 | 189 | case let .lineTo(p1): 190 | guard let p0 = lastPoint else { continue } 191 | lastPoint = p1 192 | 193 | let n = Int((p1 - p0).length) 194 | for i in 0 ..< n { 195 | let t = CGFloat(i) / CGFloat(n) 196 | let q = p0 + (p1 - p0) * t 197 | vertexes.append(Vertex(Point(q), Float(width))) 198 | } 199 | 200 | case let .quadCurveTo(p1, p2): 201 | guard let p0 = lastPoint else { continue } 202 | lastPoint = p2 203 | 204 | let n = Int(ceil(CGPath.quadraticCurveLength(p0, p1, p2))) 205 | for i in 0 ..< n { 206 | let t = CGFloat(i) / CGFloat(n) 207 | let q1 = p0 + (p1 - p0) * t 208 | let q2 = p1 + (p2 - p1) * t 209 | let r = q1 + (q2 - q1) * t 210 | vertexes.append(Vertex(Point(r), Float(width))) 211 | } 212 | 213 | case let .curveTo(p1, p2, p3): 214 | guard let p0 = lastPoint else { continue } 215 | lastPoint = p3 216 | 217 | let n = Int(ceil(CGPath.approximateCubicCurveLength(p0, p1, p2, p3))) 218 | for i in 0 ..< n { 219 | let t = CGFloat(i) / CGFloat(n) 220 | let q1 = p0 + (p1 - p0) * t 221 | let q2 = p1 + (p2 - p1) * t 222 | let q3 = p2 + (p3 - p2) * t 223 | let r1 = q1 + (q2 - q1) * t 224 | let r2 = q2 + (q3 - q2) * t 225 | let s = r1 + (r2 - r1) * t 226 | vertexes.append(Vertex(Point(s), Float(width))) 227 | } 228 | 229 | case .closeSubpath: 230 | guard let p0 = lastPoint, let p1 = startPoint else { continue } 231 | 232 | let n = Int((p1 - p0).length) 233 | for i in 0 ..< n { 234 | let t = CGFloat(i) / CGFloat(n) 235 | let q = p0 + (p1 - p0) * t 236 | vertexes.append(Vertex(Point(q), Float(width))) 237 | } 238 | } 239 | } 240 | 241 | return vertexes 242 | } 243 | 244 | } 245 | 246 | 247 | extension RenderContext { 248 | 249 | func render(vertexes: [PointVertex], texture: MTLTexture) { 250 | let renderer: PointsRenderer = self.device.renderer() 251 | let vertexBuffer = renderer.vertexBuffer(for: vertexes) 252 | renderer.render(context: self, texture: texture, vertexBuffer: vertexBuffer) 253 | } 254 | 255 | func render(points: [Point], texture: MTLTexture, width: Float) { 256 | let renderer: PointsRenderer = self.device.renderer() 257 | renderer.render(context: self, texture: texture, points: points, width: width) 258 | } 259 | 260 | } 261 | 262 | -------------------------------------------------------------------------------- /Silvershadow/PointsShaders.metal: -------------------------------------------------------------------------------- 1 | // 2 | // PointsShaders.metal 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 1/11/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | #include 10 | 11 | using namespace metal; 12 | float4x4 invert(float4x4 matrix); 13 | 14 | struct VertexIn { 15 | float2 position [[ attribute(0) ]]; 16 | float2 attributes [[ attribute(1) ]]; 17 | }; 18 | 19 | struct VertexOut { 20 | float4 position [[ position ]]; 21 | float pointSize [[ point_size ]]; 22 | }; 23 | 24 | struct Uniforms { 25 | float4x4 transform; 26 | float zoomScale; 27 | float unused2; 28 | float unused3; 29 | float unused4; 30 | }; 31 | 32 | vertex VertexOut points_vertex( 33 | const device VertexIn * vertices [[ buffer(0) ]], 34 | constant Uniforms & uniforms [[ buffer(1) ]], 35 | uint vid [[ vertex_id ]] 36 | ) { 37 | VertexIn inVertex = vertices[vid]; 38 | VertexOut outVertex; 39 | outVertex.position = uniforms.transform * float4(inVertex.position, 0.0, 1.0); 40 | float pointWidth = inVertex.attributes[0]; 41 | outVertex.pointSize = pointWidth * uniforms.zoomScale; 42 | return outVertex; 43 | } 44 | 45 | fragment float4 points_fragment( 46 | VertexOut vertexIn [[ stage_in ]], 47 | texture2d colorTexture [[ texture(0) ]], 48 | sampler colorSampler [[ sampler(0) ]], 49 | float2 texcoord [[ point_coord ]] 50 | ) { 51 | 52 | float4 color = colorTexture.sample(colorSampler, texcoord); 53 | if (color.a < 0.1) { 54 | discard_fragment(); 55 | } 56 | return color; 57 | } 58 | -------------------------------------------------------------------------------- /Silvershadow/RenderCanvasContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenderCanvasContext.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 2/19/17. 6 | // Copyright © 2017 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | 12 | 13 | /* 14 | class RenderCanvasContext: RenderContext { 15 | 16 | // var bounds: Rect 17 | var shadingTexture: MTLTexture 18 | 19 | lazy var brushShape: MTLTexture = { 20 | return self.device.texture(of: XImage(named: "Particle")!)! 21 | }() 22 | 23 | lazy var brushPattern: MTLTexture = { 24 | return self.device.texture(of: XImage(named: "Pencil")!)! 25 | }() 26 | 27 | init( 28 | renderPassDescriptor: MTLRenderPassDescriptor, 29 | commandQueue: MTLCommandQueue, 30 | transform: GLKMatrix4, 31 | zoomScale: CGFloat, 32 | bounds: CGRect, 33 | shadingTexture: MTLTexture 34 | ) { 35 | // self.bounds = Rect(bounds) 36 | self.shadingTexture = shadingTexture 37 | super.init(renderPassDescriptor: renderPassDescriptor, commandQueue: commandQueue, transform: transform, zoomScale: zoomScale) 38 | } 39 | 40 | } 41 | */ 42 | -------------------------------------------------------------------------------- /Silvershadow/RenderContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenderContentView.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/12/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import Cocoa 13 | #endif 14 | 15 | class RenderContentView: XView { 16 | 17 | weak var renderView: RenderView? 18 | 19 | var contentSize: CGSize? { 20 | return self.renderView?.scene?.contentSize 21 | } 22 | 23 | override func draw(_ rect: CGRect) { 24 | super.draw(rect) 25 | } 26 | 27 | #if os(iOS) 28 | override func layoutSubviews() { 29 | super.layoutSubviews() 30 | } 31 | 32 | #elseif os(macOS) 33 | override func layout() { 34 | super.layout() 35 | } 36 | 37 | override var isFlipped: Bool { 38 | return true 39 | } 40 | #endif 41 | 42 | 43 | // MARK: - 44 | 45 | #if os(iOS) 46 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 47 | renderView?.scene?.touchesBegan(touches, with: event) 48 | } 49 | 50 | override func touchesMoved(_ touches: Set, with event: UIEvent?) { 51 | renderView?.scene?.touchesMoved(touches, with: event) 52 | } 53 | 54 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 55 | renderView?.scene?.touchesEnded(touches, with: event) 56 | } 57 | 58 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 59 | renderView?.scene?.touchesCancelled(touches, with: event) 60 | } 61 | #elseif os(macOS) 62 | 63 | override func mouseDown(with event: NSEvent) { 64 | renderView?.scene?.mouseDown(with: event) 65 | } 66 | 67 | override func mouseMoved(with event: NSEvent) { 68 | renderView?.scene?.mouseMoved(with: event) 69 | } 70 | 71 | override func mouseDragged(with event: NSEvent) { 72 | renderView?.scene?.mouseDragged(with: event) 73 | } 74 | 75 | override func mouseUp(with event: NSEvent) { 76 | renderView?.scene?.mouseUp(with: event) 77 | } 78 | #endif 79 | 80 | } 81 | -------------------------------------------------------------------------------- /Silvershadow/RenderContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenderContext.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/12/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | import GLKit 12 | 13 | // 14 | // RenderContextState 15 | // 16 | 17 | struct RenderContextState { 18 | var renderPassDescriptor: MTLRenderPassDescriptor 19 | var commandQueue: MTLCommandQueue 20 | var contentSize: CGSize 21 | var deviceSize: CGSize // eg. MTKView's size, offscreen bitmap's size etc. 22 | var transform: GLKMatrix4 23 | var zoomScale: CGFloat 24 | } 25 | 26 | struct Stack { 27 | private var content : [Element] 28 | 29 | init() { 30 | content = [] 31 | } 32 | 33 | mutating 34 | func push(_ element: Element) { 35 | content.append(element) 36 | } 37 | 38 | mutating 39 | func pop() -> Element? { 40 | guard let l = content.last else { return nil } 41 | defer { 42 | content.removeLast() 43 | } 44 | return l 45 | } 46 | } 47 | // 48 | // RenderContext 49 | // 50 | 51 | class RenderContext { 52 | var current: RenderContextState 53 | private var contextStack = Stack() 54 | 55 | var renderPassDescriptor: MTLRenderPassDescriptor { 56 | get { return current.renderPassDescriptor } 57 | set { current.renderPassDescriptor = newValue } 58 | } 59 | var commandQueue: MTLCommandQueue { 60 | get { return current.commandQueue } 61 | set { self.current.commandQueue = newValue } 62 | } 63 | var contentSize: CGSize { 64 | get { return current.contentSize } 65 | set { self.current.contentSize = newValue } 66 | } 67 | var deviceSize: CGSize { // eg. MTKView's size, offscreen bitmap's size etc. 68 | get { return current.deviceSize } 69 | set { self.current.deviceSize = newValue } 70 | } 71 | var transform: GLKMatrix4 { 72 | get { return current.transform } 73 | set { self.current.transform = newValue } 74 | } 75 | var zoomScale: CGFloat { 76 | get { return current.zoomScale } 77 | set {} 78 | } 79 | 80 | var device: MTLDevice { return commandQueue.device } 81 | 82 | // 83 | 84 | lazy var shadingTexture: MTLTexture = { 85 | let descriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .`default`, 86 | width: Int(self.deviceSize.width), height: Int(self.deviceSize.height), mipmapped: false) 87 | descriptor.usage = [.shaderRead, .renderTarget] 88 | return self.device.makeTexture(descriptor: descriptor)! 89 | }() 90 | 91 | lazy var brushShape: MTLTexture = { 92 | return self.device.texture(of: XImage(named: "Particle")!)! 93 | }() 94 | 95 | lazy var brushPattern: MTLTexture = { 96 | return self.device.texture(of: XImage(named: "Pencil")!)! 97 | }() 98 | 99 | init( 100 | renderPassDescriptor: MTLRenderPassDescriptor, 101 | commandQueue: MTLCommandQueue, 102 | contentSize: CGSize, 103 | deviceSize: CGSize, 104 | transform: GLKMatrix4, 105 | zoomScale: CGFloat = 1 106 | ) { 107 | self.current = RenderContextState( 108 | renderPassDescriptor: renderPassDescriptor, commandQueue: commandQueue, 109 | contentSize: contentSize, deviceSize: deviceSize, transform: transform, zoomScale: zoomScale) 110 | } 111 | 112 | func makeCommandBuffer() -> MTLCommandBuffer { 113 | return commandQueue.makeCommandBuffer()! 114 | } 115 | 116 | // MARK: - 117 | 118 | func pushContext() { 119 | let copiedState = self.current 120 | let copiedRenderpassDescriptor = self.current.renderPassDescriptor.copy() as! MTLRenderPassDescriptor 121 | self.current.renderPassDescriptor = copiedRenderpassDescriptor 122 | self.contextStack.push(copiedState) 123 | } 124 | 125 | func popContext() { 126 | guard let current = contextStack.pop() else { fatalError("cannot pop") } 127 | self.current = current 128 | } 129 | } 130 | 131 | extension RenderContext { 132 | 133 | func widthCGContext(_ closure: (CGContext) -> ()) { 134 | let (width, height, bytesPerRow) = (Int(contentSize.width), Int(contentSize.height), Int(contentSize.width) * 4) 135 | let colorSpace = CGColorSpaceCreateDeviceRGB() 136 | let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) 137 | guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, 138 | space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else { return } 139 | context.clear(CGRect(size:contentSize)) 140 | 141 | let transform = CGAffineTransform.identity 142 | .translatedBy(x: 0, y: contentSize.height) 143 | .scaledBy(x: 1, y: -1) 144 | context.concatenate(transform) 145 | context.saveGState() 146 | 147 | #if os(iOS) 148 | UIGraphicsPushContext(context) 149 | #elseif os(macOS) 150 | let savedContext = NSGraphicsContext.current 151 | let graphicsContext = NSGraphicsContext(cgContext: context, flipped: false) 152 | NSGraphicsContext.current = graphicsContext 153 | #endif 154 | 155 | closure(context) 156 | 157 | #if os(iOS) 158 | UIGraphicsPopContext() 159 | #elseif os(macOS) 160 | NSGraphicsContext.current = savedContext 161 | #endif 162 | 163 | context.restoreGState() 164 | guard let cgImage = context.makeImage() else { fatalError("failed creating cgImage") } 165 | guard let texture = self.device.texture(of: cgImage) else { fatalError("failed creating texture") } 166 | self.render(texture: texture, in: Rect(0, 0, width, height)) 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /Silvershadow/RenderDrawView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenderDrawView.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/12/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import Cocoa 13 | #endif 14 | 15 | class RenderDrawView: XView { 16 | 17 | var renderView: RenderView? 18 | 19 | var contentView: RenderContentView? { 20 | return renderView?.contentView 21 | } 22 | 23 | #if os(iOS) 24 | override func layoutSubviews() { 25 | super.layoutSubviews() 26 | assert(renderView != nil) 27 | } 28 | 29 | override func draw(_ layer: CALayer, in context: CGContext) { 30 | self.draw(in : context) 31 | } 32 | 33 | override func setNeedsDisplay() { 34 | self.layer.setNeedsDisplay() 35 | super.setNeedsDisplay() 36 | } 37 | 38 | #elseif os(macOS) 39 | override func layout() { 40 | super.layout() 41 | self.wantsLayer = true 42 | self.layer?.backgroundColor = .clear 43 | } 44 | 45 | override func draw(_ dirtyRect: NSRect) { 46 | CGContext.current.map { self.draw(in: $0) } 47 | } 48 | 49 | override var isFlipped: Bool { 50 | return true 51 | } 52 | 53 | #endif 54 | 55 | // MARK: - 56 | 57 | func draw(in context: CGContext) { 58 | guard let contentView = contentView, let scene = renderView?.scene else { return } 59 | 60 | let target = contentView.convert(contentView.bounds, to: self) 61 | let transform = scene.bounds.transform(to: target) 62 | context.concatenate(transform) 63 | context.saveGState() 64 | 65 | #if os(iOS) 66 | UIGraphicsPushContext(context) 67 | #endif 68 | 69 | scene.draw(in: context) 70 | 71 | #if os(iOS) 72 | UIGraphicsPopContext() 73 | #endif 74 | 75 | context.restoreGState() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Silvershadow/RenderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenderView.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/12/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import Cocoa 13 | #endif 14 | 15 | import MetalKit 16 | import GLKit 17 | 18 | class RenderView: XView, MTKViewDelegate { 19 | 20 | var scene: Scene? { 21 | didSet { 22 | if scene !== oldValue { 23 | if let scene = scene { 24 | self.mtkView.device = scene.device 25 | self.commandQueue = scene.device.makeCommandQueue() 26 | scene.didMove(to: self) 27 | } 28 | self.setNeedsLayout() // implies adjusting document 29 | } 30 | } 31 | } 32 | 33 | #if os(iOS) 34 | override func layoutSubviews() { 35 | super.layoutSubviews() 36 | 37 | self.sendSubviewToBack(self.mtkView) 38 | self.bringSubviewToFront(self.drawView) 39 | self.bringSubviewToFront(self.scrollView) 40 | 41 | if let scene = self.scene { 42 | let contentSize = scene.contentSize 43 | self.scrollView.contentSize = contentSize 44 | // self.contentView.bounds = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height) 45 | let bounds = CGRect(size: contentSize) 46 | let frame = self.scrollView.convert(bounds, to: self.contentView) 47 | self.contentView.frame = frame 48 | } 49 | else { 50 | self.scrollView.contentSize = self.bounds.size 51 | self.contentView.bounds = self.bounds 52 | } 53 | self.scrollView.autoresizesSubviews = false; 54 | self.contentView.translatesAutoresizingMaskIntoConstraints = false 55 | self.contentView.autoresizingMask = [] 56 | self.contentView.autoresizingMask = [.flexibleRightMargin, .flexibleBottomMargin] 57 | self.setNeedsDisplay() 58 | } 59 | 60 | #elseif os(macOS) 61 | override func layout() { 62 | super.layout() 63 | 64 | self.sendSubview(toBack: self.mtkView) 65 | self.bringSubview(toFront: self.drawView) 66 | self.bringSubview(toFront: self.scrollView) 67 | 68 | let contentSize = scene?.contentSize ?? bounds.size 69 | 70 | self.scrollView.documentView?.frame = CGRect(size: contentSize) 71 | 72 | self.contentView.translatesAutoresizingMaskIntoConstraints = false 73 | self.contentView.autoresizingMask = [NSView.AutoresizingMask.maxXMargin, /*.viewMinYMargin,*/ NSView.AutoresizingMask.maxYMargin] 74 | self.setNeedsDisplay() 75 | } 76 | #endif 77 | 78 | private (set) lazy var mtkView: MTKView = { 79 | let mtkView = MTKView(frame: self.bounds) 80 | mtkView.device = MTLCreateSystemDefaultDevice()! 81 | mtkView.colorPixelFormat = .`default` 82 | mtkView.delegate = self 83 | self.addSubviewToFit(mtkView) 84 | mtkView.enableSetNeedsDisplay = true 85 | // mtkView.isPaused = true 86 | return mtkView 87 | }() 88 | 89 | #if os(iOS) 90 | private (set) lazy var scrollView: UIScrollView = { 91 | let scrollView = UIScrollView(frame: self.bounds) 92 | scrollView.delegate = self 93 | scrollView.backgroundColor = UIColor.clear 94 | scrollView.maximumZoomScale = 4.0 95 | scrollView.minimumZoomScale = 1.0 96 | scrollView.autoresizesSubviews = false 97 | scrollView.delaysContentTouches = false 98 | scrollView.panGestureRecognizer.minimumNumberOfTouches = 2 99 | self.addSubviewToFit(scrollView) 100 | scrollView.addSubview(self.contentView) 101 | self.contentView.frame = self.bounds 102 | return scrollView 103 | }() 104 | 105 | #elseif os(macOS) 106 | private (set) lazy var scrollView: NSScrollView = { 107 | let scrollView = NSScrollView(frame: self.bounds) 108 | scrollView.hasVerticalScroller = true 109 | scrollView.hasHorizontalScroller = true 110 | scrollView.borderType = .noBorder 111 | scrollView.drawsBackground = false 112 | // scrollView.autoresingMask = [.flexibleWidth, .flexibleHeight] 113 | self.addSubviewToFit(scrollView) 114 | 115 | // isFlipped cannot be set, then replace clipView with subclass it does 116 | let clipView = FlippedClipView(frame: self.contentView.frame) 117 | clipView.drawsBackground = false 118 | clipView.backgroundColor = .clear 119 | 120 | scrollView.contentView = clipView // scrollView's contentView is NSClipView 121 | scrollView.documentView = self.contentView 122 | scrollView.contentView.postsBoundsChangedNotifications = true 123 | 124 | // posting notification when zoomed, scrolled or resized 125 | 126 | NotificationCenter.default.addObserver(self, selector: #selector(RenderView.scrollContentDidChange), 127 | name: NSView.boundsDidChangeNotification, object: nil) 128 | scrollView.allowsMagnification = true 129 | scrollView.maxMagnification = 4 130 | scrollView.minMagnification = 1 131 | 132 | return scrollView 133 | }() 134 | 135 | 136 | var lastCall = Date() 137 | 138 | @objc func scrollContentDidChange(_ notification: Notification) { 139 | Swift.print("since lastCall = \(-lastCall.timeIntervalSinceNow * 1000) ms") 140 | self.lastCall = Date() 141 | // self.drawView.setNeedsDisplay() 142 | self.mtkView.setNeedsDisplay() 143 | } 144 | #endif 145 | 146 | private (set) lazy var drawView: RenderDrawView = { 147 | let drawView = RenderDrawView(frame: self.bounds) 148 | drawView.backgroundColor = .clear 149 | drawView.renderView = self 150 | self.addSubviewToFit(drawView) 151 | return drawView 152 | }() 153 | 154 | private (set) lazy var contentView: RenderContentView = { 155 | let renderableContentView = RenderContentView(frame: self.bounds) 156 | renderableContentView.renderView = self 157 | renderableContentView.backgroundColor = .clear 158 | renderableContentView.translatesAutoresizingMaskIntoConstraints = false 159 | #if os(iOS) 160 | renderableContentView.isUserInteractionEnabled = true 161 | #endif 162 | return renderableContentView 163 | }() 164 | 165 | var device: MTLDevice { 166 | return self.mtkView.device! 167 | } 168 | 169 | private (set) var commandQueue: MTLCommandQueue? 170 | 171 | // MARK: - 172 | 173 | #if os(iOS) 174 | override func setNeedsDisplay() { 175 | super.setNeedsDisplay() 176 | self.mtkView.setNeedsDisplay() 177 | self.drawView.setNeedsDisplay() 178 | } 179 | #endif 180 | 181 | #if os(macOS) 182 | override var needsDisplay: Bool { 183 | get { return super.needsDisplay } 184 | set { 185 | self.mtkView.setNeedsDisplay() 186 | self.drawView.setNeedsDisplay() 187 | super.needsDisplay = newValue 188 | } 189 | } 190 | 191 | override var isFlipped: Bool { 192 | return true 193 | } 194 | #endif 195 | 196 | // MARK: - 197 | 198 | let semaphore = DispatchSemaphore(value: 1) 199 | 200 | func draw(in view: MTKView) { 201 | 202 | let date = Date() 203 | 204 | self.semaphore.wait() 205 | defer { self.semaphore.signal() } 206 | 207 | self.drawView.setNeedsDisplay() 208 | 209 | guard let drawable = self.mtkView.currentDrawable, 210 | let renderPassDescriptor = self.mtkView.currentRenderPassDescriptor, 211 | let scene = self.scene, 212 | let commandQueue = self.commandQueue else { return } 213 | 214 | let rgba = self.scene?.backgroundColor.rgba ?? XRGBA(r: 0.9, g: 0.9, b: 0.9, a: 1.0) 215 | 216 | renderPassDescriptor.colorAttachments[0].texture = drawable.texture // error on simulator target 217 | renderPassDescriptor.colorAttachments[0].clearColor = .init(color: rgba) 218 | renderPassDescriptor.colorAttachments[0].loadAction = .clear 219 | renderPassDescriptor.colorAttachments[0].storeAction = .store 220 | 221 | // just for clearing screen 222 | do { 223 | let commandBuffer = commandQueue.makeCommandBuffer()! 224 | let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)! 225 | commandEncoder.endEncoding() 226 | commandBuffer.commit() 227 | } 228 | 229 | // setup render context 230 | let transform = GLKMatrix4(self.drawingTransform) 231 | renderPassDescriptor.colorAttachments[0].loadAction = .load 232 | let renderContext = RenderContext( 233 | renderPassDescriptor: renderPassDescriptor, commandQueue: commandQueue, 234 | contentSize: scene.contentSize, deviceSize: self.mtkView.drawableSize, 235 | transform: transform, zoomScale: self.zoomScale) 236 | 237 | // actual rendering 238 | scene.render(in: renderContext) 239 | 240 | do { 241 | let commandBuffer = commandQueue.makeCommandBuffer()! 242 | commandBuffer.present(drawable) 243 | commandBuffer.commit() 244 | } 245 | } 246 | 247 | var zoomScale: CGFloat { 248 | return scrollView.zoomScale 249 | } 250 | 251 | var drawingTransform: CGAffineTransform { 252 | guard let scene = self.scene else { return .identity } 253 | let targetRect = contentView.convert(self.contentView.bounds, to: self.mtkView) 254 | let transform0 = CGAffineTransform(translationX: 0, y: self.contentView.bounds.height).scaledBy(x: 1, y: -1) 255 | let transform1 = scene.bounds.transform(to: targetRect) 256 | let transform2 = self.mtkView.bounds.transform(to: CGRect(x: -1.0, y: -1.0, width: 2.0, height: 2.0)) 257 | let transform3 = CGAffineTransform.identity.translatedBy(x: 0, y: +1).scaledBy(x: 1, y: -1).translatedBy(x: 0, y: 1) 258 | #if os(iOS) 259 | let transform = transform1 * transform2 * transform3 260 | #elseif os(macOS) 261 | let transform = transform0 * transform1 * transform2 262 | #endif 263 | return transform 264 | } 265 | 266 | func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { 267 | } 268 | 269 | // MARK: - 270 | 271 | #if os(iOS) 272 | var minimumNumberOfTouchesToScroll: Int { 273 | get { return self.scrollView.panGestureRecognizer.minimumNumberOfTouches } 274 | set { self.scrollView.panGestureRecognizer.minimumNumberOfTouches = newValue } 275 | } 276 | 277 | var scrollEnabled: Bool { 278 | get { return self.scrollView.isScrollEnabled } 279 | set { self.scrollView.isScrollEnabled = newValue } 280 | } 281 | 282 | var delaysContentTouches: Bool { 283 | get { return self.scrollView.delaysContentTouches } 284 | set { self.scrollView.delaysContentTouches = newValue } 285 | } 286 | #endif 287 | } 288 | 289 | #if os(iOS) 290 | extension RenderView: UIScrollViewDelegate { 291 | 292 | func viewForZooming(in scrollView: UIScrollView) -> UIView? { 293 | return self.contentView 294 | } 295 | 296 | func scrollViewDidZoom(_ scrollView: UIScrollView) { 297 | self.setNeedsDisplay() 298 | } 299 | 300 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 301 | self.setNeedsDisplay() 302 | } 303 | 304 | } 305 | #endif 306 | 307 | #if os(macOS) 308 | class FlippedClipView: NSClipView { 309 | 310 | override var isFlipped: Bool { return true } 311 | 312 | } 313 | #endif 314 | -------------------------------------------------------------------------------- /Silvershadow/Renderable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Renderable.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/12/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import MetalKit 10 | 11 | protocol Renderable: class { 12 | 13 | associatedtype RendererType: Renderer 14 | var device: MTLDevice { get } 15 | var renderer: RendererType { get } 16 | func render(context: RenderContext) 17 | 18 | } 19 | 20 | 21 | extension Renderable { 22 | var renderer: RendererType { 23 | let renderer = self.device.renderer() as RendererType 24 | return renderer 25 | } 26 | } 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Silvershadow/Renderer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Renderer.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/12/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | 10 | import MetalKit 11 | 12 | extension MTLPixelFormat { 13 | static let `default` : MTLPixelFormat = .bgra8Unorm 14 | } 15 | 16 | protocol Renderer: class { 17 | var device: MTLDevice { get } 18 | init(device: MTLDevice) 19 | } 20 | 21 | 22 | extension Renderer { 23 | var library: MTLLibrary { 24 | return self.device.makeDefaultLibrary()! 25 | } 26 | } 27 | 28 | 29 | class DictLike { 30 | private var content : [Key: Value] = [:] 31 | subscript(key: Key) -> Value? { 32 | get { return content[key] } 33 | set { content[key] = newValue } 34 | } 35 | } 36 | 37 | final class RendererRegistry : DictLike { } 38 | 39 | func aligned(length: Int, alignment: Int) -> Int { 40 | return length + ((alignment - (length % alignment)) % alignment) 41 | } 42 | 43 | final class RenderMap : NSMapTable { 44 | static let shared = RenderMap.weakToStrongObjects() 45 | } 46 | 47 | extension MTLDevice { 48 | 49 | func renderer() -> T { 50 | let key = NSStringFromClass(T.self) 51 | let registry = RenderMap.shared.object(forKey: self) ?? RendererRegistry() 52 | let renderer = registry[key] ?? T(device: self) 53 | registry[key] = renderer 54 | RenderMap.shared.setObject(registry, forKey: self) 55 | return renderer as! T 56 | } 57 | 58 | } 59 | 60 | -------------------------------------------------------------------------------- /Silvershadow/Scene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RenderableContent.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 12/12/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | 12 | class Scene { 13 | 14 | let device: MTLDevice 15 | var contentSize: CGSize 16 | 17 | var backgroundColor: XColor = .white { 18 | didSet { self.setNeedsDisplay() } 19 | } 20 | 21 | var width: CGFloat { return contentSize.width } 22 | var height: CGFloat { return contentSize.height } 23 | 24 | weak var renderView: RenderView? 25 | 26 | var bounds: CGRect { 27 | return CGRect(size: contentSize) 28 | } 29 | 30 | init?(device: MTLDevice, contentSize: CGSize) { 31 | self.device = device 32 | self.contentSize = contentSize 33 | } 34 | 35 | func didMove(to renderView: RenderView) { 36 | self.renderView = renderView 37 | } 38 | 39 | var commandQueue: MTLCommandQueue? { 40 | return self.renderView?.commandQueue 41 | } 42 | 43 | private (set) var beingUpdated: Bool = false 44 | 45 | func setNeedsUpdate() { 46 | guard !self.beingUpdated else { return } 47 | self.beingUpdated = true 48 | if let semaphore = self.renderView?.semaphore { 49 | semaphore.wait() 50 | defer { semaphore.signal() } 51 | self.update() 52 | } 53 | else { 54 | self.update() 55 | } 56 | self.beingUpdated = false 57 | } 58 | 59 | func setNeedsDisplay() { 60 | self.renderView?.setNeedsDisplay() 61 | } 62 | 63 | var mipmapped: Bool { 64 | return false 65 | } 66 | 67 | var transform: CGAffineTransform { 68 | return bounds.transform(to: CGRect(-1, -1, 2, 2)) 69 | } 70 | 71 | // MARK: - 72 | 73 | func update() { 74 | } 75 | 76 | func draw(in context: CGContext) { 77 | } 78 | 79 | func render(in context: RenderContext) { 80 | } 81 | 82 | #if os(iOS) 83 | func locationInScene(_ touch: UITouch) -> CGPoint? { 84 | guard let contentView = self.renderView?.contentView else { return nil } 85 | return touch.location(in: contentView) 86 | } 87 | func touchesBegan(_ touches: Set, with event: UIEvent?) { 88 | } 89 | 90 | func touchesMoved(_ touches: Set, with event: UIEvent?) { 91 | } 92 | 93 | func touchesEnded(_ touches: Set, with event: UIEvent?) { 94 | } 95 | 96 | func touchesCancelled(_ touches: Set, with event: UIEvent?) { 97 | } 98 | 99 | #elseif os(macOS) 100 | func locationInScene(_ event: NSEvent) -> CGPoint? { 101 | return renderView?.contentView.convert(event.locationInWindow, from: nil) 102 | } 103 | func mouseDown(with event: NSEvent) { 104 | } 105 | 106 | func mouseMoved(with event: NSEvent) { 107 | } 108 | 109 | func mouseDragged(with event: NSEvent) { 110 | } 111 | 112 | func mouseUp(with event: NSEvent) { 113 | } 114 | #endif 115 | 116 | } 117 | -------------------------------------------------------------------------------- /Silvershadow/Sequence+Z.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sequence+Z.swift 3 | // ZKit [swift 3] 4 | // 5 | // The MIT License (MIT) 6 | // 7 | // Copyright (c) 2016 Electricwoods LLC, Kaz Yoshikawa. 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | // 27 | // 28 | // Description: 29 | // pair() invokes closure with a paired items accoding to the sequence. 30 | // It is usuful to build another sequence of item[N-1], item[N] based from 31 | // the current sequence. 32 | // 33 | // Usage: 34 | // let array = [1, 3, 4, 7, 8, 9] 35 | // let items = array.pair { "\($0.0)-\($0.1)" } // ["1-3", "3-4", "4-7", "7-8", "8-9"] 36 | // 37 | 38 | 39 | extension Sequence { 40 | 41 | @discardableResult 42 | func pair(_ closure: (Self.Iterator.Element, Self.Iterator.Element) -> T ) -> [T] { 43 | var results = [T]() 44 | var previous: Self.Iterator.Element? = nil 45 | var iterator = self.makeIterator() 46 | while let item = iterator.next() { 47 | if let previous = previous { 48 | results.append(closure(previous, item)) 49 | } 50 | previous = item 51 | } 52 | return results 53 | } 54 | 55 | } 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Silvershadow/VertexBuffer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VertexBuffer.swift 3 | // Silvershadow 4 | // 5 | // Created by Kaz Yoshikawa on 1/11/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MetalKit 11 | 12 | // 13 | // VertexBuffer 14 | // 15 | 16 | class VertexBuffer { 17 | 18 | let device: MTLDevice 19 | var buffer: MTLBuffer 20 | var count: Int 21 | var capacity: Int 22 | 23 | init(device: MTLDevice, vertices: [T], capacity: Int? = nil) { 24 | assert(vertices.count <= capacity ?? vertices.count) 25 | self.device = device 26 | self.count = vertices.count 27 | let capacity = capacity ?? vertices.count 28 | let length = MemoryLayout.stride * capacity 29 | self.capacity = capacity 30 | self.buffer = device.makeBuffer(bytes: vertices, length: length, options: [.storageModeShared])! // !?! 31 | } 32 | 33 | deinit { 34 | // buffer.setPurgeableState(.empty) 35 | } 36 | 37 | func append(_ vertices: [T]) { 38 | if self.count + vertices.count < self.capacity { 39 | let vertexArray = UnsafeMutablePointer(OpaquePointer(self.buffer.contents())) 40 | for index in 0 ..< vertices.count { 41 | vertexArray[self.count + index] = vertices[index] 42 | } 43 | self.count += vertices.count 44 | } 45 | else { 46 | let count = self.count 47 | let length = MemoryLayout.stride * (count + vertices.count) 48 | let buffer = self.device.makeBuffer(length: length, options: [.storageModeShared])! 49 | let sourceArrayPtr = UnsafeMutablePointer(OpaquePointer(self.buffer.contents())) 50 | let sourceArray = UnsafeMutableBufferPointer(start: sourceArrayPtr, count: count) 51 | let destinationArrayPtr = UnsafeMutablePointer(OpaquePointer(buffer.contents())) 52 | let destinationArray = UnsafeMutableBufferPointer(start: destinationArrayPtr, count: count + vertices.count) 53 | 54 | (0 ..< count).forEach { destinationArray[$0] = sourceArray[$0] } 55 | (0 ..< vertices.count).forEach { destinationArray[count + $0] = vertices[$0] } 56 | 57 | self.count = count + vertices.count 58 | self.capacity = self.count 59 | 60 | self.buffer = buffer 61 | } 62 | } 63 | 64 | func set(_ vertices: [T]) { 65 | if vertices.count < self.capacity { 66 | let destinationArrayPtr = UnsafeMutablePointer(OpaquePointer(buffer.contents())) 67 | let destinationArray = UnsafeMutableBufferPointer(start: destinationArrayPtr, count: count + vertices.count) 68 | (0 ..< vertices.count).forEach { destinationArray[$0] = vertices[$0] } 69 | self.count = vertices.count 70 | } 71 | else { 72 | let bytes = MemoryLayout.size * vertices.count 73 | let buffer = device.makeBuffer(bytes: vertices, length: bytes, options: [.storageModeShared])! 74 | self.count = vertices.count 75 | self.capacity = vertices.count 76 | self.buffer = buffer 77 | } 78 | } 79 | 80 | var vertices: [T] { 81 | let vertexArray = UnsafeMutablePointer(OpaquePointer(self.buffer.contents())) 82 | return (0 ..< count).map { vertexArray[$0] } 83 | } 84 | 85 | } 86 | 87 | -------------------------------------------------------------------------------- /Silvershadow/XView+Z.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Z.swift 3 | // ZKit 4 | // 5 | // Created by Kaz Yoshikawa on 12/12/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import Cocoa 13 | #endif 14 | 15 | 16 | extension XView { 17 | 18 | func transform(to view: XView) -> CGAffineTransform { 19 | let targetRect = self.convert(self.bounds, to: view) 20 | return view.bounds.transform(to: targetRect) 21 | } 22 | 23 | func addSubviewToFit(_ view: XView) { 24 | view.frame = self.bounds 25 | self.addSubview(view) 26 | view.translatesAutoresizingMaskIntoConstraints = false 27 | view.topAnchor.constraint(equalTo: self.topAnchor).isActive = true 28 | view.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true 29 | view.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true 30 | view.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true 31 | } 32 | 33 | func setBorder(color: XColor?, width: CGFloat) { 34 | #if os(macOS) 35 | guard let layer = self.layer else { fatalError() } 36 | #endif 37 | 38 | layer.borderWidth = width 39 | layer.borderColor = color?.cgColor 40 | } 41 | 42 | #if os(macOS) 43 | var backgroundColor: NSColor? { 44 | get { 45 | return layer?.backgroundColor.flatMap { NSColor(cgColor: $0) } 46 | } 47 | set { 48 | self.wantsLayer = true // ?? 49 | self.layer?.backgroundColor = newValue?.cgColor 50 | } 51 | } 52 | #endif 53 | 54 | } 55 | -------------------------------------------------------------------------------- /SilvershadowApp_ios/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SilvershadowApp_ios 4 | // 5 | // Created by Kaz Yoshikawa on 12/25/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SilvershadowApp_ios/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /SilvershadowApp_ios/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SilvershadowApp_ios/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SilvershadowApp_ios/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /SilvershadowApp_ios/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SilvershadowApp_mac/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SilvershadowApp_mac 4 | // 5 | // Created by Kaz Yoshikawa on 12/25/16. 6 | // Copyright © 2016 Electricwoods LLC. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | 12 | @NSApplicationMain 13 | class AppDelegate: NSObject, NSApplicationDelegate { 14 | 15 | @IBOutlet weak var sampleSceneMenuItem: NSMenuItem! 16 | @IBOutlet weak var sampleCanvasMenuItem: NSMenuItem! 17 | 18 | func applicationDidFinishLaunching(_ aNotification: Notification) { 19 | 20 | let app = NSApplication.shared 21 | if let sampleSceneAction = sampleCanvasMenuItem.action { 22 | app.sendAction(sampleSceneAction, to: app, from: sampleCanvasMenuItem) 23 | } 24 | 25 | } 26 | 27 | func applicationWillTerminate(_ aNotification: Notification) { 28 | // Insert code here to tear down your application 29 | } 30 | 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /SilvershadowApp_mac/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /SilvershadowApp_mac/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SilvershadowApp_mac/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainStoryboardFile 26 | Main 27 | NSPrincipalClass 28 | NSApplication 29 | 30 | 31 | --------------------------------------------------------------------------------