MetalShader.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. /******************************************************************************
  2. Copyright (C) 2024 by Patrick Heyer <[email protected]>
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ******************************************************************************/
  14. import Foundation
  15. import Metal
  16. class MetalShader {
  17. /// This class wraps a single uniform shader variable, which will hold the data associated with the uniform updated
  18. /// by `libobs` at each render loop, which is then converted and set as vertex or fragment bytes for a render pass
  19. /// by the ``MetalDevice/draw`` function.
  20. class ShaderUniform {
  21. let name: String
  22. let gsType: gs_shader_param_type
  23. fileprivate let textureSlot: Int
  24. var samplerState: MTLSamplerState?
  25. fileprivate let byteOffset: Int
  26. var currentValues: [UInt8]?
  27. var defaultValues: [UInt8]?
  28. fileprivate var hasUpdates: Bool
  29. init(
  30. name: String, gsType: gs_shader_param_type, textureSlot: Int, samplerState: MTLSamplerState?,
  31. byteOffset: Int
  32. ) {
  33. self.name = name
  34. self.gsType = gsType
  35. self.textureSlot = textureSlot
  36. self.samplerState = samplerState
  37. self.byteOffset = byteOffset
  38. self.currentValues = nil
  39. self.defaultValues = nil
  40. self.hasUpdates = false
  41. }
  42. /// Sets the data for the shader uniform
  43. /// - Parameters:
  44. /// - data: Pointer to data of type `T`
  45. /// - size: Size of data available at the pointer provided by `data`
  46. ///
  47. /// This function will reinterpet the data provided by the pointer as raw bytes and store it as raw bytes on
  48. /// the Uniform.
  49. public func setParameter<T>(data: UnsafePointer<T>?, size: Int) {
  50. guard let data else {
  51. assertionFailure(
  52. "MetalShader.ShaderUniform: Attempted to set a shader parameter with an empty data pointer")
  53. return
  54. }
  55. data.withMemoryRebound(to: UInt8.self, capacity: size) {
  56. self.currentValues = Array(UnsafeBufferPointer<UInt8>(start: $0, count: size))
  57. }
  58. hasUpdates = true
  59. }
  60. }
  61. /// This struct serves as a data container to communicate shader meta data between the ``OBSShader`` shader
  62. /// transpiler and the actual ``MetalShader`` instances created with them.
  63. struct ShaderData {
  64. let uniforms: [ShaderUniform]
  65. let bufferOrder: [MetalBuffer.BufferDataType]
  66. let vertexDescriptor: MTLVertexDescriptor?
  67. let samplerDescriptors: [MTLSamplerDescriptor]?
  68. let bufferSize: Int
  69. let textureCount: Int
  70. }
  71. private weak var device: MetalDevice?
  72. let source: String
  73. private var uniformData: [UInt8]
  74. private var uniformSize: Int
  75. private var uniformBuffer: MTLBuffer?
  76. private let library: MTLLibrary
  77. let function: MTLFunction
  78. var uniforms: [ShaderUniform]
  79. var vertexDescriptor: MTLVertexDescriptor?
  80. var textureCount = 0
  81. var samplers: [MTLSamplerState]?
  82. let type: MTLFunctionType
  83. let bufferOrder: [MetalBuffer.BufferDataType]
  84. var viewProjection: ShaderUniform?
  85. init(device: MetalDevice, source: String, type: MTLFunctionType, data: ShaderData) throws {
  86. self.device = device
  87. self.source = source
  88. self.type = type
  89. self.uniforms = data.uniforms
  90. self.bufferOrder = data.bufferOrder
  91. self.uniformSize = (data.bufferSize + 0x0F) & ~0x0F
  92. self.uniformData = [UInt8](repeating: 0, count: self.uniformSize)
  93. self.textureCount = data.textureCount
  94. switch type {
  95. case .vertex:
  96. guard let descriptor = data.vertexDescriptor else {
  97. throw MetalError.MetalShaderError.missingVertexDescriptor
  98. }
  99. self.vertexDescriptor = descriptor
  100. self.viewProjection = self.uniforms.first(where: { $0.name == "ViewProj" })
  101. case .fragment:
  102. guard let samplerDescriptors = data.samplerDescriptors else {
  103. throw MetalError.MetalShaderError.missingSamplerDescriptors
  104. }
  105. var samplers = [MTLSamplerState]()
  106. samplers.reserveCapacity(samplerDescriptors.count)
  107. for descriptor in samplerDescriptors {
  108. guard let samplerState = device.device.makeSamplerState(descriptor: descriptor) else {
  109. throw MetalError.MTLDeviceError.samplerStateCreationFailure
  110. }
  111. samplers.append(samplerState)
  112. }
  113. self.samplers = samplers
  114. default:
  115. fatalError("MetalShader: Unsupported shader type \(type)")
  116. }
  117. do {
  118. library = try device.device.makeLibrary(source: source, options: nil)
  119. } catch {
  120. throw MetalError.MTLDeviceError.shaderCompilationFailure("Failed to create shader library")
  121. }
  122. guard let function = library.makeFunction(name: "_main") else {
  123. throw MetalError.MTLDeviceError.shaderCompilationFailure("Failed to create '_main' function")
  124. }
  125. self.function = function
  126. }
  127. /// Updates the Metal-specific data associated with a ``ShaderUniform`` with the raw bytes provided by `libobs`
  128. /// - Parameter uniform: Inout reference to the ``ShaderUniform`` instance
  129. ///
  130. /// Uniform data is provided by `libobs` precisely in the format required by the shader (and interpreted by
  131. /// `libobs`), which means that the raw bytes stored on the ``ShaderUniform`` are usually already in the correct
  132. /// order and can be used without reinterpretation.
  133. ///
  134. /// The exception to this rule is data for textures, which represents a copy of a `gs_shader_texture` struct that
  135. /// itself contains the pointer address of an `OpaquePointer` for a ``MetalTexture`` instance.
  136. private func updateUniform(uniform: inout ShaderUniform) {
  137. guard let device = self.device else { return }
  138. guard let currentValues = uniform.currentValues else { return }
  139. if uniform.gsType == GS_SHADER_PARAM_TEXTURE {
  140. var textureObject: OpaquePointer?
  141. var isSrgb = false
  142. currentValues.withUnsafeBufferPointer {
  143. $0.baseAddress?.withMemoryRebound(to: gs_shader_texture.self, capacity: 1) {
  144. textureObject = $0.pointee.tex
  145. isSrgb = $0.pointee.srgb
  146. }
  147. }
  148. if let textureObject {
  149. let texture: MetalTexture = unretained(UnsafeRawPointer(textureObject))
  150. if texture.sRGBtexture != nil, isSrgb {
  151. device.renderState.textures[uniform.textureSlot] = texture.sRGBtexture!
  152. } else {
  153. device.renderState.textures[uniform.textureSlot] = texture.texture
  154. }
  155. }
  156. if let samplerState = uniform.samplerState {
  157. device.renderState.samplers[uniform.textureSlot] = samplerState
  158. uniform.samplerState = nil
  159. }
  160. } else {
  161. if uniform.hasUpdates {
  162. let startIndex = uniform.byteOffset
  163. let endIndex = uniform.byteOffset + currentValues.count
  164. uniformData.replaceSubrange(startIndex..<endIndex, with: currentValues)
  165. }
  166. }
  167. uniform.hasUpdates = false
  168. }
  169. /// Creates a new buffer with the provided data or updates an existing buffer with the provided data
  170. /// - Parameters:
  171. /// - buffer: Reference to a buffer variable to either receive the new buffer or provide an existing buffer
  172. /// - data: Raw byte data array
  173. private func createOrUpdateBuffer(buffer: inout MTLBuffer?, data: inout [UInt8]) {
  174. guard let device = self.device else { return }
  175. let size = MemoryLayout<UInt8>.size * data.count
  176. let alignedSize = (size + 0x0F) & ~0x0F
  177. if buffer != nil {
  178. if buffer!.length == alignedSize {
  179. buffer!.contents().copyMemory(from: data, byteCount: size)
  180. return
  181. }
  182. }
  183. buffer = device.device.makeBuffer(bytes: data, length: alignedSize)
  184. }
  185. /// Sets uniform data for a current render encoder either directly as a buffer
  186. /// - Parameter encoder: `MTLRenderCommandEncoder` for a render pass that requires the uniform data
  187. ///
  188. /// Uniform data will be uploaded at index 30 (the very last available index) and is available as a single
  189. /// contiguous block of data. Uniforms are declared as structs in the Metal Shaders and explicitly passed into
  190. /// each function that requires access to them.
  191. func uploadShaderParameters(encoder: MTLRenderCommandEncoder) {
  192. for var uniform in uniforms {
  193. updateUniform(uniform: &uniform)
  194. }
  195. guard uniformSize > 0 else {
  196. return
  197. }
  198. switch function.functionType {
  199. case .vertex:
  200. switch uniformData.count {
  201. case 0..<4096: encoder.setVertexBytes(&uniformData, length: uniformData.count, index: 30)
  202. default:
  203. createOrUpdateBuffer(buffer: &uniformBuffer, data: &uniformData)
  204. #if DEBUG
  205. uniformBuffer?.label = "Vertex shader uniform buffer"
  206. #endif
  207. encoder.setVertexBuffer(uniformBuffer, offset: 0, index: 30)
  208. }
  209. case .fragment:
  210. switch uniformData.count {
  211. case 0..<4096: encoder.setFragmentBytes(&uniformData, length: uniformData.count, index: 30)
  212. default:
  213. createOrUpdateBuffer(buffer: &uniformBuffer, data: &uniformData)
  214. #if DEBUG
  215. uniformBuffer?.label = "Fragment shader uniform buffer"
  216. #endif
  217. encoder.setFragmentBuffer(uniformBuffer, offset: 0, index: 30)
  218. }
  219. default:
  220. fatalError("MetalShader: Unsupported shader type \(function.functionType)")
  221. }
  222. }
  223. /// Gets an opaque pointer for the ``MetalShader`` instance and increases its reference count by one
  224. /// - Returns: `OpaquePointer` to class instance
  225. ///
  226. /// > Note: Use this method when the instance is to be shared via an `OpaquePointer` and needs to be retained. Any
  227. /// opaque pointer shared this way needs to be converted into a retained reference again to ensure automatic
  228. /// deinitialization by the Swift runtime.
  229. func getRetained() -> OpaquePointer {
  230. let retained = Unmanaged.passRetained(self).toOpaque()
  231. return OpaquePointer(retained)
  232. }
  233. /// Gets an opaque pointer for the ``MetalShader`` instance without increasing its reference count
  234. /// - Returns: `OpaquePointer` to class instance
  235. func getUnretained() -> OpaquePointer {
  236. let unretained = Unmanaged.passUnretained(self).toOpaque()
  237. return OpaquePointer(unretained)
  238. }
  239. }