From 463246df36abed348ef5b4f58e5198e8569f65a6 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 5 Aug 2017 09:04:43 -0700 Subject: [PATCH] [Metaballs] A Metal graphics pipeline that builds and runs but crashes --- Metaballs/Renderer.swift | 99 ++++++++++++++++++++++++++++++++++++++++ Metaballs/Shaders.metal | 34 +++++++------- 2 files changed, 115 insertions(+), 18 deletions(-) diff --git a/Metaballs/Renderer.swift b/Metaballs/Renderer.swift index 8761196..7bb8dc6 100644 --- a/Metaballs/Renderer.swift +++ b/Metaballs/Renderer.swift @@ -7,3 +7,102 @@ // import Foundation +import MetaballsKit +import MetalKit + +enum RendererError: Error { + case MetalError(String) +} + +protocol RendererDelegate { + var renderSize: CGSize { get set } + var field: Field { get } +} + +struct Vertex { + let x: Float + let y: Float + let texX: Float + let texY: Float +} + +class Renderer: NSObject, MTKViewDelegate { + var delegate: RendererDelegate? + + private var device: MTLDevice + private var commandQueue: MTLCommandQueue + private var renderPipelineState: MTLRenderPipelineState + + init(view: MTKView, field: Field) throws { + guard let device = MTLCreateSystemDefaultDevice() else { + throw RendererError.MetalError("Unable to create Metal system device") + } + self.device = device + view.device = device + do { + try field.setupMetal(withDevice: device) + } catch let e { + throw e + } + + let library = try device.makeDefaultLibrary(bundle: Bundle.main) + let vertexShader = library.makeFunction(name: "passthroughVertexShader") + let fragmentShader = library.makeFunction(name: "sampleToColorShader") + + let pipelineStateDescriptor = MTLRenderPipelineDescriptor() + pipelineStateDescriptor.label = "Render Pipeline" + pipelineStateDescriptor.vertexFunction = vertexShader + pipelineStateDescriptor.fragmentFunction = fragmentShader + pipelineStateDescriptor.colorAttachments[0].pixelFormat = view.colorPixelFormat + renderPipelineState = try device.makeRenderPipelineState(descriptor: pipelineStateDescriptor) + + commandQueue = device.makeCommandQueue() + super.init() + } + + /// MARK: - MTKViewDelegate + + func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { + delegate?.renderSize = size + // TODO: Reallocate the sample buffer and texture + } + + func draw(in view: MTKView) { + guard let field = delegate?.field else { + return + } + + // Two triangles, plus texture coordinates. + let points: [Vertex] = [Vertex(x: 1, y: -1, texX: 1, texY: 0), + Vertex(x: -1, y: -1, texX: 0, texY: 0), + Vertex(x: -1, y: 1, texX: 0, texY: 1), + + Vertex(x: 1, y: -1, texX: 1, texY: 0), + Vertex(x: -1, y: 1, texX: 0, texY: 1), + Vertex(x: 1, y: 1, texX: 1, texY: 1)] + + let buffer = commandQueue.makeCommandBuffer() + do { + let _ = try field.computeEncoderForSamplingKernel(withDevice: device, commandBuffer: buffer) + buffer.commit() + } catch let e { + print("\(e)") + } + + if let renderPass = view.currentRenderPassDescriptor { + let encoder = buffer.makeRenderCommandEncoder(descriptor: renderPass) + encoder.label = "Render Pass" + encoder.setViewport(MTLViewport(originX: 0.0, originY: 0.0, width: Double(view.drawableSize.width), height: Double(view.drawableSize.height), znear: -1.0, zfar: 1.0)) + encoder.setRenderPipelineState(renderPipelineState) + encoder.setVertexBytes(points, length: points.count * MemoryLayout.size, at: 0) + encoder.setFragmentTexture(field.sampleTexture, at: 0) + encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6) + encoder.endEncoding() + + if let drawable = view.currentDrawable { + buffer.present(drawable) + } + } + buffer.commit() + } +} diff --git a/Metaballs/Shaders.metal b/Metaballs/Shaders.metal index 47b0bbb..1f0b2b2 100644 --- a/Metaballs/Shaders.metal +++ b/Metaballs/Shaders.metal @@ -9,16 +9,10 @@ #include using namespace metal; -typedef struct { - float2 position; - float radius; -} Ball; - - typedef struct { float2 position; float2 textureCoordinate; -} VertexIn; +} Vertex; // From HelloCompute sample code project. // Vertex shader outputs and per-fragmeht inputs. Includes clip-space position and vertex outputs interpolated by rasterizer and fed to each fragment genterated by clip-space primitives. @@ -30,22 +24,26 @@ typedef struct { float2 textureCoordinate; } RasterizerData; -//vertex RasterizerData -//passthroughVertexShader(uint vertexID [[vertex_id]]) -//{ -// // TODO: Nothing really. Just pass on through to the fragment shader. -//} +vertex RasterizerData +passthroughVertexShader(uint vid [[vertex_id]], + constant Vertex* vertexes [[buffer(0)]]) +{ + RasterizerData out; + Vertex v = vertexes[vid]; + out.position = float4(v.position.xy, 0.0, 1.0); + out.textureCoordinate = v.textureCoordinate; + return out; +} fragment float4 -sampleToColorShader(RasterizerData in [[stage_in]], - constant float* samples [[buffer(0)]], - constant float2* size [[buffer(1)]]) +sampleToColorShader(RasterizerData in [[stage_in]], + texture2d samples [[texture(0)]]) { - int index = in.textureCoordinate.y * size->y + in.textureCoordinate.x; - float sample = samples[index]; + constexpr sampler textureSampler(mag_filter::linear, min_filter::linear); + const half4 sample = samples.sample(textureSampler, in.textureCoordinate); float4 out; - if (sample > 1.0) { + if (sample.r > 1.0) { out = float4(0.0, 1.0, 0.0, 0.0); } else {