From 8da90580143b210e8a44dba21c321307b06dbf2e Mon Sep 17 00:00:00 2001 From: Jacob Bandes-Storch Date: Sun, 21 Feb 2016 19:23:01 -0800 Subject: [PATCH] Initial commit --- .gitignore | 1 + LICENSE.txt | 21 ++ Metalbrot.playground/Contents.swift | 189 +++++++++++++++ Metalbrot.playground/Resources/Shaders.metal | 110 +++++++++ Metalbrot.playground/Resources/navigation.png | Bin 0 -> 61179 bytes Metalbrot.playground/Sources/Helpers.swift | 224 ++++++++++++++++++ Metalbrot.playground/contents.xcplayground | 2 + .../contents.xcworkspacedata | 7 + Metalbrot.playground/timeline.xctimeline | 11 + README.md | 3 + 10 files changed, 568 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 Metalbrot.playground/Contents.swift create mode 100644 Metalbrot.playground/Resources/Shaders.metal create mode 100644 Metalbrot.playground/Resources/navigation.png create mode 100644 Metalbrot.playground/Sources/Helpers.swift create mode 100644 Metalbrot.playground/contents.xcplayground create mode 100644 Metalbrot.playground/playground.xcworkspace/contents.xcworkspacedata create mode 100644 Metalbrot.playground/timeline.xctimeline create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..125ee7a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.xcuserdatad diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..7105990 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Jacob Bandes-Storch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Metalbrot.playground/Contents.swift b/Metalbrot.playground/Contents.swift new file mode 100644 index 0000000..5e5fb45 --- /dev/null +++ b/Metalbrot.playground/Contents.swift @@ -0,0 +1,189 @@ +import XCPlayground +import Cocoa +import Metal +/*: + ## Drawing Fractals with Minimal Metal + + *[Jacob Bandes-Storch](http://bandes-stor.ch/), Feb 2016* + + This playground provides a small interactive example of how to use Metal to render visualizations of [fractals](https://en.wikipedia.org/wiki/Fractal) (namely, the [Mandelbrot set](https://en.wikipedia.org/wiki/Mandelbrot_set) and [Julia sets](https://en.wikipedia.org/wiki/Julia_set)). This certainly isn’t a comprehensive overview of Metal, but hopefully it’s easy to follow and modify. Enjoy! + + - Experiment: Click and drag on the fractal. Watch it change from the Mandelbrot set to a Julia set, and morph as you move the mouse. What happens if you click in a black area of the Mandelbrot set, as opposed to a colored area? + + - Note: To see the playground output, click the Assistant Editor button in the toolbar, or press ⌥⌘↩, which should display the Timeline. To see the extra functions in `Helpers.swift`, enable the Navigator via the toolbar or by pressing ⌘1. + ![navigation](navigation.png) + + - Note: This demo only covers using Metal for **compute functions**. There are a lot more complicated things you can do with the full rendering pipeline. Some further reading material is linked at the end of the playground. + */ +/*: + ---- + ### Setup + + To use Metal, we first need access to a connected graphics card (***device***). Since this is a small demo, we’ll prefer a low-power (integrated) graphics card. + + We’ll also need a ***command queue*** to let us send commands to the device, and a [dispatch queue](https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html) on which we’ll send these commands. + */ +let device = require(MTLCopyAllDevices().firstWhere{ $0.lowPower } ?? MTLCreateSystemDefaultDevice(), + orDie: "need a Metal device") + +let commandQueue = device.newCommandQueue() + +let drawingQueue = dispatch_queue_create("drawingQueue", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0)) + +/*: + ---- + ***Shaders*** are small programs which run on the graphics card. + We can load the shader library from a separate file, `Shaders.metal` (which you can find in the left-hand Project navigator (⌘1) under **Resources**), and compile them on the fly for this device. This example uses two shaders, or ***compute kernels***, named `mandelbrotShader` and `juliaShader`. + */ +let shaderSource = require(try String(contentsOfURL: [#FileReference(fileReferenceLiteral: "Shaders.metal")#]), + orDie: "unable to read shader source file") + +let library = require(try device.newLibraryWithSource(shaderSource, options: nil), + orDie: "compiling shaders failed") + +//: - Experiment: Open up `Shaders.metal` and glance through it to understand what the shaders are doing. +//: - Important: If your shader has a syntax error, `newLibraryWithSource()` will throw an error here when it tries to compile the program. + +let mandelbrotShader = require(library.newFunctionWithName("mandelbrotShader"), + orDie: "unable to get mandelbrotShader") + +let juliaShader = require(library.newFunctionWithName("juliaShader"), + orDie: "unable to get juliaShader") + +//: The Julia set shader also needs some extra input, an *(x, y)* point, from the CPU. We can pass this via a shared buffer. +let juliaBuffer = device.newBufferWithLength(2 * sizeof(Float32), options: []) + +/*: + ---- + Before we can use these shaders, Metal needs to know how to request they be executed on the GPU. This information is precomputed and stored as ***compute pipeline state***, which we can reuse repeatedly. + + When executing the program, we’ll also have to decide how to utilize the GPU’s threads (how many groups of threads to use, and the number of threads per group). This will depend on the size of the view we want to draw into. + */ +let mandelbrotPipelineState = require(try device.newComputePipelineStateWithFunction(mandelbrotShader), + orDie: "unable to create compute pipeline state") + +let juliaPipelineState = require(try device.newComputePipelineStateWithFunction(juliaShader), + orDie: "unable to create compute pipeline state") + +var threadgroupSizes = ThreadgroupSizes.zeros // To be calculated later + +/*: + ---- + ### Drawing + + The fundamental way that Metal content gets onscreen is via CAMetalLayer. The layer has a pool of ***textures*** which hold image data. Our shaders will write into these textures, which can then be displayed on the screen. + + We’ll use a custom view class called `MetalView` which is backed by a CAMetalLayer, and automatically resizes its “drawable” (texture) to match the view’s size. (MetalKit provides the MTKView class, but it’s overkill for this demo.) + */ +let outputSize = CGSize(width: 300, height: 250) + +let metalView = MetalView(frame: CGRect(origin: .zero, size: outputSize), device: device) +let metalLayer = metalView.metalLayer +/*: + - Experiment: Look at the MetalView implementation to see how it interacts with the CAMetalLayer. + + A helper function called `computeAndDraw` in `Helpers.swift` takes care of encoding the commands which execute the shader, and submitting the buffer of encoded commands to the device. All we need to tell it is which pipeline state to use, which texture to draw into, and set up any necessary parameters to the shader functions. + */ +func drawMandelbrotSet() +{ + dispatch_async(drawingQueue) { + commandQueue.computeAndDraw(into: metalLayer.nextDrawable(), with: threadgroupSizes) { + $0.setComputePipelineState(mandelbrotPipelineState) + } + } +} + +func drawJuliaSet(point: CGPoint) +{ + dispatch_async(drawingQueue) { + commandQueue.computeAndDraw(into: metalLayer.nextDrawable(), with: threadgroupSizes) { + $0.setComputePipelineState(juliaPipelineState) + + // Pass the (x,y) coordinates of the clicked point via the buffer we allocated ahead of time. + $0.setBuffer(juliaBuffer, offset: 0, atIndex: 0) + let buf = UnsafeMutablePointer(juliaBuffer.contents()) + buf[0] = Float32(point.x) + buf[1] = Float32(point.y) + } + } +} +/*: + - Experiment: + Go check out the implementation of `computeAndDraw`! Can you understand how it works? + + ---- + ### The easy part + Now for some user interaction! Our view controller draws fractals when the view is first laid out, and whenever the mouse is dragged (user interaction requires Xcode 7.3). + */ +class Controller: NSViewController, MetalViewDelegate +{ + override func viewDidLayout() { + metalViewDrawableSizeDidChange(metalView) + } + func metalViewDrawableSizeDidChange(metalView: MetalView) { + // This helper function chooses how to assign the GPU’s threads to portions of the texture. + threadgroupSizes = mandelbrotPipelineState.threadgroupSizesForDrawableSize(metalView.metalLayer.drawableSize) + drawMandelbrotSet() + } + + override func mouseDown(event: NSEvent) { + drawJuliaSetForEvent(event) + } + override func mouseDragged(event: NSEvent) { + drawJuliaSetForEvent(event) + } + override func mouseUp(event: NSEvent) { + drawMandelbrotSet() + } + + func drawJuliaSetForEvent(event: NSEvent) { + var pos = metalView.convertPointToLayer(metalView.convertPoint(event.locationInWindow, fromView: nil)) + let scale = metalLayer.contentsScale + pos.x *= scale + pos.y *= scale + + drawJuliaSet(pos) + } +} + +//: Finally, we can put our view onscreen! +let controller = Controller() +controller.view = metalView +metalView.delegate = controller + +metalView.addSubview(Label(string: "Click me! (requires Xcode 7.3)"), at: CGPoint(x: 5, y: 5)) + +XCPlaygroundPage.currentPage.liveView = metalView + +/*: + ---- + ## What Next? + + I hope you’ve enjoyed (and learned something from) this demo. If you haven’t already, I encourage you to poke around in `Helpers.swift` and `Shaders.metal`. Try changing the code and see what happens — *take chances, make mistkaes, get messy!* + + If you like reading, there’s lots of reading material about Metal available from Apple, as well as excellent resources from others in the community. Just a few examples: + * Apple’s own [Metal for Developers](https://developer.apple.com/metal/) documentation and resources. + * [Metal By Example](http://metalbyexample.com/), a blog and book by Warren Moore. + * [Blog posts about Metal](http://redqueencoder.com/category/metal/) by The Red Queen Coder (Janie Clayton). + * [Posts and demos](http://flexmonkey.blogspot.co.uk/?view=magazine) by FlexMonkey (Simon Gladman) on topics including Metal and Core Image. + + ### Modify this playground! + + This demo barely scratches the surface. Here are a handful of ideas for things to try. (If you come up with something cool, I’d love to [hear about it](https://twitter.com/jtbandes)!) + + - Experiment: Tweak the `maxiters` and `escape` parameters in the shader source file. Do the fractals look different? Can you notice any difference in speed? Try modifying the playground to use a discrete graphics card, if your machine has one. + + - Experiment: Adapt this code to display the same fractals on an iOS device. (Metal isn’t supported in the iOS simulator.) You’ll need to use UIView instead of NSView, but most the Metal-related code can remain the same. + + - Experiment: Choose another fractal or another coloring scheme, and modify `Shaders.metal` to render it. + + - Experiment: Add a label which shows the coordinates that were clicked in the complex plane. + + Bonus: can you share the code which does this x,y-to-complex conversion between Swift and the shader itself? Try moving things into a full Xcode project and setting up a [bridging header](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html). You might want to use `#ifdef __cplusplus` and/or `extern "C"`. + + - Experiment: Try using [MTKView](https://developer.apple.com/library/ios/documentation/MetalKit/Reference/MTKView_ClassReference/) instead of the simple `MetalView` in this playground. Use the MTKView’s delegate or a subclass to render each frame, and modify the pipeline so that your fractals can change over time. + * Try to make the colors change slowly over time. + * Try to make the visualization zoom in on an [interesting point](https://en.wikipedia.org/wiki/Mandelbrot_set#Image_gallery_of_a_zoom_sequence). + + - Experiment: Use `CIImage.init(MTLTexture:options:)` to render the fractals into an image. Save an animated GIF using [CGImageDestination](http://stackoverflow.com/q/14915138/23649), or a movie using [AVAssetWriter](http://stackoverflow.com/q/3741323/23649). + */ diff --git a/Metalbrot.playground/Resources/Shaders.metal b/Metalbrot.playground/Resources/Shaders.metal new file mode 100644 index 0000000..ef2d9ff --- /dev/null +++ b/Metalbrot.playground/Resources/Shaders.metal @@ -0,0 +1,110 @@ +#include +using namespace metal; + +#define M_PI 3.141592653589793238462643383 + +/// Basic implementation of complex numbers, with * + - operators, and a function to return the squared magnitude. +template +struct complex +{ + T _x, _y; + + complex(T x, T y) : _x(x), _y(y) { } + + T sqmag() const { + return _x*_x + _y*_y; + } + + complex operator*(const thread complex& other) const { + return complex(_x*other._x - _y*other._y, + _x*other._y + _y*other._x); + } + + complex operator+(const thread complex& other) const { + return complex(_x + other._x, _y + other._y); + } + + complex operator-(const thread complex& other) const { + return complex(_x - other._x, _y - other._y); + } + + complex operator*(const thread T& c) const { + return complex(_x * c, _y * c); + } +}; + + +#define complexExtentX 3.5 +#define complexExtentY 3 +#define mandelbrotShiftX 0.2 + +/// Convert a point on the screen to a point in the complex plane. +template +complex screenToComplex(T x, T y, T width, T height) +{ + const T scale = max(complexExtentX/width, complexExtentY/height); + + return complex((x-width/2)*scale, (y-height/2)*scale); +} + + +/// Both Mandelbrot and Julia sets use the same iterative algorithm to determine whether +/// a given point is in the set. Each point is colored based on how quickly it escape to infinity. +template +float4 colorForIteration(complex z, complex c, int maxiters, float escape) +{ + for (int i = 0; i < maxiters; i++) { + z = z*z + c; + if (z.sqmag() > escape) { + // Smoothing coloring, adapted from: + // + float hue = (i+1-log2(log10(z.sqmag())/2))/maxiters*4 * M_PI + 3; + + // Convert to RGB + return float4((cos(hue)+1)/2, + (-cos(hue+M_PI/3)+1)/2, + (-cos(hue-M_PI/3)+1)/2, + 1); + } + } + + return float4(0, 0, 0, 1); +} + + +/// Render a visualization of the Mandelbrot set into the `output` texture. +kernel void mandelbrotShader(texture2d output [[texture(0)]], + uint2 upos [[thread_position_in_grid]]) +{ + int width = output.get_width(); + int height = output.get_height(); + if (upos.x > width || upos.y > height) return; + + complex z(0, 0); + + complex c = screenToComplex(upos.x - mandelbrotShiftX*width, + upos.y, + width, height); + + output.write(float4(colorForIteration(z, c, 100, 100)), upos); +} + + +/// Render a visualization of the Julia set for the point `screenPoint` into the `output` texture. +kernel void juliaShader(texture2d output [[texture(0)]], + uint2 upos [[thread_position_in_grid]], + const device float2& screenPoint [[buffer(0)]]) +{ + int width = output.get_width(); + int height = output.get_height(); + if (upos.x > width || upos.y > height) return; + + complex z = screenToComplex(upos.x, upos.y, width, height); + + complex c = screenToComplex(screenPoint.x - mandelbrotShiftX*width, + screenPoint.y, + width, + height); + + output.write(float4(colorForIteration(z, c, 100, 50)), upos); +} diff --git a/Metalbrot.playground/Resources/navigation.png b/Metalbrot.playground/Resources/navigation.png new file mode 100644 index 0000000000000000000000000000000000000000..32b7e04ee25f507e8a690387cda1307f0f18a9e6 GIT binary patch literal 61179 zcmd?RgjfOL;C(hSW2Ly0II(%s$NEip9G9nvr~3`n2R z_uYHzcg}xsu4};nYprM9&)xA`1gHRHak0s!ph%#|NlU7`pzbDPq!D*bz=l1( zFz=0I<F(6#EYAqwO>IUGG3sZs*k$l!wlI1uHBQQjI!XO@eVx`+~v%@*Kwb5

y)dKdowBiVsF>OZGU%&;OR#c%>N%AMDn!R4-YX= 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 FV1g~X78(Em literal 0 HcmV?d00001 diff --git a/Metalbrot.playground/Sources/Helpers.swift b/Metalbrot.playground/Sources/Helpers.swift new file mode 100644 index 0000000..bcfae5c --- /dev/null +++ b/Metalbrot.playground/Sources/Helpers.swift @@ -0,0 +1,224 @@ +import XCPlayground +import Cocoa +import Metal + +/// A view which is backed by a CAMetalLayer, automatically updating its drawableSize and contentsScale as appropriate. +public class MetalView: NSView +{ + @available(*, unavailable) public required init?(coder: NSCoder) { fatalError() } + + public let metalLayer = CAMetalLayer() + public weak var delegate: MetalViewDelegate? + + public init(frame: CGRect, device: MTLDevice) { + super.init(frame: frame) + metalLayer.device = device + wantsLayer = true + updateDrawableSize(contentsScale: metalLayer.contentsScale) + } + + public override func makeBackingLayer() -> CALayer { + return metalLayer + } + + public override func layer(layer: CALayer, shouldInheritContentsScale newScale: CGFloat, fromWindow window: NSWindow) -> Bool { + updateDrawableSize(contentsScale: newScale) + return true + } + + private func updateDrawableSize(contentsScale scale: CGFloat) { + var size = metalLayer.bounds.size + size.width *= scale + size.height *= scale + metalLayer.drawableSize = size + delegate?.metalViewDrawableSizeDidChange(self) + } +} + + +public protocol MetalViewDelegate: class +{ + func metalViewDrawableSizeDidChange(metalView: MetalView) +} + + +extension MTLSize +{ + var hasZeroDimension: Bool { + return depth == 0 || width == 0 || height == 0 + } +} + + +/// Encapsulates the sizes to be passed to `MTLComputeCommandEncoder.dispatchThreadgroups(_:threadsPerThreadgroup:)`. +public struct ThreadgroupSizes +{ + var threadsPerThreadgroup: MTLSize + var threadgroupsPerGrid: MTLSize + + public static let zeros = ThreadgroupSizes( + threadsPerThreadgroup: MTLSize(), + threadgroupsPerGrid: MTLSize()) + + var hasZeroDimension: Bool { + return threadsPerThreadgroup.hasZeroDimension || threadgroupsPerGrid.hasZeroDimension + } +} + + + +public extension MTLCommandQueue +{ + /// Helper function for running compute kernels and displaying the output onscreen. + /// + /// This function configures a MTLComputeCommandEncoder by setting the given `drawable`'s texture + /// as the 0th texture (so it will be available as a `[[texture(0)]]` parameter in the kernel). + /// It calls `drawBlock` to allow further configuration, then dispatches the threadgroups and + /// presents the results. + /// + /// - Requires: `drawBlock` must call `setComputePipelineState` on the command encoder to select a compute function. + func computeAndDraw(@autoclosure into drawable: () -> CAMetalDrawable?, with threadgroupSizes: ThreadgroupSizes, @noescape drawBlock: MTLComputeCommandEncoder -> Void) + { + if threadgroupSizes.hasZeroDimension { + print("dimensions are zero; not drawing") + return + } + + autoreleasepool { // Ensure drawables are freed for the system to allocate new ones. + guard let drawable = drawable() else { + print("no drawable") + return + } + + let buffer = self.commandBuffer() + let encoder = buffer.computeCommandEncoder() + encoder.setTexture(drawable.texture, atIndex: 0) + + drawBlock(encoder) + + encoder.dispatchThreadgroups(threadgroupSizes.threadgroupsPerGrid, threadsPerThreadgroup: threadgroupSizes.threadsPerThreadgroup) + encoder.endEncoding() + + buffer.presentDrawable(drawable) + buffer.commit() + buffer.waitUntilCompleted() + } + } +} + + +public extension MTLComputePipelineState +{ + /// Selects "reasonable" values for threadsPerThreadgroup and threadgroupsPerGrid for the given `drawableSize`. + /// - Remark: The heuristics used here are not perfect. There are many ways to underutilize the GPU, + /// including selecting suboptimal threadgroup sizes, or branching in the shader code. + /// + /// If you are certain you can always use threadgroups with a multiple of `threadExecutionWidth` + /// threads, then you may want to use MTLComputePipleineDescriptor and its property + /// `threadGroupSizeIsMultipleOfThreadExecutionWidth` to configure your pipeline state. + /// + /// If your shader is doing some more interesting calculations, and your threads need to share memory in some + /// meaningful way, then you’ll probably want to do something less generalized to choose your threadgroups. + func threadgroupSizesForDrawableSize(drawableSize: CGSize) -> ThreadgroupSizes + { + let waveSize = self.threadExecutionWidth + let maxThreadsPerGroup = self.maxTotalThreadsPerThreadgroup + + let drawableWidth = Int(drawableSize.width) + let drawableHeight = Int(drawableSize.height) + + if drawableWidth == 0 || drawableHeight == 0 { + print("drawableSize is zero") + return .zeros + } + + // Determine the set of possible sizes (not exceeding maxThreadsPerGroup). + var candidates: [ThreadgroupSizes] = [] + for groupWidth in 1...maxThreadsPerGroup { + for groupHeight in 1...(maxThreadsPerGroup/groupWidth) { + // Round up the number of groups to ensure the entire drawable size is covered. + // + let groupsPerGrid = MTLSize(width: (drawableWidth + groupWidth - 1) / groupWidth, + height: (drawableHeight + groupHeight - 1) / groupHeight, + depth: 1) + + candidates.append(ThreadgroupSizes( + threadsPerThreadgroup: MTLSize(width: groupWidth, height: groupHeight, depth: 1), + threadgroupsPerGrid: groupsPerGrid)) + } + } + + /// Make a rough approximation for how much compute power will be "wasted" (e.g. when the total number + /// of threads in a group isn’t an even multiple of `threadExecutionWidth`, or when the total number of + /// threads being dispatched exceeds the drawable size). Smaller is better. + func _estimatedUnderutilization(s: ThreadgroupSizes) -> Int { + let excessWidth = s.threadsPerThreadgroup.width * s.threadgroupsPerGrid.width - drawableWidth + let excessHeight = s.threadsPerThreadgroup.height * s.threadgroupsPerGrid.height - drawableHeight + + let totalThreadsPerGroup = s.threadsPerThreadgroup.width * s.threadsPerThreadgroup.height + let totalGroups = s.threadgroupsPerGrid.width * s.threadgroupsPerGrid.height + + let excessArea = excessWidth * drawableHeight + excessHeight * drawableWidth + excessWidth * excessHeight + let excessThreadsPerGroup = (waveSize - totalThreadsPerGroup % waveSize) % waveSize + + return excessArea + excessThreadsPerGroup * totalGroups + } + + // Choose the threadgroup sizes which waste the least amount of execution time/power. + let result = candidates.minElement { _estimatedUnderutilization($0) < _estimatedUnderutilization($1) } + return result ?? .zeros + } +} + + +/// Playground helper: produce the value of `expr`, or print the given `message` and exit. +/// If `expr` throws an error, the error is also printed. +public func require(@autoclosure expr: () throws -> T?, @autoclosure orDie message: () -> String) -> T +{ + do { + if let result = try expr() { return result } + else { print(message()) } + } + catch { + print(message()) + print("error: \(error)") + } + XCPlaygroundPage.currentPage.finishExecution() +} + + +public extension SequenceType +{ + /// - Returns: The first element in `self` which matches the given `predicate`. + func firstWhere(@noescape predicate: Generator.Element throws -> Bool) rethrows -> Generator.Element? + { + return try lazy.filter(predicate).first + } +} + + +public extension NSView +{ + func addSubview(subview: NSView, at origin: CGPoint) + { + addSubview(subview) + subview.frame.origin = origin + } +} + + +public class Label: NSTextField +{ + @available(*, unavailable) public required init?(coder: NSCoder) { fatalError() } + + public init(string: String) { + super.init(frame: .zero) + selectable = false + editable = false + bordered = false + drawsBackground = false + textColor = .whiteColor() + stringValue = string + sizeToFit() + } +} diff --git a/Metalbrot.playground/contents.xcplayground b/Metalbrot.playground/contents.xcplayground new file mode 100644 index 0000000..50ce46d --- /dev/null +++ b/Metalbrot.playground/contents.xcplayground @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/Metalbrot.playground/playground.xcworkspace/contents.xcworkspacedata b/Metalbrot.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Metalbrot.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Metalbrot.playground/timeline.xctimeline b/Metalbrot.playground/timeline.xctimeline new file mode 100644 index 0000000..f46c0b4 --- /dev/null +++ b/Metalbrot.playground/timeline.xctimeline @@ -0,0 +1,11 @@ + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..7beafc9 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +`Metalbrot.playground` is an [interactive playground](https://developer.apple.com/swift/blog/?id=35) showing how to use Metal compute kernels with Swift. More information can be found [on my blog](http://bandes-stor.ch/blog/2016/02/21/drawing-fractals-with-minimal-metal/). + +(This demo is free for personal and educational use. If you plan to use it for anything else, please credit me — see LICENSE.txt for details.)