macos: MetalView, render an MTKView

This commit is contained in:
Mitchell Hashimoto
2023-10-18 21:09:03 -07:00
parent 9fb4497675
commit 69cc6d11dc
5 changed files with 114 additions and 3 deletions

View File

@@ -30,7 +30,7 @@ extension Ghostty {
// surface. We need to keep the split root around so that we don't
// lose all of the surface state so this must be a ZStack.
if let surfaceView = zoomedSurface {
SurfaceWrapper(surfaceView: surfaceView)
InspectableSurface(surfaceView: surfaceView)
}
}
.focusedValue(\.ghosttySurfaceZoomed, zoomedSurface != nil)
@@ -343,7 +343,7 @@ extension Ghostty {
let pubClose = center.publisher(for: Notification.ghosttyCloseSurface, object: leaf.surface)
let pubFocus = center.publisher(for: Notification.ghosttyFocusSplit, object: leaf.surface)
SurfaceWrapper(surfaceView: leaf.surface, isSplit: !neighbors.isEmpty())
InspectableSurface(surfaceView: leaf.surface, isSplit: !neighbors.isEmpty())
.onReceive(pub) { onNewSplit(notification: $0) }
.onReceive(pubClose) { onClose(notification: $0) }
.onReceive(pubFocus) { onMoveFocus(notification: $0) }

View File

@@ -0,0 +1,70 @@
import Foundation
import MetalKit
import SwiftUI
extension Ghostty {
/// InspectableSurface is a type of Surface view that allows an inspector to be attached.
struct InspectableSurface: View {
/// Same as SurfaceWrapper, see the doc comments there.
@ObservedObject var surfaceView: SurfaceView
var isSplit: Bool = false
var body: some View {
SplitView(.vertical, left: {
SurfaceWrapper(surfaceView: surfaceView, isSplit: isSplit)
}, right: {
InspectorView()
})
}
}
struct InspectorView: View {
var body: some View {
MetalView<Renderer>()
}
}
class Renderer: NSObject, MetalViewRenderer {
let device: MTLDevice
let commandQueue: MTLCommandQueue
required init(metalView: MTKView) {
// Initialize our Metal primitives
guard
let device = MTLCreateSystemDefaultDevice(),
let commandQueue = device.makeCommandQueue() else {
fatalError("GPU not available")
}
self.device = device
self.commandQueue = commandQueue
super.init()
// Setup the view to point to this renderer
metalView.device = device
metalView.delegate = self
metalView.clearColor = MTLClearColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
}
}
}
extension Ghostty.Renderer: MTKViewDelegate {
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
guard
let commandBuffer = self.commandQueue.makeCommandBuffer(),
let descriptor = view.currentRenderPassDescriptor,
let renderEncoder =
commandBuffer.makeRenderCommandEncoder(
descriptor: descriptor) else {
return
}
renderEncoder.endEncoding()
guard let drawable = view.currentDrawable else { return }
commandBuffer.present(drawable)
commandBuffer.commit()
}
}

View File

@@ -32,7 +32,7 @@ extension Ghostty {
content(surfaceView)
}
}
struct SurfaceWrapper: View {
// The surface to create a view for. This must be created upstream. As long as this
// remains the same, the surface that is being rendered remains the same.

View File

@@ -0,0 +1,33 @@
import SwiftUI
import MetalKit
/// Implements the logic for a metal view used by MetalView.
protocol MetalViewRenderer: MTKViewDelegate {
init(metalView: MTKView)
}
/// Renders an MTKView with the given renderer class.
struct MetalView<R: MetalViewRenderer>: View {
@State private var metalView = MTKView()
@State private var renderer: R?
var body: some View {
MetalViewRepresentable(metalView: $metalView)
.onAppear { renderer = R(metalView: metalView) }
}
}
fileprivate struct MetalViewRepresentable: NSViewRepresentable {
@Binding var metalView: MTKView
func makeNSView(context: Context) -> some NSView {
metalView
}
func updateNSView(_ view: NSViewType, context: Context) {
updateMetalView()
}
func updateMetalView() {
}
}