diff --git a/Sources/Waveform/MetalView.swift b/Sources/Waveform/MetalView.swift new file mode 100644 index 0000000..21e540f --- /dev/null +++ b/Sources/Waveform/MetalView.swift @@ -0,0 +1,76 @@ +// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/ + +#if os(iOS) || os(visionOS) +import UIKit +class MetalView: UIView { + + var renderer: Renderer? + + @objc static override var layerClass: AnyClass { + CAMetalLayer.self + } + + var metalLayer: CAMetalLayer { + layer as! CAMetalLayer + } + + override func draw(_ rect: CGRect) { + render() + } + + override func draw(_ layer: CALayer, in ctx: CGContext) { + render() + } + + override func display(_ layer: CALayer) { + render() + } + + func render() { + guard let renderer else { return } + renderer.draw(to: metalLayer) + } + + func resizeDrawable() { + + var newSize = bounds.size + newSize.width *= contentScaleFactor + newSize.height *= contentScaleFactor + + if newSize.width <= 0 || newSize.height <= 0 { + return + } + + if newSize.width == metalLayer.drawableSize.width && + newSize.height == metalLayer.drawableSize.height { + return + } + + metalLayer.drawableSize = newSize + + setNeedsDisplay() + } + + @objc override var frame: CGRect { + get { super.frame } + set { + super.frame = newValue + resizeDrawable() + } + } + + @objc override func layoutSubviews() { + super.layoutSubviews() + resizeDrawable() + } + + @objc override var bounds: CGRect { + get { super.bounds } + set { + super.bounds = newValue + resizeDrawable() + } + } + +} +#endif diff --git a/Sources/Waveform/Renderer.swift b/Sources/Waveform/Renderer.swift index e324145..b179790 100644 --- a/Sources/Waveform/Renderer.swift +++ b/Sources/Waveform/Renderer.swift @@ -20,7 +20,7 @@ struct Constants { } } -class Renderer: NSObject, MTKViewDelegate { +class Renderer: NSObject { var device: MTLDevice! var queue: MTLCommandQueue! var pipeline: MTLRenderPipelineState! @@ -36,6 +36,8 @@ class Renderer: NSObject, MTKViewDelegate { var start = 0 var length = 0 + let layerRenderPassDescriptor: MTLRenderPassDescriptor + init(device: MTLDevice) { self.device = device queue = device.makeCommandQueue() @@ -59,11 +61,14 @@ class Renderer: NSObject, MTKViewDelegate { minBuffers = [device.makeBuffer([0])!] maxBuffers = [device.makeBuffer([0])!] + layerRenderPassDescriptor = MTLRenderPassDescriptor() + layerRenderPassDescriptor.colorAttachments[0].loadAction = .clear + layerRenderPassDescriptor.colorAttachments[0].storeAction = .store + layerRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1, 1, 1, 1); + super.init() } - func mtkView(_: MTKView, drawableSizeWillChange _: CGSize) {} - func selectBuffers(width: CGFloat) -> (MTLBuffer?, MTLBuffer?) { var level = 0 for (minBuffer, maxBuffer) in zip(minBuffers, maxBuffers) { @@ -115,8 +120,9 @@ class Renderer: NSObject, MTKViewDelegate { enc.endEncoding() } - func draw(in view: MTKView) { - let size = view.frame.size + func draw(to layer: CAMetalLayer) { + + let size = layer.drawableSize let w = Float(size.width) let h = Float(size.height) // let scale = Float(view.contentScaleFactor) @@ -135,10 +141,15 @@ class Renderer: NSObject, MTKViewDelegate { semaphore.signal() } - if let renderPassDescriptor = view.currentRenderPassDescriptor, let currentDrawable = view.currentDrawable { - encode(to: commandBuffer, pass: renderPassDescriptor, width: size.width) + if let currentDrawable = layer.nextDrawable() { + + layerRenderPassDescriptor.colorAttachments[0].texture = currentDrawable.texture + + encode(to: commandBuffer, pass: layerRenderPassDescriptor, width: size.width) commandBuffer.present(currentDrawable) + } else { + print("⚠️ couldn't get drawable") } commandBuffer.commit() } @@ -157,6 +168,41 @@ class Renderer: NSObject, MTKViewDelegate { } } +#if !os(visionOS) +extension Renderer: MTKViewDelegate { + + func mtkView(_: MTKView, drawableSizeWillChange _: CGSize) {} + + func draw(in view: MTKView) { + let size = view.frame.size + let w = Float(size.width) + let h = Float(size.height) + // let scale = Float(view.contentScaleFactor) + + if w == 0 || h == 0 { + return + } + + // use semaphore to encode 3 frames ahead + _ = inflightSemaphore.wait(timeout: DispatchTime.distantFuture) + + let commandBuffer = queue.makeCommandBuffer()! + + let semaphore = inflightSemaphore + commandBuffer.addCompletedHandler { _ in + semaphore.signal() + } + + if let renderPassDescriptor = view.currentRenderPassDescriptor, let currentDrawable = view.currentDrawable { + encode(to: commandBuffer, pass: renderPassDescriptor, width: size.width) + + commandBuffer.present(currentDrawable) + } + commandBuffer.commit() + } +} +#endif + func makeBuffers(device: MTLDevice, samples: SampleBuffer) -> ([MTLBuffer], [MTLBuffer]) { var minSamples = samples.samples var maxSamples = samples.samples diff --git a/Sources/Waveform/Waveform.swift b/Sources/Waveform/Waveform.swift index c923d20..68e8f48 100644 --- a/Sources/Waveform/Waveform.swift +++ b/Sources/Waveform/Waveform.swift @@ -108,12 +108,10 @@ public struct Waveform: UIViewRepresentable { /// Required by UIViewRepresentable public func makeUIView(context: Context) -> some UIView { - let metalView = MTKView(frame: CGRect(x: 0, y: 0, width: 1024, height: 768), - device: MTLCreateSystemDefaultDevice()!) - metalView.enableSetNeedsDisplay = true - metalView.isPaused = true - metalView.delegate = context.coordinator.renderer - metalView.layer.isOpaque = false + let metalView = MetalView(frame: CGRect(x: 0, y: 0, width: 1024, height: 768)) + metalView.renderer = context.coordinator.renderer + metalView.metalLayer.pixelFormat = .bgra8Unorm + metalView.metalLayer.isOpaque = false return metalView }