-
-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merging visionos branch into main #87
Changes from all commits
aea6005
29f462f
b6e14c8
a84f3df
b093e8e
d9519bc
7799fe0
87e0ddb
5a72b47
5c9a87e
75acd89
b634ded
1a78c74
f900f0b
2c1667c
99a9e15
2f1a96e
dc4c553
f4b8846
07b8185
b4d73f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ import Metal | |
import MetalKit | ||
|
||
// This must be in sync with the definition in shaders.metal | ||
public struct FragmentConstants { | ||
struct FragmentConstants { | ||
public var foregroundColor: SIMD4<Float> | ||
public var backgroundColor: SIMD4<Float> | ||
public var isFFT: Bool | ||
|
@@ -17,13 +17,15 @@ public struct FragmentConstants { | |
public var padding: Int = 0 | ||
} | ||
|
||
public class FloatPlot: MTKView, MTKViewDelegate { | ||
let waveformTexture: MTLTexture! | ||
class FloatPlot: NSObject { | ||
var waveformTexture: MTLTexture? | ||
let commandQueue: MTLCommandQueue! | ||
let pipelineState: MTLRenderPipelineState! | ||
let bufferSampleCount: Int | ||
var bufferSampleCount: Int | ||
var dataCallback: () -> [Float] | ||
var constants: FragmentConstants | ||
let layerRenderPassDescriptor: MTLRenderPassDescriptor | ||
let device = MTLCreateSystemDefaultDevice() | ||
|
||
public init(frame frameRect: CGRect, | ||
constants: FragmentConstants, | ||
|
@@ -32,15 +34,6 @@ public class FloatPlot: MTKView, MTKViewDelegate { | |
self.constants = constants | ||
bufferSampleCount = Int(frameRect.width) | ||
|
||
let desc = MTLTextureDescriptor() | ||
desc.textureType = .type1D | ||
desc.width = Int(frameRect.width) | ||
desc.pixelFormat = .r32Float | ||
assert(desc.height == 1) | ||
assert(desc.depth == 1) | ||
|
||
let device = MTLCreateSystemDefaultDevice() | ||
waveformTexture = device?.makeTexture(descriptor: desc) | ||
commandQueue = device!.makeCommandQueue() | ||
|
||
let library = try! device?.makeDefaultLibrary(bundle: Bundle.module) | ||
|
@@ -51,7 +44,6 @@ public class FloatPlot: MTKView, MTKViewDelegate { | |
let pipelineStateDescriptor = MTLRenderPipelineDescriptor() | ||
pipelineStateDescriptor.vertexFunction = vertexProgram | ||
pipelineStateDescriptor.fragmentFunction = fragmentProgram | ||
pipelineStateDescriptor.sampleCount = 1 | ||
|
||
let colorAttachment = pipelineStateDescriptor.colorAttachments[0]! | ||
colorAttachment.pixelFormat = .bgra8Unorm | ||
|
@@ -63,23 +55,45 @@ public class FloatPlot: MTKView, MTKViewDelegate { | |
|
||
pipelineState = try! device!.makeRenderPipelineState(descriptor: pipelineStateDescriptor) | ||
|
||
super.init(frame: frameRect, device: device) | ||
|
||
clearColor = .init(red: 0.0, green: 0.0, blue: 0.0, alpha: 0) | ||
|
||
delegate = self | ||
layerRenderPassDescriptor = MTLRenderPassDescriptor() | ||
layerRenderPassDescriptor.colorAttachments[0].loadAction = .clear | ||
layerRenderPassDescriptor.colorAttachments[0].storeAction = .store | ||
layerRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0); | ||
} | ||
|
||
@available(*, unavailable) | ||
required init(coder _: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
func resize(width: Int) { | ||
|
||
if width == 0 { | ||
return | ||
} | ||
|
||
let desc = MTLTextureDescriptor() | ||
desc.textureType = .type1D | ||
desc.width = width | ||
desc.pixelFormat = .r32Float | ||
assert(desc.height == 1) | ||
assert(desc.depth == 1) | ||
|
||
waveformTexture = device?.makeTexture(descriptor: desc) | ||
bufferSampleCount = width | ||
|
||
} | ||
|
||
func updateWaveform(samples: [Float]) { | ||
if samples.count == 0 { | ||
return | ||
} | ||
|
||
guard let waveformTexture else { | ||
print("⚠️ updateWaveform: waveformTexture is nil") | ||
return | ||
} | ||
|
||
var resampled = [Float](repeating: 0, count: bufferSampleCount) | ||
|
||
for i in 0 ..< bufferSampleCount { | ||
|
@@ -97,24 +111,60 @@ public class FloatPlot: MTKView, MTKViewDelegate { | |
} | ||
} | ||
|
||
public func mtkView(_: MTKView, drawableSizeWillChange _: CGSize) { | ||
// We may want to resize the texture. | ||
func encode(to commandBuffer: MTLCommandBuffer, pass: MTLRenderPassDescriptor) { | ||
guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: pass) else { return } | ||
|
||
encoder.setRenderPipelineState(pipelineState) | ||
encoder.setFragmentTexture(waveformTexture, index: 0) | ||
assert(MemoryLayout<FragmentConstants>.size == 48) | ||
encoder.setFragmentBytes(&constants, length: MemoryLayout<FragmentConstants>.size, index: 0) | ||
encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) | ||
encoder.endEncoding() | ||
} | ||
|
||
public func draw(in view: MTKView) { | ||
func draw(to layer: CAMetalLayer) { | ||
|
||
updateWaveform(samples: dataCallback()) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace) |
||
let size = layer.drawableSize | ||
let w = Float(size.width) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Identifier Name Violation: Variable name should be between 3 and 40 characters long: 'w' (identifier_name) |
||
let h = Float(size.height) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Identifier Name Violation: Variable name should be between 3 and 40 characters long: 'h' (identifier_name) |
||
// let scale = Float(view.contentScaleFactor) | ||
|
||
if let commandBuffer = commandQueue.makeCommandBuffer() { | ||
if let renderPassDescriptor = currentRenderPassDescriptor { | ||
guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { return } | ||
if w == 0 || h == 0 { | ||
return | ||
} | ||
|
||
guard let commandBuffer = commandQueue.makeCommandBuffer() else { | ||
return | ||
} | ||
|
||
if let currentDrawable = layer.nextDrawable() { | ||
|
||
layerRenderPassDescriptor.colorAttachments[0].texture = currentDrawable.texture | ||
|
||
encoder.setRenderPipelineState(pipelineState) | ||
encoder.setFragmentTexture(waveformTexture, index: 0) | ||
assert(MemoryLayout<FragmentConstants>.size == 48) | ||
encoder.setFragmentBytes(&constants, length: MemoryLayout<FragmentConstants>.size, index: 0) | ||
encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) | ||
encoder.endEncoding() | ||
encode(to: commandBuffer, pass: layerRenderPassDescriptor) | ||
|
||
commandBuffer.present(currentDrawable) | ||
} else { | ||
print("⚠️ couldn't get drawable") | ||
} | ||
commandBuffer.commit() | ||
} | ||
} | ||
|
||
#if !os(visionOS) | ||
extension FloatPlot: MTKViewDelegate { | ||
public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { | ||
resize(width: Int(size.width)) | ||
} | ||
|
||
public func draw(in view: MTKView) { | ||
updateWaveform(samples: dataCallback()) | ||
|
||
if let commandBuffer = commandQueue.makeCommandBuffer() { | ||
if let renderPassDescriptor = view.currentRenderPassDescriptor { | ||
encode(to: commandBuffer, pass: renderPassDescriptor) | ||
if let drawable = view.currentDrawable { | ||
commandBuffer.present(drawable) | ||
} | ||
|
@@ -125,3 +175,38 @@ public class FloatPlot: MTKView, MTKViewDelegate { | |
} | ||
} | ||
} | ||
#endif | ||
|
||
#if !os(visionOS) | ||
public class FloatPlotCoordinator { | ||
var renderer: FloatPlot | ||
|
||
init(renderer: FloatPlot) { | ||
self.renderer = renderer | ||
} | ||
|
||
var view: MTKView { | ||
let view = MTKView(frame: CGRect(x: 0, y: 0, width: 1024, height: 1024), device: renderer.device) | ||
view.clearColor = .init(red: 0.0, green: 0.0, blue: 0.0, alpha: 0) | ||
view.delegate = renderer | ||
return view | ||
} | ||
} | ||
#else | ||
public class FloatPlotCoordinator { | ||
var renderer: FloatPlot | ||
|
||
init(renderer: FloatPlot) { | ||
self.renderer = renderer | ||
} | ||
|
||
var view: MetalView { | ||
let view = MetalView(frame: CGRect(x: 0, y: 0, width: 1024, height: 1024)) | ||
view.renderer = renderer | ||
view.metalLayer.pixelFormat = .bgra8Unorm | ||
view.metalLayer.isOpaque = false | ||
view.createDisplayLink() | ||
return view | ||
} | ||
} | ||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// 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: FloatPlot? | ||
var displayLink: CADisplayLink? | ||
|
||
@objc static override var layerClass: AnyClass { | ||
CAMetalLayer.self | ||
} | ||
|
||
var metalLayer: CAMetalLayer { | ||
layer as! CAMetalLayer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Force Cast Violation: Force casts should be avoided. (force_cast) |
||
} | ||
|
||
func createDisplayLink() { | ||
displayLink = CADisplayLink(target: self, | ||
selector: #selector(render)) | ||
|
||
displayLink?.add(to: .current, | ||
forMode: .default) | ||
} | ||
|
||
override func draw(_ rect: CGRect) { | ||
render() | ||
} | ||
|
||
override func draw(_ layer: CALayer, in ctx: CGContext) { | ||
render() | ||
} | ||
|
||
override func display(_ layer: CALayer) { | ||
render() | ||
} | ||
|
||
@objc func render() { | ||
guard let renderer else { | ||
print("⚠️ no renderer") | ||
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 | ||
renderer?.resize(width: Int(newSize.width)) | ||
|
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing Semicolon Violation: Lines should not have trailing semicolons. (trailing_semicolon)