OBSShader.swift 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603
  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. private enum SampleVariant {
  17. case load
  18. case sample
  19. case sampleBias
  20. case sampleGrad
  21. case sampleLevel
  22. }
  23. private struct VariableType: OptionSet {
  24. var rawValue: UInt
  25. static let typeUniform = VariableType(rawValue: 1 << 0)
  26. static let typeStruct = VariableType(rawValue: 1 << 1)
  27. static let typeStructMember = VariableType(rawValue: 1 << 2)
  28. static let typeInput = VariableType(rawValue: 1 << 3)
  29. static let typeOutput = VariableType(rawValue: 1 << 4)
  30. static let typeTexture = VariableType(rawValue: 1 << 5)
  31. static let typeConstant = VariableType(rawValue: 1 << 6)
  32. }
  33. private struct OBSShaderFunction {
  34. let name: String
  35. var returnType: String
  36. var typeMap: [String: String]
  37. var requiresUniformBuffers: Bool
  38. var textures: [String]
  39. var samplers: [String]
  40. var arguments: [OBSShaderVariable]
  41. let gsFunction: UnsafeMutablePointer<shader_func>
  42. }
  43. private struct OBSShaderVariable {
  44. let name: String
  45. var type: String
  46. var mapping: String?
  47. var storageType: VariableType
  48. var requiredBy: Set<String>
  49. var returnedBy: Set<String>
  50. var isStage: Bool
  51. var attributeId: Int?
  52. var isConstant: Bool
  53. var isReference: Bool
  54. let gsVariable: UnsafeMutablePointer<shader_var>
  55. }
  56. private struct OBSShaderStruct {
  57. let name: String
  58. var storageType: VariableType
  59. var members: [OBSShaderVariable]
  60. let gsVariable: UnsafeMutablePointer<shader_struct>
  61. }
  62. private struct MSLTemplates {
  63. static let header = """
  64. #include <metal_stdlib>
  65. using namespace metal;
  66. """
  67. static let variable = "[qualifier] [type] [name] [mapping]"
  68. static let shaderStruct = """
  69. typedef struct {
  70. [variable]
  71. } [typename];
  72. """
  73. static let function = "[decorator] [type] [name]([parameters]) {[content]}"
  74. }
  75. private typealias ParserError = MetalError.OBSShaderParserError
  76. private typealias ShaderError = MetalError.OBSShaderError
  77. class OBSShader {
  78. private let type: MTLFunctionType
  79. private let content: String
  80. private let fileLocation: String
  81. private var parser: shader_parser
  82. private var parsed: Bool
  83. private var uniformsOrder = [String]()
  84. private var uniforms = [String: OBSShaderVariable]()
  85. private var structs = [String: OBSShaderStruct]()
  86. private var functionsOrder = [String]()
  87. private var functions = [String: OBSShaderFunction]()
  88. private var referenceVariables = [String]()
  89. var metaData: MetalShader.ShaderData?
  90. init(type: MTLFunctionType, content: String, fileLocation: String) throws {
  91. guard type == .vertex || type == .fragment else {
  92. throw ShaderError.unsupportedType
  93. }
  94. self.type = type
  95. self.content = content
  96. self.fileLocation = fileLocation
  97. self.parsed = false
  98. self.parser = shader_parser()
  99. try withUnsafeMutablePointer(to: &parser) {
  100. shader_parser_init($0)
  101. let result = shader_parse($0, content.cString(using: .utf8), content.cString(using: .utf8))
  102. let warnings = shader_parser_geterrors($0)
  103. if let warnings {
  104. throw ShaderError.parseError(String(cString: warnings))
  105. }
  106. if !result {
  107. throw ShaderError.parseFail("Shader failed to parse: \(fileLocation)")
  108. } else {
  109. self.parsed = true
  110. }
  111. }
  112. }
  113. /// Transpiles a `libobs` effect string into a Metal Shader Language (MSL) string
  114. /// - Returns: MSL string representing the transpiled shader
  115. func transpiled() throws -> String {
  116. try analyzeUniforms()
  117. try analyzeParameters()
  118. try analyzeFunctions()
  119. let uniforms = try transpileUniforms()
  120. let structs = try transpileStructs()
  121. let functions = try transpileFunctions()
  122. self.metaData = try buildMetadata()
  123. return [MSLTemplates.header, uniforms, structs, functions].joined(separator: "\n\n")
  124. }
  125. /// Builds a metadata object for the current shader
  126. /// - Returns: ``ShaderData`` object with the shader metadata
  127. ///
  128. /// The effects used by `libobs` are written in HLSL with some customizations to allow multiple shaders within the
  129. /// same effects file (which is supported natively by MSL). As MSL does not support "global" variables, uniforms
  130. /// have to be provided explicitly via buffers and the data inside those buffers needs to be laid out in the correct
  131. /// way.
  132. ///
  133. /// Uniforms are converted into `struct` objects in the shader files and as MSL is based on C++14, these structs
  134. /// will have a size, stride, and alignment, set by the compiler. Thus the uniform data used by the shader needs to
  135. /// be laid out in the buffer according to this alignment.
  136. ///
  137. /// The layout of vertex buffer data also needs to be communicated using `MTLVertexDescriptor` instances for vertex
  138. /// shaders and `MTLSamplerState` instances for fragment shaders. Both will be created and set up in a
  139. /// ``ShaderData`` which is used to create the actual ``MetalShader`` object.
  140. private func buildMetadata() throws -> MetalShader.ShaderData {
  141. var uniformInfo = [MetalShader.ShaderUniform]()
  142. var textureSlot = 0
  143. var uniformBufferSize = 0
  144. /// The order of buffers and uniforms is "load-bearing" as the order (and thus alignment and offsets) of
  145. /// uniforms in the corresponding uniforms struct are
  146. /// influenced by it.
  147. for uniformName in uniformsOrder {
  148. guard let uniform = uniforms[uniformName] else {
  149. throw ParserError.parseFail("No uniform data found for '\(uniformName)'")
  150. }
  151. let gsType = get_shader_param_type(uniform.gsVariable.pointee.type)
  152. let isTexture = uniform.storageType.contains(.typeTexture)
  153. let byteSize: Int
  154. let alignment: Int
  155. let bufferOffset: Int
  156. if isTexture {
  157. byteSize = 0
  158. alignment = 0
  159. bufferOffset = uniformBufferSize
  160. } else {
  161. byteSize = gsType.mtlSize
  162. alignment = gsType.mtlAlignment
  163. bufferOffset = (uniformBufferSize + (alignment - 1)) & ~(alignment - 1)
  164. }
  165. let shaderUniform = MetalShader.ShaderUniform(
  166. name: uniform.name,
  167. gsType: gsType,
  168. textureSlot: (isTexture ? textureSlot : 0),
  169. samplerState: nil,
  170. byteOffset: bufferOffset
  171. )
  172. shaderUniform.defaultValues = Array(
  173. UnsafeMutableBufferPointer(
  174. start: uniform.gsVariable.pointee.default_val.array,
  175. count: uniform.gsVariable.pointee.default_val.num)
  176. )
  177. shaderUniform.currentValues = shaderUniform.defaultValues
  178. uniformBufferSize = bufferOffset + byteSize
  179. if isTexture {
  180. textureSlot += 1
  181. }
  182. uniformInfo.append(shaderUniform)
  183. }
  184. guard let mainFunction = functions["main"] else {
  185. throw ParserError.missingMainFunction
  186. }
  187. let parameterMapper = { (mapping: String) -> MetalBuffer.BufferDataType? in
  188. switch mapping {
  189. case "POSITION":
  190. .vertex
  191. case "NORMAL":
  192. .normal
  193. case "TANGENT":
  194. .tangent
  195. case "COLOR":
  196. .color
  197. case _ where mapping.hasPrefix("TEXCOORD"):
  198. .texcoord
  199. default:
  200. .none
  201. }
  202. }
  203. let descriptorMapper = { (parameter: OBSShaderVariable) -> (MTLVertexFormat, Int)? in
  204. guard let mapping = parameter.mapping else {
  205. return nil
  206. }
  207. let type = parameter.type
  208. switch mapping {
  209. case "COLOR":
  210. return (.float4, MemoryLayout<vec4>.size)
  211. case "POSITION", "NORMAL", "TANGENT":
  212. return (.float4, MemoryLayout<vec4>.size)
  213. case _ where mapping.hasPrefix("TEXCOORD"):
  214. guard let numCoordinates = type[type.index(type.startIndex, offsetBy: 5)].wholeNumberValue else {
  215. assertionFailure("Unsupported type \(type) for texture parameter")
  216. return nil
  217. }
  218. let format: MTLVertexFormat =
  219. switch numCoordinates {
  220. case 0: .float
  221. case 2: .float2
  222. case 3: .float3
  223. case 4: .float4
  224. default: .invalid
  225. }
  226. guard format != .invalid else {
  227. assertionFailure("OBSShader: Unsupported amount of texture coordinates '\(numCoordinates)'")
  228. return nil
  229. }
  230. return (format, MemoryLayout<Float32>.size * numCoordinates)
  231. case "VERTEXID":
  232. return nil
  233. default:
  234. assertionFailure("OBSShader: Unsupported mapping \(mapping)")
  235. return nil
  236. }
  237. }
  238. switch type {
  239. case .vertex:
  240. var bufferOrder = [MetalBuffer.BufferDataType]()
  241. var descriptorData = [(MTLVertexFormat, Int)?]()
  242. let descriptor = MTLVertexDescriptor()
  243. for argument in mainFunction.arguments {
  244. if argument.storageType.contains(.typeStruct) {
  245. let actualStructType = argument.type.replacingOccurrences(of: "_In", with: "")
  246. guard let shaderStruct = structs[actualStructType] else {
  247. throw ParserError.parseFail("Shader function without struct metadata encountered ")
  248. }
  249. for shaderParameter in shaderStruct.members {
  250. if let mapping = shaderParameter.mapping, let mapping = parameterMapper(mapping) {
  251. bufferOrder.append(mapping)
  252. }
  253. if let description = descriptorMapper(shaderParameter) {
  254. descriptorData.append(description)
  255. }
  256. }
  257. } else {
  258. if let mapping = argument.mapping, let mapping = parameterMapper(mapping) {
  259. bufferOrder.append(mapping)
  260. }
  261. if let description = descriptorMapper(argument) {
  262. descriptorData.append(description)
  263. }
  264. }
  265. }
  266. let textureUnitCount = bufferOrder.filter({ $0 == .texcoord }).count
  267. for (attributeId, description) in descriptorData.filter({ $0 != nil }).enumerated() {
  268. descriptor.attributes[attributeId].bufferIndex = attributeId
  269. descriptor.attributes[attributeId].format = description!.0
  270. descriptor.layouts[attributeId].stride = description!.1
  271. }
  272. return MetalShader.ShaderData(
  273. uniforms: uniformInfo,
  274. bufferOrder: bufferOrder,
  275. vertexDescriptor: descriptor,
  276. samplerDescriptors: nil,
  277. bufferSize: uniformBufferSize,
  278. textureCount: textureUnitCount
  279. )
  280. case .fragment:
  281. var samplers = [MTLSamplerDescriptor]()
  282. for i in 0..<parser.samplers.num {
  283. let sampler: UnsafeMutablePointer<shader_sampler>? = parser.samplers.array.advanced(by: i)
  284. if let sampler {
  285. var sampler_info = gs_sampler_info()
  286. shader_sampler_convert(sampler, &sampler_info)
  287. let borderColor: MTLSamplerBorderColor =
  288. switch sampler_info.border_color {
  289. case 0x00_00_00_FF:
  290. .opaqueBlack
  291. case 0xFF_FF_FF_FF:
  292. .opaqueWhite
  293. default:
  294. .transparentBlack
  295. }
  296. let descriptor = MTLSamplerDescriptor()
  297. descriptor.borderColor = borderColor
  298. descriptor.maxAnisotropy = Int(sampler_info.max_anisotropy)
  299. guard
  300. let sAddressMode = sampler_info.address_u.mtlMode,
  301. let tAddressMode = sampler_info.address_v.mtlMode,
  302. let rAddressMode = sampler_info.address_w.mtlMode,
  303. let minMagFilter = sampler_info.filter.minMagFilter,
  304. let mipFilter = sampler_info.filter.mipFilter
  305. else {
  306. samplers.append(descriptor)
  307. continue
  308. }
  309. descriptor.sAddressMode = sAddressMode
  310. descriptor.tAddressMode = tAddressMode
  311. descriptor.rAddressMode = rAddressMode
  312. descriptor.minFilter = minMagFilter
  313. descriptor.magFilter = minMagFilter
  314. descriptor.mipFilter = mipFilter
  315. samplers.append(descriptor)
  316. }
  317. }
  318. return MetalShader.ShaderData(
  319. uniforms: uniformInfo,
  320. bufferOrder: [],
  321. vertexDescriptor: nil,
  322. samplerDescriptors: samplers,
  323. bufferSize: uniformBufferSize,
  324. textureCount: 0
  325. )
  326. default:
  327. throw ShaderError.unsupportedType
  328. }
  329. }
  330. /// Analyzes shader uniform parameters parsed by the ``libobs`` shader parser.
  331. ///
  332. /// Each global variable declared as a "uniform" is stored as an ``OBSShaderVariable`` struct, which will be
  333. /// extended with additional metadata by later analystics steps.
  334. ///
  335. /// This is necessary as MSL does not support global variables and all data needs to be explicitly provided
  336. /// via buffer objects, which requires these "unforms" to be wrapped into a single struct and passed as an explicit
  337. /// buffer object.
  338. private func analyzeUniforms() throws {
  339. for i in 0..<parser.params.num {
  340. let uniform: UnsafeMutablePointer<shader_var>? = parser.params.array.advanced(by: i)
  341. guard let uniform, let name = uniform.pointee.name, let type = uniform.pointee.type else {
  342. throw ParserError.parseFail("Uniform is missing name or type information")
  343. }
  344. let mapping: String? =
  345. if let mapping = uniform.pointee.mapping {
  346. String(cString: mapping)
  347. } else {
  348. nil
  349. }
  350. var data = OBSShaderVariable(
  351. name: String(cString: name),
  352. type: String(cString: type),
  353. mapping: mapping,
  354. storageType: .typeUniform,
  355. requiredBy: [],
  356. returnedBy: [],
  357. isStage: false,
  358. attributeId: 0,
  359. isConstant: (uniform.pointee.var_type == SHADER_VAR_CONST),
  360. isReference: false,
  361. gsVariable: uniform
  362. )
  363. if self.type == .fragment {
  364. /// A texture uniform does not contribute to the uniform buffer
  365. if data.type.hasPrefix("texture") {
  366. data.storageType.remove(.typeUniform)
  367. data.storageType.insert(.typeTexture)
  368. }
  369. }
  370. uniformsOrder.append(data.name)
  371. uniforms.updateValue(data, forKey: data.name)
  372. }
  373. }
  374. /// Analyzes struct parameter declarations parsed by the ``libobs`` shader parser.
  375. ///
  376. /// Structured data declarations are used to pass data into and out of shaders.
  377. ///
  378. /// Whereas HLSL allows one to use "InOut" structures with attribute mappings (e.g., using the same type defintion
  379. /// for vertex data going in and out of a vertex shader), MSL does not allow the mixing of input mappings and output
  380. /// mappings in the same type definition.
  381. ///
  382. /// Thus when the same struct type is used as an input argument for a function but also used as its output type, it
  383. /// needs to be split up into two separate types for the MSL shader.
  384. ///
  385. /// This function will first detect all struct type definitions in the shader file and then check if it is used as
  386. /// an input argument or function output and update the associated ``OBSShaderVariable`` structs accordingly.
  387. private func analyzeParameters() throws {
  388. for i in 0..<parser.structs.num {
  389. let shaderStruct: UnsafeMutablePointer<shader_struct>? = parser.structs.array.advanced(by: i)
  390. guard let shaderStruct, let name = shaderStruct.pointee.name else {
  391. throw ParserError.parseFail("Constant data struct has no name")
  392. }
  393. var parameters = [OBSShaderVariable]()
  394. parameters.reserveCapacity(shaderStruct.pointee.vars.num)
  395. for j in 0..<shaderStruct.pointee.vars.num {
  396. let variablePointer: UnsafeMutablePointer<shader_var>? = shaderStruct.pointee.vars.array.advanced(by: j)
  397. guard let variablePointer, let variableName = variablePointer.pointee.name,
  398. let variableType = variablePointer.pointee.type
  399. else {
  400. throw ParserError.parseFail("Constant data variable has no name")
  401. }
  402. let mapping: String? =
  403. if let variableMapping = variablePointer.pointee.mapping { String(cString: variableMapping) } else {
  404. nil
  405. }
  406. let variable = OBSShaderVariable(
  407. name: String(cString: variableName),
  408. type: String(cString: variableType),
  409. mapping: mapping,
  410. storageType: .typeStructMember,
  411. requiredBy: [],
  412. returnedBy: [],
  413. isStage: false,
  414. attributeId: nil,
  415. isConstant: false,
  416. isReference: false,
  417. gsVariable: variablePointer
  418. )
  419. parameters.append(variable)
  420. }
  421. let data = OBSShaderStruct(
  422. name: String(cString: name),
  423. storageType: [],
  424. members: parameters,
  425. gsVariable: shaderStruct
  426. )
  427. structs.updateValue(data, forKey: data.name)
  428. }
  429. for i in 0..<parser.funcs.num {
  430. let function: UnsafeMutablePointer<shader_func>? = parser.funcs.array.advanced(by: i)
  431. guard let function, let functionName = function.pointee.name, let returnType = function.pointee.return_type
  432. else {
  433. throw ParserError.parseFail("Shader function has no name or type information")
  434. }
  435. var functionData = OBSShaderFunction(
  436. name: String(cString: functionName),
  437. returnType: String(cString: returnType),
  438. typeMap: [:],
  439. requiresUniformBuffers: false,
  440. textures: [],
  441. samplers: [],
  442. arguments: [],
  443. gsFunction: function,
  444. )
  445. for j in 0..<function.pointee.params.num {
  446. let parameter: UnsafeMutablePointer<shader_var>? = function.pointee.params.array.advanced(by: j)
  447. guard let parameter, let parameterName = parameter.pointee.name,
  448. let parameterType = parameter.pointee.type
  449. else {
  450. throw ParserError.parseFail("Function parameter has no name or type information")
  451. }
  452. let mapping: String? =
  453. if let parameterMapping = parameter.pointee.mapping {
  454. String(cString: parameterMapping)
  455. } else {
  456. nil
  457. }
  458. /// Most effects do not seem to use `out` or `inout` function arguments, but the lanczos scale filter
  459. /// does. The most straight-forward way
  460. /// to support this pattern is to use C++-style references with the `thread` storage specifier.
  461. let isReferenceVariable =
  462. (parameter.pointee.var_type == SHADER_VAR_OUT || parameter.pointee.var_type == SHADER_VAR_INOUT)
  463. var parameterData = OBSShaderVariable(
  464. name: String(cString: parameterName),
  465. type: String(cString: parameterType),
  466. mapping: mapping,
  467. storageType: .typeInput,
  468. requiredBy: [functionData.name],
  469. returnedBy: [],
  470. isStage: false,
  471. attributeId: nil,
  472. isConstant: (parameter.pointee.var_type == SHADER_VAR_CONST),
  473. isReference: isReferenceVariable,
  474. gsVariable: parameter
  475. )
  476. if isReferenceVariable {
  477. referenceVariables.append(parameterData.name)
  478. }
  479. if parameterData.type == functionData.returnType {
  480. parameterData.returnedBy.insert(functionData.name)
  481. }
  482. if !functionData.typeMap.keys.contains(parameterData.name) {
  483. functionData.typeMap.updateValue(parameterData.type, forKey: parameterData.name)
  484. }
  485. /// Metal does not support using the same attribute mappings for structs as input to shader functions
  486. /// and output. They need to use different
  487. /// mappings and thus every "InOut" struct by `libobs` needs to be split up into a separate input and
  488. /// output struct type.
  489. for var shaderStruct in structs.values {
  490. if shaderStruct.name == parameterData.type {
  491. shaderStruct.storageType.insert(.typeInput)
  492. parameterData.storageType.insert(.typeStruct)
  493. if shaderStruct.name == functionData.returnType {
  494. shaderStruct.storageType.insert(.typeOutput)
  495. parameterData.storageType.insert(.typeOutput)
  496. parameterData.type.append("_In")
  497. functionData.returnType.append("_Out")
  498. }
  499. structs.updateValue(shaderStruct, forKey: shaderStruct.name)
  500. }
  501. }
  502. functionData.arguments.append(parameterData)
  503. }
  504. if var shaderStruct = structs[functionData.returnType] {
  505. shaderStruct.storageType.insert(.typeOutput)
  506. structs.updateValue(shaderStruct, forKey: shaderStruct.name)
  507. }
  508. functions.updateValue(functionData, forKey: functionData.name)
  509. }
  510. }
  511. /// Analyzes function data parsed by the ``libobs`` shader parser
  512. ///
  513. /// As MSL does not support uniforms or using the same struct type for input and output, function bodies themselves
  514. /// need to be parsed again and checked for their usage of these types or variables.
  515. ///
  516. /// Due to the way that the ``libobs`` parser works, each body of a block (either within curly braces or
  517. /// parentheses) is analyzed recursively and updating the same ``OBSShaderFunction`` struct.
  518. ///
  519. /// After a full analysis pass, this struct should contain information about all uniforms, textures, and samplers
  520. /// used (or passed on) by the function.
  521. private func analyzeFunctions() throws {
  522. for i in 0..<parser.funcs.num {
  523. let function: UnsafeMutablePointer<shader_func>? = parser.funcs.array.advanced(by: i)
  524. guard var function, var token = function.pointee.start, let functionName = function.pointee.name else {
  525. throw ParserError.parseFail("Shader function has no name")
  526. }
  527. let functionData = functions[String(cString: functionName)]
  528. guard var functionData else {
  529. throw ParserError.parseFail("Shader function without function meta data encountered")
  530. }
  531. try analyzeFunction(function: &function, functionData: &functionData, token: &token, end: "}")
  532. functionData.textures = functionData.textures.unique()
  533. functionData.samplers = functionData.samplers.unique()
  534. functions.updateValue(functionData, forKey: functionData.name)
  535. functionsOrder.append(functionData.name)
  536. }
  537. }
  538. /// Analyzes a function body or source scope to check for use of global variables, textures, or samplers.
  539. ///
  540. /// Because MSL does not support global variables, unforms, textures, or samplers need to be passed explicitly to a
  541. /// function. This requires scanning the entire function body (recursively in the case of separate function scopes
  542. /// denoted by curvy brackets or parantheses) for any occurrence of a known uniform, texture, or sampler variable
  543. /// name.
  544. ///
  545. /// - Parameters:
  546. /// - function: Pointer to a ``shader_func`` element representing a parsed shader function
  547. /// - functionData: Reference to a ``OBSShaderFunction`` struct, which will be updated by this function
  548. /// - token: Pointer to a ``cf_token`` element used to interact with the shader parser provided by ``libobs``
  549. /// - end: The sentinel character at which analysis (and parsing) should stop
  550. private func analyzeFunction(
  551. function: inout UnsafeMutablePointer<shader_func>, functionData: inout OBSShaderFunction,
  552. token: inout UnsafeMutablePointer<cf_token>, end: String
  553. ) throws {
  554. let uniformNames =
  555. (uniforms.filter {
  556. !$0.value.storageType.contains(.typeTexture)
  557. }).keys
  558. while token.pointee.type != CFTOKEN_NONE {
  559. token = token.successor()
  560. if token.pointee.str.isEqualTo(end) {
  561. break
  562. }
  563. let stringToken = token.pointee.str.getString()
  564. if token.pointee.type == CFTOKEN_NAME {
  565. if uniformNames.contains(stringToken) && functionData.requiresUniformBuffers == false {
  566. functionData.requiresUniformBuffers = true
  567. }
  568. if let function = functions[stringToken] {
  569. if function.requiresUniformBuffers && functionData.requiresUniformBuffers == false {
  570. functionData.requiresUniformBuffers = true
  571. }
  572. functionData.textures.append(contentsOf: function.textures)
  573. functionData.samplers.append(contentsOf: function.samplers)
  574. }
  575. if type == .fragment {
  576. for uniform in uniforms.values {
  577. if stringToken == uniform.name && uniform.storageType.contains(.typeTexture) {
  578. functionData.textures.append(stringToken)
  579. }
  580. }
  581. for i in 0..<parser.samplers.num {
  582. let sampler: UnsafeMutablePointer<shader_sampler>? = parser.samplers.array.advanced(by: i)
  583. guard let sampler, let samplerName = sampler.pointee.name else {
  584. break
  585. }
  586. if stringToken == String(cString: samplerName) {
  587. functionData.samplers.append(stringToken)
  588. }
  589. }
  590. }
  591. } else if token.pointee.type == CFTOKEN_OTHER {
  592. if token.pointee.str.isEqualTo("{") {
  593. try analyzeFunction(function: &function, functionData: &functionData, token: &token, end: "}")
  594. } else if token.pointee.str.isEqualTo("(") {
  595. try analyzeFunction(function: &function, functionData: &functionData, token: &token, end: ")")
  596. }
  597. }
  598. }
  599. }
  600. /// Transpiles the uniform global variables used by the shader into a `UniformData` struct that contains the
  601. /// uniforms.
  602. /// - Returns: String representing the uniform data struct
  603. private func transpileUniforms() throws -> String {
  604. var output = [String]()
  605. for uniformName in uniformsOrder {
  606. if var uniform = uniforms[uniformName] {
  607. uniform.isStage = false
  608. uniform.attributeId = 0
  609. if !uniform.storageType.contains(.typeTexture) {
  610. let variableString = try transpileVariable(variable: uniform)
  611. output.append("\(variableString);")
  612. }
  613. }
  614. }
  615. if output.count > 0 {
  616. let replacements = [
  617. ("[variable]", output.joined(separator: "\n")),
  618. ("[typename]", "UniformData"),
  619. ]
  620. let uniformString = replacements.reduce(into: MSLTemplates.shaderStruct) { string, replacement in
  621. string = string.replacingOccurrences(of: replacement.0, with: replacement.1)
  622. }
  623. return uniformString
  624. } else {
  625. return ""
  626. }
  627. }
  628. /// Transpiles the vertex data structs used by the shader
  629. /// - Returns: String representing the vertex data structs
  630. private func transpileStructs() throws -> String {
  631. var output = [String]()
  632. for var shaderStruct in structs.values {
  633. if shaderStruct.storageType.isSuperset(of: [.typeInput, .typeOutput]) {
  634. /// Metal does not support using the same attribute mappings for structs as input to shader functions
  635. /// and output. They need to use different mappings and thus every "InOut" struct by `libobs` needs to
  636. /// be split up into a separate input and output struct type.
  637. for suffix in ["_In", "_Out"] {
  638. var variables = [String]()
  639. for (structVariableId, var structVariable) in shaderStruct.members.enumerated() {
  640. let variableString: String
  641. switch suffix {
  642. case "_In":
  643. structVariable.storageType.formUnion([.typeInput])
  644. structVariable.attributeId = structVariableId
  645. variableString = try transpileVariable(variable: structVariable)
  646. structVariable.storageType.remove([.typeInput])
  647. case "_Out":
  648. structVariable.storageType.formUnion([.typeOutput])
  649. variableString = try transpileVariable(variable: structVariable)
  650. structVariable.storageType.remove([.typeOutput])
  651. default:
  652. throw ParserError.parseFail("Shader struct with unknown prefix encountered")
  653. }
  654. variables.append("\(variableString);")
  655. shaderStruct.members[structVariableId] = structVariable
  656. }
  657. let replacements = [
  658. ("[variable]", variables.joined(separator: "\n")),
  659. ("[typename]", "\(shaderStruct.name)\(suffix)"),
  660. ]
  661. let result = replacements.reduce(into: MSLTemplates.shaderStruct) {
  662. string, replacement in
  663. string = string.replacingOccurrences(of: replacement.0, with: replacement.1)
  664. }
  665. output.append(result)
  666. }
  667. } else {
  668. var variables = [String]()
  669. for (structVariableId, var structVariable) in shaderStruct.members.enumerated() {
  670. if shaderStruct.storageType.contains(.typeInput) {
  671. structVariable.storageType.insert(.typeInput)
  672. structVariable.attributeId = structVariableId
  673. } else if shaderStruct.storageType.contains(.typeOutput) {
  674. structVariable.storageType.insert(.typeOutput)
  675. }
  676. let variableString = try transpileVariable(variable: structVariable)
  677. structVariable.storageType.subtract([.typeInput, .typeOutput])
  678. variables.append("\(variableString);")
  679. shaderStruct.members[structVariableId] = structVariable
  680. }
  681. let replacements = [
  682. ("[variable]", variables.joined(separator: "\n")),
  683. ("[typename]", shaderStruct.name),
  684. ]
  685. let result = replacements.reduce(into: MSLTemplates.shaderStruct) {
  686. string, replacement in
  687. string = string.replacingOccurrences(of: replacement.0, with: replacement.1)
  688. }
  689. output.append(result)
  690. }
  691. }
  692. if output.count > 0 {
  693. return output.joined(separator: "\n\n")
  694. } else {
  695. return ""
  696. }
  697. }
  698. /// Transpiles a shader function into its MSL variant
  699. /// - Returns: String representing the transpiled MSL shader function
  700. private func transpileFunctions() throws -> String {
  701. var output = [String]()
  702. for functionName in functionsOrder {
  703. guard let function = functions[functionName], var token = function.gsFunction.pointee.start else {
  704. throw ParserError.parseFail("Shader function has no name")
  705. }
  706. var stageConsumed = false
  707. let isMain = functionName == "main"
  708. var variables = [String]()
  709. for var variable in function.arguments {
  710. if isMain && !stageConsumed {
  711. variable.isStage = true
  712. stageConsumed = true
  713. }
  714. try variables.append(transpileVariable(variable: variable))
  715. }
  716. /// As Metal has no support for global constants, the constant data needs to be wrapped into a `struct`
  717. /// and the associated data is uploaded into a vertex buffer at a specific index (30 in this case).
  718. ///
  719. /// Buffers are not automatically available to shader functions but are passed into the function explicitly
  720. ///as arguments.
  721. ///
  722. /// As `libobs` effects are based around a "main" entry function (something strongly discouraged by Metal),
  723. /// each "main" function needs to receive the actual buffer as an argument and each function called _by_
  724. /// the main function and which internally accesses the uniform needs to have that uniform passed
  725. /// explicitly as an argument as well.
  726. if (uniforms.values.filter { !$0.storageType.contains(.typeTexture) }).count > 0 {
  727. if isMain {
  728. variables.append("constant UniformData &uniforms [[buffer(30)]]")
  729. } else if function.requiresUniformBuffers {
  730. variables.append("constant UniformData &uniforms")
  731. }
  732. }
  733. if type == .fragment {
  734. var textureId = 0
  735. for uniformName in uniformsOrder {
  736. guard let uniform = uniforms[uniformName] else {
  737. break
  738. }
  739. if uniform.storageType.contains(.typeTexture) {
  740. if isMain {
  741. let variableString = try transpileVariable(variable: uniform)
  742. variables.append("\(variableString) [[texture(\(textureId))]]")
  743. textureId += 1
  744. } else if function.textures.contains(uniform.name) {
  745. let variableString = try transpileVariable(variable: uniform)
  746. variables.append(variableString)
  747. }
  748. }
  749. }
  750. var samplerId = 0
  751. for i in 0..<parser.samplers.num {
  752. let sampler: UnsafeMutablePointer<shader_sampler>? = parser.samplers.array.advanced(by: i)
  753. if let sampler, let samplerName = sampler.pointee.name {
  754. let name = String(cString: samplerName)
  755. if isMain {
  756. let variableString = "sampler \(name) [[sampler(\(samplerId))]]"
  757. variables.append(variableString)
  758. samplerId += 1
  759. } else if function.samplers.contains(name) {
  760. let variabelString = "sampler \(name)"
  761. variables.append(variabelString)
  762. }
  763. }
  764. }
  765. }
  766. let mappedType = try convertToMTLType(gsType: function.returnType)
  767. let functionContent: String
  768. var replacements = [(String, String)]()
  769. /// Metal shaders do not have "main" functions - a single shader file usually contains all shader functions
  770. /// used by an application, each identified by their name and type decorator. This is not supported by OBS,
  771. /// so each shader needs to have a "main" function that calls the actual shader function, which thus
  772. /// requires a new shader library to be created for each effect file.
  773. if isMain {
  774. replacements = [
  775. ("[name]", "_main"),
  776. ("[parameters]", variables.joined(separator: ", ")),
  777. ]
  778. switch type {
  779. case .vertex:
  780. replacements.append(("[decorator]", "[[vertex]]"))
  781. case .fragment:
  782. replacements.append(("[decorator]", "[[fragment]]"))
  783. default:
  784. fatalError("OBSShader: Unsupported shader type \(type)")
  785. }
  786. let temporaryContent = try transpileFunctionContent(token: &token, end: "}")
  787. if type == .fragment && isMain && mappedType == "float3" {
  788. replacements.append(("[type]", "float4"))
  789. // TODO: Replace with Swift-native Regex once macOS 13+ is minimum target
  790. let regex = try NSRegularExpression(pattern: "return (.+);")
  791. functionContent = regex.stringByReplacingMatches(
  792. in: temporaryContent,
  793. range: NSRange(location: 0, length: temporaryContent.count),
  794. withTemplate: "return float4($1, 1);"
  795. )
  796. } else {
  797. functionContent = temporaryContent
  798. replacements.append(("[type]", mappedType))
  799. }
  800. replacements.append(("[content]", functionContent))
  801. } else {
  802. functionContent = try transpileFunctionContent(token: &token, end: "}")
  803. replacements = [
  804. ("[decorator]", ""),
  805. ("[type]", mappedType),
  806. ("[name]", function.name),
  807. ("[parameters]", variables.joined(separator: ", ")),
  808. ("[content]", functionContent),
  809. ]
  810. }
  811. let result = replacements.reduce(into: MSLTemplates.function) {
  812. string, replacement in
  813. string = string.replacingOccurrences(of: replacement.0, with: replacement.1)
  814. }
  815. output.append(result)
  816. }
  817. if output.count > 0 {
  818. return output.joined(separator: "\n\n")
  819. } else {
  820. return ""
  821. }
  822. }
  823. /// Transpiles a variable into its MSL variant
  824. /// - Parameter variable: Variable to transpile
  825. /// - Returns: String representing a transpiled variable
  826. ///
  827. /// Variables can either be members of a `struct` or an argument to a function. The ``OBSShaderVariable`` instance
  828. /// has a `storageType` property which encodes the use of the variable and helps in creation of the appropriate MSL
  829. /// string representation.
  830. private func transpileVariable(variable: OBSShaderVariable) throws -> String {
  831. var mappings = [String]()
  832. var metalMapping: String
  833. var indent = 0
  834. let metalType = try convertToMTLType(gsType: variable.type)
  835. if variable.storageType.contains(.typeUniform) {
  836. indent = 4
  837. } else if variable.storageType.isSuperset(of: [.typeInput, .typeStructMember]) {
  838. switch type {
  839. case .vertex:
  840. indent = 4
  841. /// Attributes are used to associate a member of a uniform `struct` with its data in the vertex buffer
  842. /// stage.
  843. if let attributeId = variable.attributeId {
  844. mappings.append("attribute(\(attributeId))")
  845. }
  846. case .fragment:
  847. indent = 4
  848. if let mappingPointer = variable.gsVariable.pointee.mapping,
  849. let mappedString = convertToMTLMapping(gsMapping: String(cString: mappingPointer))
  850. {
  851. mappings.append(mappedString)
  852. }
  853. default:
  854. fatalError("OBSShader: Unsupported shader function type \(type)")
  855. }
  856. } else if variable.storageType.isSuperset(of: [.typeOutput, .typeStructMember]) {
  857. indent = 4
  858. if let mappingPointer = variable.gsVariable.pointee.mapping,
  859. let mappedString = convertToMTLMapping(gsMapping: String(cString: mappingPointer))
  860. {
  861. mappings.append(mappedString)
  862. }
  863. } else {
  864. indent = 0
  865. if variable.isStage {
  866. if let mappingPointer = variable.gsVariable.pointee.mapping,
  867. let mappedString = convertToMTLMapping(gsMapping: String(cString: mappingPointer))
  868. {
  869. mappings.append(mappedString)
  870. } else {
  871. mappings.append("stage_in")
  872. }
  873. }
  874. }
  875. if mappings.count > 0 {
  876. metalMapping = " [[\(mappings.joined(separator: ", "))]]"
  877. } else {
  878. metalMapping = ""
  879. }
  880. let qualifier =
  881. if variable.storageType.contains(.typeConstant) {
  882. " constant "
  883. } else if variable.isReference {
  884. " thread "
  885. } else {
  886. ""
  887. }
  888. let name =
  889. if variable.isReference {
  890. "&\(variable.name)"
  891. } else { variable.name }
  892. let result = "\(String(repeating: " ", count: indent))\(qualifier)\(metalType) \(name)\(metalMapping)"
  893. return result
  894. }
  895. /// Transpiles the body of a function into its MSL representation
  896. /// - Parameters:
  897. /// - token: Stateful `libobs` parser token pointer
  898. /// - end: String representing which ends function body parsing if matched
  899. /// - Returns: String representing the body of a MSL shader function
  900. ///
  901. /// OBS effect function content needs to be transpiled into MSL function content token by token, as each token
  902. /// needs to be matched not only against direct translations (e.g., a HLSL function name into its appropriate MSL
  903. /// variant) but also to detect if a token represents a uniform variable which will not be available as a global
  904. /// variable in MSL, but instead will only exist as part of the `uniform` struct that was explicitly passed into
  905. /// the function.
  906. ///
  907. /// Similarly, if a function call is encountered, the function's metadata needs to be checked for use of such a
  908. /// uniform and the call signature extended to explicitly pass the data into the called function.
  909. ///
  910. /// Because Metal does not implicitly or automagically coerce types (but the effects files sometimes rely on this),
  911. /// some arguments and parameters need to be explicitly wrapped in casts to wider types (e.g., a `float3` is
  912. /// returned from a fragment shader, but fragment shaders _have to_ provide a `float4`).
  913. ///
  914. /// There are many such conversions necessary, as MSL is more strict than HLSL or GLSL when it comes to type safety.
  915. private func transpileFunctionContent(token: inout UnsafeMutablePointer<cf_token>, end: String) throws -> String {
  916. var content = [String]()
  917. while token.pointee.type != CFTOKEN_NONE {
  918. token = token.successor()
  919. if token.pointee.str.isEqualTo(end) {
  920. break
  921. }
  922. let stringToken = token.pointee.str.getString()
  923. if token.pointee.type == CFTOKEN_NAME {
  924. let type = try convertToMTLType(gsType: stringToken)
  925. if stringToken == "obs_glsl_compile" {
  926. content.append("false")
  927. continue
  928. }
  929. if type != stringToken {
  930. content.append(type)
  931. continue
  932. }
  933. if let intrinsic = try convertToMTLIntrinsic(intrinsic: stringToken) {
  934. content.append(intrinsic)
  935. continue
  936. }
  937. if stringToken == "mul" {
  938. try content.append(convertToMTLMultiplication(token: &token))
  939. continue
  940. } else if stringToken == "mad" {
  941. try content.append(convertToMTLMultiplyAdd(token: &token))
  942. continue
  943. } else {
  944. var skip = false
  945. for uniform in uniforms.values {
  946. if uniform.name == stringToken && uniform.storageType.contains(.typeTexture) {
  947. try content.append(createSampler(token: &token))
  948. skip = true
  949. break
  950. }
  951. }
  952. if skip {
  953. continue
  954. }
  955. }
  956. if uniforms.keys.contains(stringToken) {
  957. let priorToken = token.predecessor()
  958. let priorString = priorToken.pointee.str.getString()
  959. if priorString != "." {
  960. content.append("uniforms.\(stringToken)")
  961. continue
  962. }
  963. }
  964. var skip = false
  965. for shaderStruct in structs.values {
  966. if shaderStruct.name == stringToken {
  967. if shaderStruct.storageType.isSuperset(of: [.typeInput, .typeOutput]) {
  968. content.append("\(stringToken)_Out")
  969. skip = true
  970. break
  971. }
  972. }
  973. }
  974. if skip {
  975. continue
  976. }
  977. if let comparison = try convertToMTLComparison(token: &token) {
  978. content.append(comparison)
  979. continue
  980. }
  981. content.append(stringToken)
  982. } else if token.pointee.type == CFTOKEN_OTHER {
  983. if token.pointee.str.isEqualTo("{") {
  984. let blockContent = try transpileFunctionContent(token: &token, end: "}")
  985. content.append("{\(blockContent)}")
  986. continue
  987. } else if token.pointee.str.isEqualTo("(") {
  988. let priorToken = token.predecessor()
  989. let functionName = priorToken.pointee.str.getString()
  990. var functionParameters = [String]()
  991. let parameters = try transpileFunctionContent(token: &token, end: ")")
  992. if functionName == "int3" {
  993. let intParameters = parameters.split(
  994. separator: ",", maxSplits: 3, omittingEmptySubsequences: true)
  995. switch intParameters.count {
  996. case 3:
  997. functionParameters.append(
  998. "int(\(intParameters[0])), int(\(intParameters[1])), int(\(intParameters[2]))")
  999. case 2:
  1000. functionParameters.append("int2(\(intParameters[0])), int(\(intParameters[1]))")
  1001. case 1:
  1002. functionParameters.append("\(intParameters)")
  1003. default:
  1004. throw ParserError.parseFail("int3 constructor with invalid amount of arguments encountered")
  1005. }
  1006. } else {
  1007. functionParameters.append(parameters)
  1008. }
  1009. if let additionalArguments = generateAdditionalArguments(for: functionName) {
  1010. functionParameters.append(additionalArguments)
  1011. }
  1012. content.append("(\(functionParameters.joined(separator: ", ")))")
  1013. continue
  1014. }
  1015. content.append(stringToken)
  1016. } else {
  1017. content.append(stringToken)
  1018. }
  1019. }
  1020. return content.joined()
  1021. }
  1022. /// Converts a HLSL-like type into a MSL type if possible
  1023. /// - Parameter gsType: HLSL-like type string
  1024. /// - Returns: MSL type string
  1025. private func convertToMTLType(gsType: String) throws -> String {
  1026. switch gsType {
  1027. case "texture2d":
  1028. return "texture2d<float>"
  1029. case "texture3d":
  1030. return "texture3d<float>"
  1031. case "texture_cube":
  1032. return "texturecube<float>"
  1033. case "texture_rect":
  1034. throw ParserError.unsupportedType
  1035. case "half2":
  1036. return "float2"
  1037. case "half3":
  1038. return "float3"
  1039. case "half4":
  1040. return "float4"
  1041. case "half":
  1042. return "float"
  1043. case "min16float2":
  1044. return "half2"
  1045. case "min16float3":
  1046. return "half3"
  1047. case "min16float4":
  1048. return "half4"
  1049. case "min16float":
  1050. return "half"
  1051. case "min10float":
  1052. throw ParserError.unsupportedType
  1053. case "double":
  1054. throw ParserError.unsupportedType
  1055. case "min16int2":
  1056. return "short2"
  1057. case "min16int3":
  1058. return "short3"
  1059. case "min16int4":
  1060. return "short4"
  1061. case "min16int":
  1062. return "short"
  1063. case "min16uint2":
  1064. return "ushort2"
  1065. case "min16uint3":
  1066. return "ushort3"
  1067. case "min16uint4":
  1068. return "ushort4"
  1069. case "min16uint":
  1070. return "ushort"
  1071. case "min13int":
  1072. throw ParserError.unsupportedType
  1073. default:
  1074. return gsType
  1075. }
  1076. }
  1077. /// Converts an HLSL-like uniform mapping into a MSL attribute decoration if possible
  1078. /// - Parameter gsMapping: HLSL-like mapping
  1079. /// - Returns: MSL attribute string
  1080. private func convertToMTLMapping(gsMapping: String) -> String? {
  1081. switch gsMapping {
  1082. case "POSITION":
  1083. return "position"
  1084. case "VERTEXID":
  1085. return "vertex_id"
  1086. default:
  1087. return nil
  1088. }
  1089. }
  1090. /// Converts a HLSL-like comparison to a vector-safe MSL comparison operation
  1091. /// - Parameter token: Start token of the comparison in the function body
  1092. /// - Returns: MSL comparison operation
  1093. ///
  1094. /// A comparison operation that involves a vector will always result in a boolean vector in MSL (and not a scalar
  1095. /// vector). Thus any functions that compares two vectors will also result in a vector
  1096. /// (e.g., float2 == float2 -> bool2). This will break when a ternary expression is used, as the first element of
  1097. /// it needs to be as scalar boolean in MSL.
  1098. ///
  1099. /// Wrapping the comparison in `all` ensures that a single scalar `true` is returned if all elements of the
  1100. /// resulting boolean vectors are `true` as well.
  1101. private func convertToMTLComparison(token: inout UnsafeMutablePointer<cf_token>) throws -> String? {
  1102. var isComparator = false
  1103. let nextToken = token.successor()
  1104. if nextToken.pointee.type == CFTOKEN_OTHER {
  1105. let comparators = ["==", "!=", "<", "<=", ">=", ">"]
  1106. for comparator in comparators {
  1107. if nextToken.pointee.str.isEqualTo(comparator) {
  1108. isComparator = true
  1109. break
  1110. }
  1111. }
  1112. }
  1113. if isComparator {
  1114. var cfp = parser.cfp
  1115. cfp.cur_token = token
  1116. let lhs = cfp.cur_token.pointee.str.getString()
  1117. guard cfp.advanceToken() else { throw ParserError.missingNextToken }
  1118. let comparator = cfp.cur_token.pointee.str.getString()
  1119. guard cfp.advanceToken() else { throw ParserError.missingNextToken }
  1120. let rhs = cfp.cur_token.pointee.str.getString()
  1121. return "all(\(lhs) \(comparator) \(rhs))"
  1122. } else {
  1123. return nil
  1124. }
  1125. }
  1126. /// Converts HLSL-like intrinsic into its MSL representation
  1127. /// - Parameter intrinsic: HLSL-like intrinsic string
  1128. /// - Returns: MSL intrinsic string
  1129. private func convertToMTLIntrinsic(intrinsic: String) throws -> String? {
  1130. switch intrinsic {
  1131. case "clip":
  1132. throw ParserError.unsupportedType
  1133. case "ddx":
  1134. return "dfdx"
  1135. case "ddy":
  1136. return "dfdy"
  1137. case "frac":
  1138. return "fract"
  1139. case "lerp":
  1140. return "mix"
  1141. default:
  1142. return nil
  1143. }
  1144. }
  1145. /// Converts a HLSL-like multiplication function call into a direct multiplication
  1146. /// - Parameter token: Start token of the multiplication in the function body
  1147. /// - Returns: MSL multiplication string
  1148. private func convertToMTLMultiplication(token: inout UnsafeMutablePointer<cf_token>) throws -> String {
  1149. var cfp = parser.cfp
  1150. cfp.cur_token = token
  1151. guard cfp.advanceToken() else { throw ParserError.missingNextToken }
  1152. guard cfp.tokenIsEqualTo("(") else { throw ParserError.unexpectedToken }
  1153. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1154. let lhs = try transpileFunctionContent(token: &cfp.cur_token, end: ",")
  1155. guard cfp.advanceToken() else { throw ParserError.missingNextToken }
  1156. cfp.cur_token = cfp.cur_token.predecessor()
  1157. let rhs = try transpileFunctionContent(token: &cfp.cur_token, end: ")")
  1158. token = cfp.cur_token
  1159. return "(\(lhs)) * (\(rhs))"
  1160. }
  1161. /// Converts a HLSL-like multiply+add function call into a direct multiplication followed by addition
  1162. /// - Parameter token: Start token of the multiply+add in the function body
  1163. /// - Returns: MSL multiplication and addition string
  1164. private func convertToMTLMultiplyAdd(token: inout UnsafeMutablePointer<cf_token>) throws -> String {
  1165. var cfp = parser.cfp
  1166. cfp.cur_token = token
  1167. guard cfp.advanceToken() else { throw ParserError.missingNextToken }
  1168. guard cfp.tokenIsEqualTo("(") else { throw ParserError.unexpectedToken }
  1169. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1170. let first = try transpileFunctionContent(token: &cfp.cur_token, end: ",")
  1171. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1172. let second = try transpileFunctionContent(token: &cfp.cur_token, end: ",")
  1173. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1174. let third = try transpileFunctionContent(token: &cfp.cur_token, end: ")")
  1175. token = cfp.cur_token
  1176. return "((\(first)) * (\(second))) + (\(third))"
  1177. }
  1178. /// Creates an MSL sampler call from a HLSL-like sampler call
  1179. /// - Parameter token: Start token of the sampler call in the function
  1180. /// - Returns: String of an MSL sampler call
  1181. private func createSampler(token: inout UnsafeMutablePointer<cf_token>) throws -> String {
  1182. var cfp = parser.cfp
  1183. cfp.cur_token = token
  1184. let stringToken = token.pointee.str.getString()
  1185. guard cfp.advanceToken() else { throw ParserError.missingNextToken }
  1186. guard cfp.tokenIsEqualTo(".") else { throw ParserError.unexpectedToken }
  1187. guard cfp.advanceToken() else { throw ParserError.missingNextToken }
  1188. guard cfp.cur_token.pointee.type == CFTOKEN_NAME else { throw ParserError.unexpectedToken }
  1189. let textureCall: String
  1190. if cfp.tokenIsEqualTo("Sample") {
  1191. textureCall = try createTextureCall(token: &cfp.cur_token, callType: .sample)
  1192. } else if cfp.tokenIsEqualTo("SampleBias") {
  1193. textureCall = try createTextureCall(token: &cfp.cur_token, callType: .sampleBias)
  1194. } else if cfp.tokenIsEqualTo("SampleGrad") {
  1195. textureCall = try createTextureCall(token: &cfp.cur_token, callType: .sampleGrad)
  1196. } else if cfp.tokenIsEqualTo("SampleLevel") {
  1197. textureCall = try createTextureCall(token: &cfp.cur_token, callType: .sampleLevel)
  1198. } else if cfp.tokenIsEqualTo("Load") {
  1199. textureCall = try createTextureCall(token: &cfp.cur_token, callType: .load)
  1200. } else {
  1201. throw ParserError.missingNextToken
  1202. }
  1203. token = cfp.cur_token
  1204. return "\(stringToken).\(textureCall)"
  1205. }
  1206. /// Creates a MSL sampler call based on the sampling type
  1207. /// - Parameters:
  1208. /// - token: Start token of the sampler call arguments in the function body
  1209. /// - callType: Type of sampling used
  1210. /// - Returns: String of an MSL sampler call
  1211. private func createTextureCall(token: inout UnsafeMutablePointer<cf_token>, callType: SampleVariant) throws
  1212. -> String
  1213. {
  1214. var cfp = parser.cfp
  1215. cfp.cur_token = token
  1216. guard cfp.advanceToken() else { throw ParserError.missingNextToken }
  1217. guard cfp.tokenIsEqualTo("(") else { throw ParserError.unexpectedToken }
  1218. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1219. switch callType {
  1220. case .sample:
  1221. let first = try transpileFunctionContent(token: &cfp.cur_token, end: ",")
  1222. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1223. let second = try transpileFunctionContent(token: &cfp.cur_token, end: ")")
  1224. token = cfp.cur_token
  1225. return "sample(\(first), \(second))"
  1226. case .sampleBias:
  1227. let first = try transpileFunctionContent(token: &cfp.cur_token, end: ",")
  1228. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1229. let second = try transpileFunctionContent(token: &cfp.cur_token, end: ",")
  1230. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1231. let third = try transpileFunctionContent(token: &cfp.cur_token, end: ")")
  1232. token = cfp.cur_token
  1233. return "sample(\(first), \(second), bias(\(third)))"
  1234. case .sampleGrad:
  1235. let first = try transpileFunctionContent(token: &cfp.cur_token, end: ",")
  1236. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1237. let second = try transpileFunctionContent(token: &cfp.cur_token, end: ",")
  1238. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1239. let third = try transpileFunctionContent(token: &cfp.cur_token, end: ",")
  1240. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1241. let fourth = try transpileFunctionContent(token: &cfp.cur_token, end: ")")
  1242. token = cfp.cur_token
  1243. return "sample(\(first), \(second), gradient2d(\(third), \(fourth)))"
  1244. case .sampleLevel:
  1245. let first = try transpileFunctionContent(token: &cfp.cur_token, end: ",")
  1246. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1247. let second = try transpileFunctionContent(token: &cfp.cur_token, end: ",")
  1248. guard cfp.hasNextToken() else { throw ParserError.missingNextToken }
  1249. let third = try transpileFunctionContent(token: &cfp.cur_token, end: ")")
  1250. token = cfp.cur_token
  1251. return "sample(\(first), \(second), level(\(third)))"
  1252. case .load:
  1253. let first = try transpileFunctionContent(token: &cfp.cur_token, end: ")")
  1254. let loadCall: String
  1255. /// Many load calls in OBS effects files rely on implicit type conversion, which is not allowed in MSL in
  1256. /// addition to `read` calls only accepting a `uint2` followed by a `uint`. Any instance of a `int3` thus
  1257. /// needs to be converted into the appropriate variant compatible with the `read` call.
  1258. if first.hasPrefix("int3(") {
  1259. let loadParameters = first[
  1260. first.index(first.startIndex, offsetBy: 5)..<first.index(first.endIndex, offsetBy: -1)
  1261. ].split(separator: ",", maxSplits: 3, omittingEmptySubsequences: true)
  1262. switch loadParameters.count {
  1263. case 3:
  1264. loadCall = "read(uint2(\(loadParameters[0]), \(loadParameters[1])), uint(\(loadParameters[2])))"
  1265. case 2:
  1266. loadCall = "read(uint2(\(loadParameters[0])), uint(\(loadParameters[1])))"
  1267. case 1:
  1268. loadCall = "read(uint2(\(loadParameters[0]).xy), 0)"
  1269. default:
  1270. throw ParserError.parseFail("int3 constructor with invalid number of arguments encountered")
  1271. }
  1272. } else {
  1273. loadCall = "read(uint2(\(first).xy), 0)"
  1274. }
  1275. token = cfp.cur_token
  1276. return loadCall
  1277. }
  1278. }
  1279. /// Generates the explicit arguments that need to be passed into MSL shader functions in place of direct access to
  1280. /// uniform globals which are not supported by Metal.
  1281. /// - Parameter functionName: Name of the function to generate the additional arguments for
  1282. /// - Returns: String of additional arguments to be put into the function signature
  1283. private func generateAdditionalArguments(for functionName: String) -> String? {
  1284. var output = [String]()
  1285. for function in functions.values {
  1286. if function.name != functionName {
  1287. continue
  1288. }
  1289. if function.requiresUniformBuffers {
  1290. output.append("uniforms")
  1291. }
  1292. for texture in function.textures {
  1293. for uniform in uniforms.values {
  1294. if uniform.name == texture && uniform.storageType.contains(.typeTexture) {
  1295. output.append(texture)
  1296. }
  1297. }
  1298. }
  1299. for sampler in function.samplers {
  1300. for i in 0..<parser.samplers.num {
  1301. let samplerPointer: UnsafeMutablePointer<shader_sampler>? = parser.samplers.array.advanced(by: i)
  1302. if let samplerPointer {
  1303. if sampler == String(cString: samplerPointer.pointee.name) {
  1304. output.append(sampler)
  1305. }
  1306. }
  1307. }
  1308. }
  1309. }
  1310. if output.count > 0 {
  1311. return output.joined(separator: ", ")
  1312. }
  1313. return nil
  1314. }
  1315. deinit {
  1316. withUnsafeMutablePointer(to: &parser) {
  1317. shader_parser_free($0)
  1318. }
  1319. }
  1320. }