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

@ -26,6 +26,8 @@
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58882ACDE6CA00508D2C /* ServiceProvider.swift */; };
A571AB1D2A206FCF00248498 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59444F629A2ED5200725BBA /* SettingsView.swift */; };
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */; };
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5D02AE0DEA7009128F3 /* MetalView.swift */; };
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; };
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */; };
@ -59,6 +61,8 @@
A56D58882ACDE6CA00508D2C /* ServiceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceProvider.swift; sourceTree = "<group>"; };
A571AB1C2A206FC600248498 /* Ghostty-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Ghostty-Info.plist"; sourceTree = "<group>"; };
A59444F629A2ED5200725BBA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorView.swift; sourceTree = "<group>"; };
A59FB5D02AE0DEA7009128F3 /* MetalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalView.swift; sourceTree = "<group>"; };
A5A1F8842A489D6800D1E8BC /* terminfo */ = {isa = PBXFileReference; lastKnownFileType = folder; name = terminfo; path = "../zig-out/share/terminfo"; sourceTree = "<group>"; };
A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; };
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -115,6 +119,7 @@
children = (
A5CEAFFE29C2410700646FDA /* Backport.swift */,
8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */,
A59FB5D02AE0DEA7009128F3 /* MetalView.swift */,
A5FECBD829D2010400022361 /* WindowAccessor.swift */,
A5CEAFDA29B8005900646FDA /* SplitView */,
);
@ -151,6 +156,7 @@
A55B7BB729B6F53A0055DE60 /* Package.swift */,
A55B7BB529B6F47F0055DE60 /* AppState.swift */,
A55B7BBB29B6FC330055DE60 /* SurfaceView.swift */,
A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */,
A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */,
A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */,
A55B7BBD29B701360055DE60 /* Ghostty.SplitView.swift */,
@ -288,6 +294,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
A53426392A7DC55C00EBB7A2 /* PrimaryWindowManager.swift in Sources */,
85DE1C922A6A3DCA00493853 /* PrimaryWindow.swift in Sources */,
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
@ -304,6 +311,7 @@
A55B7BB629B6F47F0055DE60 /* AppState.swift in Sources */,
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */,
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */,
A55685E029A03A9F004303CE /* AppError.swift in Sources */,
A5FECBD929D2010400022361 /* WindowAccessor.swift in Sources */,
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,

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() {
}
}