Skip to content

Instantly share code, notes, and snippets.

@0xLeif
Last active May 6, 2023 11:47
Show Gist options
  • Star 42 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save 0xLeif/bc0d908bd7c5758d2f7766b8458ed4fd to your computer and use it in GitHub Desktop.
Save 0xLeif/bc0d908bd7c5758d2f7766b8458ed4fd to your computer and use it in GitHub Desktop.
Metal + SwiftUI
//
// 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