Last active
May 6, 2023 11:47
-
-
Save 0xLeif/bc0d908bd7c5758d2f7766b8458ed4fd to your computer and use it in GitHub Desktop.
Metal + SwiftUI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ContentView.swift | |
// MetalSwiftUI | |
// | |
// Created by Zach Eriksen on 9/8/20. | |
// Copyright © 2020 oneleif. All rights reserved. | |
// | |
// Inspired [MetalUI](https://github.com/0xLeif/MetalUI) | |
import SwiftUI | |
import MetalKit | |
struct ContentView: View { | |
var body: some View { | |
List { | |
SwiftUIView { | |
MetalView() | |
} | |
SwiftUIView { | |
MetalView() | |
} | |
SwiftUIView { | |
MetalView() | |
} | |
SwiftUIView { | |
MetalView() | |
} | |
} | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} | |
// MARK: SwiftUI + Metal | |
public struct SwiftUIView: UIViewRepresentable { | |
public var wrappedView: UIView | |
private var handleUpdateUIView: ((UIView, Context) -> Void)? | |
private var handleMakeUIView: ((Context) -> UIView)? | |
public init(closure: () -> UIView) { | |
wrappedView = closure() | |
} | |
public func makeUIView(context: Context) -> UIView { | |
guard let handler = handleMakeUIView else { | |
return wrappedView | |
} | |
return handler(context) | |
} | |
public func updateUIView(_ uiView: UIView, context: Context) { | |
handleUpdateUIView?(uiView, context) | |
} | |
} | |
public extension SwiftUIView { | |
mutating func setMakeUIView(handler: @escaping (Context) -> UIView) -> Self { | |
handleMakeUIView = handler | |
return self | |
} | |
mutating func setUpdateUIView(handler: @escaping (UIView, Context) -> Void) -> Self { | |
handleUpdateUIView = handler | |
return self | |
} | |
} | |
// MARK: Metal Stuff | |
class MetalView: MTKView { | |
var renderer: Renderer! | |
init() { | |
super.init(frame: .zero, device: MTLCreateSystemDefaultDevice()) | |
// Make sure we are on a device that can run metal! | |
guard let defaultDevice = device else { | |
fatalError("Device loading error") | |
} | |
colorPixelFormat = .bgra8Unorm | |
// Our clear color, can be set to any color | |
clearColor = MTLClearColor(red: 0.1, green: 0.57, blue: 0.25, alpha: 1) | |
createRenderer(device: defaultDevice) | |
} | |
required init(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
func createRenderer(device: MTLDevice){ | |
renderer = Renderer(device: device) | |
delegate = renderer | |
} | |
} | |
// MARK: Renderer | |
struct Vertex { | |
var position: float3 | |
var color: float4 | |
} | |
class Renderer: NSObject { | |
var commandQueue: MTLCommandQueue! | |
var renderPipelineState: MTLRenderPipelineState! | |
var vertexBuffer: MTLBuffer! | |
var vertices: [Vertex] = [ | |
Vertex(position: float3(0,1,0), color: float4(1,0,0,1)), | |
Vertex(position: float3(-1,-1,0), color: float4(0,1,0,1)), | |
Vertex(position: float3(1,-1,0), color: float4(0,0,1,1)) | |
] | |
init(device: MTLDevice) { | |
super.init() | |
createCommandQueue(device: device) | |
createPipelineState(device: device) | |
createBuffers(device: device) | |
} | |
//MARK: Builders | |
func createCommandQueue(device: MTLDevice) { | |
commandQueue = device.makeCommandQueue() | |
} | |
func createPipelineState(device: MTLDevice) { | |
// The device will make a library for us | |
let library = device.makeDefaultLibrary() | |
// Our vertex function name | |
let vertexFunction = library?.makeFunction(name: "basic_vertex_function") | |
// Our fragment function name | |
let fragmentFunction = library?.makeFunction(name: "basic_fragment_function") | |
// Create basic descriptor | |
let renderPipelineDescriptor = MTLRenderPipelineDescriptor() | |
// Attach the pixel format that si the same as the MetalView | |
renderPipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm | |
// Attach the shader functions | |
renderPipelineDescriptor.vertexFunction = vertexFunction | |
renderPipelineDescriptor.fragmentFunction = fragmentFunction | |
// Try to update the state of the renderPipeline | |
do { | |
renderPipelineState = try device.makeRenderPipelineState(descriptor: renderPipelineDescriptor) | |
} catch { | |
print(error.localizedDescription) | |
} | |
} | |
func createBuffers(device: MTLDevice) { | |
vertexBuffer = device.makeBuffer(bytes: vertices, | |
length: MemoryLayout<Vertex>.stride * vertices.count, | |
options: []) | |
} | |
} | |
extension Renderer: MTKViewDelegate { | |
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {} | |
func draw(in view: MTKView) { | |
// Get the current drawable and descriptor | |
guard let drawable = view.currentDrawable, | |
let renderPassDescriptor = view.currentRenderPassDescriptor else { | |
return | |
} | |
// Create a buffer from the commandQueue | |
let commandBuffer = commandQueue.makeCommandBuffer() | |
let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor) | |
commandEncoder?.setRenderPipelineState(renderPipelineState) | |
// Pass in the vertexBuffer into index 0 | |
commandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0) | |
// Draw primitive at vertextStart 0 | |
commandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertices.count) | |
commandEncoder?.endEncoding() | |
commandBuffer?.present(drawable) | |
commandBuffer?.commit() | |
} | |
} | |
// MARK: Shaders.metal | |
/* | |
// | |
// Shaders.metal | |
// macOSMetal | |
// | |
// Created by Zach Eriksen on 4/30/18. | |
// Copyright © 2018 Zach Eriksen. All rights reserved. | |
// | |
#include <metal_stdlib> | |
using namespace metal; | |
struct VertexIn { | |
float3 position; | |
float4 color; | |
}; | |
struct VertexOut { | |
float4 position [[ position ]]; | |
float4 color; | |
}; | |
vertex VertexOut basic_vertex_function(const device VertexIn *vertices [[ buffer(0) ]], | |
uint vertexID [[ vertex_id ]]) { | |
VertexOut vOut; | |
vOut.position = float4(vertices[vertexID].position,1); | |
vOut.color = vertices[vertexID].color; | |
return vOut; | |
} | |
fragment float4 basic_fragment_function(VertexOut vIn [[ stage_in ]]) { | |
return vIn.color; | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment