Added init for Metal in SwiftUIKit

This commit is contained in:
zmeriksen 2020-01-19 09:44:56 -06:00
parent 749631e664
commit 7b33f525ed
10 changed files with 689 additions and 0 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
/Packages
/*.xcodeproj
xcuserdata/
build

View File

@ -0,0 +1,93 @@
//
// Primitive.swift
// MetalTutoral
//
// Created by developer on 10/22/19.
// Copyright © 2019 developer. All rights reserved.
//
import MetalKit
@available(iOS 9.0, *)
class Primitive {
static func importObj(device: MTLDevice, size: Float) -> MTKMesh {
let allocator = MTKMeshBufferAllocator(device: device)
var url = URL(string: "/Users/developer/Documents/cube.obj")
let vertexDescriptor = MDLVertexDescriptor()
let vertexLayout = MDLVertexBufferLayout()
vertexLayout.stride = MemoryLayout<Vertex>.stride
vertexDescriptor.layouts = [vertexLayout]
vertexDescriptor.attributes = [MDLVertexAttribute(name: MDLVertexAttributePosition, format: MDLVertexFormat.float2, offset: 0, bufferIndex: 0),
MDLVertexAttribute(name: MDLVertexAttributeNormal, format: MDLVertexFormat.float2, offset: MemoryLayout<vector_float2>.stride, bufferIndex: 0)]
// vertexDescriptor.attributes = [MDLVertexAttribute(name: MDLVertexAttributePosition, format: MDLVertexFormat.float3, offset: 0, bufferIndex: 0),
// MDLVertexAttribute(name: MDLVertexAttributeColor, format: MDLVertexFormat.float4, offset: MemoryLayout<vector_float3>.stride, bufferIndex: 0),
// MDLVertexAttribute(name: MDLVertexAttributeTextureCoordinate, format: MDLVertexFormat.float2, offset: MemoryLayout<vector_float3>.stride+MemoryLayout<vector_float4>.stride, bufferIndex: 0),
// MDLVertexAttribute(name: MDLVertexAttributeNormal, format: MDLVertexFormat.float3, offset: MemoryLayout<vector_float3>.stride + MemoryLayout<vector_float4>.stride +
// MemoryLayout<vector_float2>.stride, bufferIndex: 0)]
var error: NSError?
let asset = MDLAsset(url: url!,vertexDescriptor: vertexDescriptor, bufferAllocator: allocator, preserveTopology: true, error: &error)
if error != nil{
print(error)
}
let model = asset.object(at: 0) as! MDLMesh
var mesh : MTKMesh
do {
mesh = try MTKMesh(mesh: model, device: device)
} catch let error {
fatalError(error.localizedDescription)
}
return mesh
}
static func makeCube(device: MTLDevice, size: Float) -> MDLMesh {
let allocator = MTKMeshBufferAllocator(device: device)
let mesh = MDLMesh(boxWithExtent: [size, size, size],
segments: [1,1,1],
inwardNormals: false,
geometryType: .triangles,
allocator: allocator)
return mesh
}
static func makeTriangle(device: MTLDevice, size: Float) -> MDLMesh {
let allocator = MTKMeshBufferAllocator(device: device)
//create vertex buffer
let vertices: [vector_float2] = [vector_float2(0, 0), vector_float2(0.1, 0), vector_float2(0.1,0.1)]
let vertexBuffer = allocator.newBuffer(MemoryLayout<vector_float2>.stride * vertices.count, type: .vertex)
let vertexMap = vertexBuffer.map()
vertexMap.bytes.assumingMemoryBound(to: vector_float2.self).assign(from: vertices, count: vertices.count)
//create index buffer
let indices: [UInt16] = [UInt16(0), UInt16(1), UInt16(2)]
let indexBuffer = allocator.newBuffer(MemoryLayout<UInt16>.stride * indices.count, type: .index)
let indexMap = indexBuffer.map()
indexMap.bytes.assumingMemoryBound(to: UInt16.self).assign(from: indices, count: indices.count)
let submesh = MDLSubmesh(indexBuffer: indexBuffer,
indexCount: indices.count,
indexType: .uInt16,
geometryType: .points,
material: nil)
let vertexDescriptor = MDLVertexDescriptor()
vertexDescriptor.attributes[0] = MDLVertexAttribute(name: MDLVertexAttributePosition,
format: .float2,
offset: 0,
bufferIndex: 0)
vertexDescriptor.layouts[0] = MDLVertexBufferLayout(stride: MemoryLayout<vector_float2>.stride)
let mesh = MDLMesh(vertexBuffer: vertexBuffer,
vertexCount: vertices.count,
descriptor: vertexDescriptor,
submeshes: [submesh])
return mesh
}
}

View File

@ -0,0 +1,137 @@
//
// CustomRenderer.swift
// SwiftUIKit
//
// Created by Zach Eriksen on 1/19/20.
//
import MetalKit
@available(iOS 9.0, *)
class CustomRenderer: NSObject, Renderer {
// Dummy
func getMesh() -> MDLMesh {
return Primitive.makeCube(device: device, size: 1)
}
var mesh: MTKMesh?
var vertexBuffer: MTLBuffer?
var pipelineState: MTLRenderPipelineState?
var vertexFunction: MTLFunction?
var fragmentFunction: MTLFunction?
var clearColor: MTLClearColor?
var renderMethod: ((MTLRenderCommandEncoder) -> Void)?
internal var vertexShaderName: String = "vertex_main_moving"
internal var fragmentShaderName: String = "fragment_main"
var timer: Float = 0
override init() {
super.init()
}
func mesh(_ closure: (MTLDevice) -> MDLMesh) -> Self {
do{
mesh = try MTKMesh(mesh: closure(device), device: device)
} catch let error {
print(error.localizedDescription)
}
vertexBuffer = mesh?.vertexBuffers[0].buffer
return self
}
func vertex(shaderName: () -> String) -> Self {
let library = device.makeDefaultLibrary()
vertexFunction = library?.makeFunction(name: shaderName())
return self
}
func fragment(shaderName: () -> String) -> Self {
let library = device.makeDefaultLibrary()
fragmentFunction = library?.makeFunction(name: shaderName())
return self
}
func clear(color: () -> MTLClearColor) -> Self {
clearColor = color()
return self
}
func render(encoder: @escaping (MTLRenderCommandEncoder) -> Void) -> Self {
renderMethod = encoder
return self
}
func load(metalView: MTKView) {
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
if let mesh = mesh {
pipelineDescriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mesh.vertexDescriptor)
}
pipelineDescriptor.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
do {
pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch let error {
fatalError(error.localizedDescription)
}
// Protocol
if let color = clearColor {
metalView.clearColor = color
}
metalView.delegate = self
}
func configure(renderEncoder: MTLRenderCommandEncoder) -> MTLRenderCommandEncoder? {
//pipeline state
guard let pipelineState = pipelineState,
let mesh = mesh else {
return nil
}
//pre-processing
timer += 0.05
var currentTime = sin(timer)
var al = abs(currentTime)
//render encoder init
renderEncoder.setVertexBytes(&currentTime,
length: MemoryLayout<Float>.stride,
index: 1)
renderEncoder.setFragmentBytes(&al,
length: MemoryLayout<Float>.stride,
index: 0)
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
for submesh in mesh.submeshes {
renderEncoder.drawIndexedPrimitives(type: .triangle,
indexCount: submesh.indexCount,
indexType: submesh.indexType,
indexBuffer: submesh.indexBuffer.buffer,
indexBufferOffset: submesh.indexBuffer.offset)
}
return renderEncoder
}
}
@available(iOS 9.0, *)
extension CustomRenderer: MTKViewDelegate {
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
draw(metalView: view)
}
}

View File

@ -0,0 +1,17 @@
//
// Vertex.swift
// MetalTutoral
//
// Created by developer on 10/28/19.
// Copyright © 2019 developer. All rights reserved.
//
import MetalKit
struct Vertex {
var vertex: float2
init(x: Float, y: Float) {
self.vertex = float2(x, y)
}
}

View File

@ -0,0 +1,102 @@
////
//// MeshRenderer.swift
//// MetalTutoral
////
//// Created by developer on 11/12/19.
//// Copyright © 2019 developer. All rights reserved.
////
import MetalKit
@available(iOS 9.0, *)
class ColorChangeRenderer: NSObject, Renderer {
var mesh: MTKMesh?
var vertexBuffer: MTLBuffer?
var pipelineState: MTLRenderPipelineState?
internal var vertexShaderName: String = "vertex_main"
internal var fragmentShaderName: String = "fragment_main_change"
var timer: Float = 0
override init() {
super.init()
let mdlMesh = getMesh()
do{
mesh = try MTKMesh(mesh: mdlMesh, device: device)
} catch let error {
print(error.localizedDescription)
}
vertexBuffer = mesh?.vertexBuffers[0].buffer
}
func getMesh() -> MDLMesh {
return Primitive.makeCube(device: device, size: 1)
}
func load(metalView: MTKView) {
let library = device.makeDefaultLibrary()
let vertexFunction = library?.makeFunction(name: vertexShaderName)
let fragmentFunction = library?.makeFunction(name: fragmentShaderName)
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
if let mesh = mesh {
pipelineDescriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mesh.vertexDescriptor)
}
pipelineDescriptor.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
do {
pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch let error {
fatalError(error.localizedDescription)
}
// Protocol
metalView.clearColor = MTLClearColor(red: 1, green: 1, blue: 0.8, alpha: 1)
metalView.delegate = self
}
func configure(renderEncoder: MTLRenderCommandEncoder) -> MTLRenderCommandEncoder? {
guard let pipelineState = pipelineState,
let mesh = mesh else {
return nil
}
timer += 0.05
var currentTime = sin(timer)
var al = abs(currentTime)
renderEncoder.setFragmentBytes(&al,
length: MemoryLayout<Float>.stride,
index: 0)
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
for submesh in mesh.submeshes {
renderEncoder.drawIndexedPrimitives(type: .triangle,
indexCount: submesh.indexCount,
indexType: submesh.indexType,
indexBuffer: submesh.indexBuffer.buffer,
indexBufferOffset: submesh.indexBuffer.offset)
}
return renderEncoder
}
}
@available(iOS 9.0, *)
extension ColorChangeRenderer: MTKViewDelegate {
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
draw(metalView: view)
}
}

View File

@ -0,0 +1,92 @@
////
//// MeshRenderer.swift
//// MetalTutoral
////
//// Created by developer on 11/12/19.
//// Copyright © 2019 developer. All rights reserved.
////
import MetalKit
@available(iOS 9.0, *)
class MeshRenderer: NSObject, Renderer {
var mesh: MTKMesh?
var vertexBuffer: MTLBuffer?
var pipelineState: MTLRenderPipelineState?
internal var vertexShaderName: String = "vertex_main"
internal var fragmentShaderName: String = "fragment_main_test"
override init() {
super.init()
let mdlMesh = getMesh()
do{
mesh = try MTKMesh(mesh: mdlMesh, device: device)
} catch let error {
print(error.localizedDescription)
}
vertexBuffer = mesh?.vertexBuffers[0].buffer
}
func getMesh() -> MDLMesh {
return Primitive.makeCube(device: device, size: 1)
}
func load(metalView: MTKView) {
let library = device.makeDefaultLibrary()
let vertexFunction = library?.makeFunction(name: vertexShaderName)
let fragmentFunction = library?.makeFunction(name: fragmentShaderName)
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
if let mesh = mesh {
pipelineDescriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mesh.vertexDescriptor)
}
pipelineDescriptor.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
do {
pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch let error {
fatalError(error.localizedDescription)
}
// Protocol
metalView.clearColor = MTLClearColor(red: 1, green: 1, blue: 0.8, alpha: 1)
metalView.delegate = self
}
func configure(renderEncoder: MTLRenderCommandEncoder) -> MTLRenderCommandEncoder? {
guard let pipelineState = pipelineState,
let mesh = mesh else {
return nil
}
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
for submesh in mesh.submeshes {
renderEncoder.drawIndexedPrimitives(type: .triangle,
indexCount: submesh.indexCount,
indexType: submesh.indexType,
indexBuffer: submesh.indexBuffer.buffer,
indexBufferOffset: submesh.indexBuffer.offset)
}
return renderEncoder
}
}
@available(iOS 9.0, *)
extension MeshRenderer: MTKViewDelegate {
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
draw(metalView: view)
}
}

View File

@ -0,0 +1,77 @@
//
// Renderer.swift
// MetalTutoral
//
// Created by developer on 10/22/19.
// Copyright © 2019 developer. All rights reserved.
//
import MetalKit
class MetalDevice {
static var shared: MetalDevice = MetalDevice()
var device: MTLDevice
var commandQueue: MTLCommandQueue
init() {
guard let device = MTLCreateSystemDefaultDevice(),
let commandQueue = device.makeCommandQueue() else {
fatalError("GPU not available")
}
self.device = device
self.commandQueue = commandQueue
}
}
@available(iOS 9.0, *)
protocol Renderer {
var device: MTLDevice { get }
var commandQueue: MTLCommandQueue { get }
var mesh: MTKMesh? { get set }
var vertexBuffer: MTLBuffer? { get set }
var pipelineState: MTLRenderPipelineState? { get set }
var vertexShaderName: String { get }
var fragmentShaderName: String { get }
func getMesh() -> MDLMesh
func load(metalView: MTKView)
func configure(renderEncoder: MTLRenderCommandEncoder) -> MTLRenderCommandEncoder?
}
@available(iOS 9.0, *)
extension Renderer {
var device: MTLDevice {
return MetalDevice.shared.device
}
var commandQueue: MTLCommandQueue {
return MetalDevice.shared.commandQueue
}
}
// MARK: MTKViewDelegate
@available(iOS 9.0, *)
extension Renderer {
//Called once per frame, regenerate the command buffer
func draw(metalView view: MTKView) {
guard let descriptor = view.currentRenderPassDescriptor,
let commandBuffer = self.commandQueue.makeCommandBuffer(),
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor),
let configuredEncoder = configure(renderEncoder: renderEncoder) else {
return
}
//drawing code goes here
configuredEncoder.endEncoding()
guard let drawable = view.currentDrawable else {
return
}
//Present and commit the drawable texture to the GPU
commandBuffer.present(drawable)
commandBuffer.commit()
}
}

View File

@ -0,0 +1,105 @@
//
// Renderer.swift
// MetalTutoral
//
// Created by developer on 10/22/19.
// Copyright © 2019 developer. All rights reserved.
//
import MetalKit
@available(iOS 9.0, *)
class TriangleRenderer: NSObject, Renderer {
var mesh: MTKMesh?
var vertexBuffer: MTLBuffer?
var pipelineState: MTLRenderPipelineState?
internal var vertexShaderName: String = "vertex_main_moving"
internal var fragmentShaderName: String = "fragment_main"
var timer: Float = 0
override init() {
super.init()
let mdlMesh = getMesh()
do{
mesh = try MTKMesh(mesh: mdlMesh, device: device)
} catch let error {
print(error.localizedDescription)
}
vertexBuffer = mesh?.vertexBuffers[0].buffer
}
func getMesh() -> MDLMesh {
return Primitive.makeTriangle(device: device, size: 1)
}
func load(metalView: MTKView) {
let library = device.makeDefaultLibrary()
let vertexFunction = library?.makeFunction(name: vertexShaderName)
let fragmentFunction = library?.makeFunction(name: fragmentShaderName)
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
if let mesh = mesh {
pipelineDescriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mesh.vertexDescriptor)
}
pipelineDescriptor.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
do {
pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch let error {
fatalError(error.localizedDescription)
}
// Protocol
metalView.clearColor = MTLClearColor(red: 1, green: 1, blue: 0.8, alpha: 1)
metalView.delegate = self
}
func configure(renderEncoder: MTLRenderCommandEncoder) -> MTLRenderCommandEncoder? {
guard let pipelineState = pipelineState,
let mesh = mesh else {
return nil
}
timer += 0.05
var currentTime = sin(timer)
var al = abs(currentTime)
renderEncoder.setVertexBytes(&currentTime,
length: MemoryLayout<Float>.stride,
index: 1)
renderEncoder.setFragmentBytes(&al,
length: MemoryLayout<Float>.stride,
index: 0)
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
for submesh in mesh.submeshes {
renderEncoder.drawIndexedPrimitives(type: .triangle,
indexCount: submesh.indexCount,
indexType: submesh.indexType,
indexBuffer: submesh.indexBuffer.buffer,
indexBufferOffset: submesh.indexBuffer.offset)
}
return renderEncoder
}
}
@available(iOS 9.0, *)
extension TriangleRenderer: MTKViewDelegate {
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
draw(metalView: view)
}
}

View File

@ -0,0 +1,40 @@
//
// Shaders.metal
// MetalTutoral
//
// Created by developer on 10/22/19.
// Copyright © 2019 developer. All rights reserved.
//
#include <metal_stdlib>
using namespace metal;
// Describes vertex attributes that match the descriptor
struct VertexIn {
float4 position [[attribute(0)]];
};
// Implement a vertex shader that returns the vertices passed in as a float4
vertex float4 vertex_main_moving(const VertexIn vertexIn [[stage_in]], constant float &timer [[ buffer(1) ]]) {
float4 position = vertexIn.position;
position.x *= cos(timer);
position.y *= sin(timer);
return position;
}
vertex float4 vertex_main(const VertexIn vertexIn [[stage_in]]) {
float4 position = vertexIn.position;
return position;
}
fragment float4 fragment_main() {
return float4(1, 0, 0, 1);
}
fragment float4 fragment_main_test() {
return float4(1, 1, 0, 1);
}
//fragment float4 fragment_main_change(VertexOut in [[stage_in]]) {
// return float4(in.normal, 1);
//}

View File

@ -0,0 +1,25 @@
//
// MetalView.swift
// SwiftUIKit
//
// Created by Zach Eriksen on 1/19/20.
//
import MetalKit
@available(iOS 9.0, *)
class MetalView: MTKView {
private var renderer: Renderer
init(renderer: Renderer) {
self.renderer = renderer
super.init(frame: .zero, device: renderer.device)
self.renderer.load(metalView: self)
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}