1
0

metal-texture2d.swift 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  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. /// Creates a two-dimensional ``MetalTexture`` instance with the specified usage options and the raw image data (if
  17. /// provided)
  18. /// - Parameters:
  19. /// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs`
  20. /// - width: Desired width of the texture
  21. /// - height: Desired height of the texture
  22. /// - color_format: Desired color format of the texture as described by `gs_color_format`
  23. /// - levels: Amount of mip map levels to generate for the texture
  24. /// - data: Optional pointer to raw pixel data per mip map level
  25. /// - flags: Texture resource use information encoded as `libobs` bitfield
  26. /// - Returns: Opaque pointer to a created ``MetalTexture`` instance or a `nil` pointer on error
  27. ///
  28. /// This function will create a new ``MTLTexture`` wrapped within a ``MetalTexture`` class and also upload any pixel
  29. /// data if non-`nil` pointers have been provided via the `data` argument.
  30. ///
  31. /// > Important: If mipmap generation is requested, execution will be blocked by waiting for the blit command encoder
  32. /// to generate the mipmaps.
  33. @_cdecl("device_texture_create")
  34. public func device_texture_create(
  35. device: UnsafeRawPointer, width: UInt32, height: UInt32, color_format: gs_color_format, levels: UInt32,
  36. data: UnsafePointer<UnsafePointer<UInt8>?>?, flags: UInt32
  37. ) -> OpaquePointer? {
  38. let device: MetalDevice = unretained(device)
  39. let descriptor = MTLTextureDescriptor.init(
  40. type: .type2D,
  41. width: width,
  42. height: height,
  43. depth: 1,
  44. colorFormat: color_format,
  45. levels: levels,
  46. flags: flags
  47. )
  48. guard let descriptor, let texture = MetalTexture(device: device, descriptor: descriptor) else {
  49. return nil
  50. }
  51. if let data {
  52. texture.upload(data: data, mipmapLevels: descriptor.mipmapLevelCount)
  53. }
  54. return texture.getRetained()
  55. }
  56. /// Creates a ``MetalTexture`` instance for a cube texture with the specified usage options and the raw image data (if provided)
  57. /// - Parameters:
  58. /// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs`
  59. /// - size: Desized edge length for the cube
  60. /// - color_format: Desired color format of the texture as described by `gs_color_format`
  61. /// - levels: Amount of mip map levels to generate for the texture
  62. /// - data: Optional pointer to raw pixel data per mip map level
  63. /// - flags: Texture resource use information encoded as `libobs` bitfield
  64. /// - Returns: Opaque pointer to created ``MetalTexture`` instance or a `nil` pointer on error
  65. ///
  66. /// This function will create a new ``MTLTexture`` wrapped within a ``MetalTexture`` class and also upload any pixel
  67. /// data if non-`nil` pointers have
  68. /// been provided via the `data` argument.
  69. ///
  70. /// > Important: If mipmap generation is requested, execution will be blocked by waiting for the blit command encoder
  71. /// to generate the mipmaps.
  72. @_cdecl("device_cubetexture_create")
  73. public func device_cubetexture_create(
  74. device: UnsafeRawPointer, size: UInt32, color_format: gs_color_format, levels: UInt32,
  75. data: UnsafePointer<UnsafePointer<UInt8>?>?, flags: UInt32
  76. ) -> OpaquePointer? {
  77. let device: MetalDevice = unretained(device)
  78. let descriptor = MTLTextureDescriptor.init(
  79. type: .typeCube,
  80. width: size,
  81. height: size,
  82. depth: 1,
  83. colorFormat: color_format,
  84. levels: levels,
  85. flags: flags
  86. )
  87. guard let descriptor, let texture = MetalTexture(device: device, descriptor: descriptor) else {
  88. return nil
  89. }
  90. if let data {
  91. texture.upload(data: data, mipmapLevels: descriptor.mipmapLevelCount)
  92. }
  93. return texture.getRetained()
  94. }
  95. /// Requests deinitialization of the ``MetalTexture`` instance shared with `libobs`
  96. /// - Parameter texture: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  97. ///
  98. /// The ownership of the shared pointer is transferred into this function and the instance is placed under Swift's
  99. /// memory management again.
  100. @_cdecl("gs_texture_destroy")
  101. public func gs_texture_destroy(texture: UnsafeRawPointer) {
  102. let _ = retained(texture) as MetalTexture
  103. }
  104. /// Gets the type of the texture wrapped by the ``MetalTexture`` instance
  105. /// - Parameter texture: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  106. /// - Returns: Texture type identified by `gs_texture_type` enum value
  107. ///
  108. /// > Warning: As `libobs` has no enum value for "invalid texture type", there is no way for this function to signal
  109. /// that the wrapped texture has an incompatible ``MTLTextureType``. Instead of crashing the program (which would
  110. /// avoid undefined behavior), this function will return the 2D texture type value instead, which is incorrect, but is
  111. /// more in line with how OBS Studio handles undefined behavior.
  112. @_cdecl("device_get_texture_type")
  113. public func device_get_texture_type(texture: UnsafeRawPointer) -> gs_texture_type {
  114. let texture: MetalTexture = unretained(texture)
  115. return texture.texture.textureType.gsTextureType ?? GS_TEXTURE_2D
  116. }
  117. /// Requests the ``MetalTexture`` instance to be loaded as one of the current pipeline's fragment attachments in the
  118. /// specified texture slot
  119. /// - Parameters:
  120. /// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs`
  121. /// - tex: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  122. /// - unit: Texture slot for fragment attachment
  123. ///
  124. /// OBS Studio expects pipelines to support fragment attachments for textures and samplers up to the amount defined in
  125. /// the `GS_MAX_TEXTURES` preprocessor directive. The order of this calls can be arbitrary, so at any point in time a
  126. /// request to load a texture into slot "5" can take place, even if slots 0 to 4 are empty.
  127. @_cdecl("device_load_texture")
  128. public func device_load_texture(device: UnsafeRawPointer, tex: UnsafeRawPointer, unit: UInt32) {
  129. let device: MetalDevice = unretained(device)
  130. let texture: MetalTexture = unretained(tex)
  131. device.renderState.textures[Int(unit)] = texture.texture
  132. }
  133. /// Requests an sRGB variant of a ``MetalTexture`` instance to be set as one of the current pipeline's fragment
  134. /// attachments in the specified texture slot.
  135. /// - Parameters:
  136. /// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs`
  137. /// - tex: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  138. /// - unit: Texture slot for fragment attachment
  139. /// OBS Studio expects pipelines to support fragment attachments for textures and samplers up to the amount defined in
  140. /// the `GS_MAX_TEXTURES` preprocessor directive. The order of this calls can be arbitrary, so at any point in time a
  141. /// request to load a texture into slot "5" can take place, even if slots 0 to 4 are empty.
  142. ///
  143. /// > Important: This variant of the texture load functions expects a texture whose color values are already sRGB gamma
  144. /// encoded and thus also expects that the color values used in the fragment shader will have been automatically
  145. /// decoded into linear gamma. If the ``MetalTexture`` instance has no dedicated ``MetalTexture/sRGBtexture`` instance,
  146. /// it will use the normal ``MetalTexture/texture`` instance instead.
  147. @_cdecl("device_load_texture_srgb")
  148. public func device_load_texture_srgb(device: UnsafeRawPointer, tex: UnsafeRawPointer, unit: UInt32) {
  149. let device: MetalDevice = unretained(device)
  150. let texture: MetalTexture = unretained(tex)
  151. if texture.sRGBtexture != nil {
  152. device.renderState.textures[Int(unit)] = texture.sRGBtexture!
  153. } else {
  154. device.renderState.textures[Int(unit)] = texture.texture
  155. }
  156. }
  157. /// Copies image data from a region in the source ``MetalTexture`` into a destination ``MetalTexture`` at the provided
  158. /// origin
  159. /// - Parameters:
  160. /// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs`
  161. /// - dst: Opaque pointer to ``MetalTexture`` instance shared with `libobs`, used as destination for the copy operation
  162. /// - dst_x: X coordinate of the origin in the destination texture
  163. /// - dst_y: Y coordinate of the origin in the destination texture
  164. /// - src: Opaque pointer to ``MetalTexture`` instance shared with `libobs`, used as source for the copy operation
  165. /// - src_x: X coordinate of the origin in the source texture
  166. /// - src_y: Y coordinate of the origin in the source texture
  167. /// - src_w: Width of the region in the source texture
  168. /// - src_h: Height of the region in the source texture
  169. ///
  170. /// This function will fail if the destination texture's dimensions aren't large enough to hold the region copied from
  171. /// the source texture. This check will use the desired origin within the destination texture and the region's size
  172. /// into account and checks whether the total dimensions of the destination are large enough (starting at the
  173. /// destination origin) to hold the source's region.
  174. ///
  175. /// > Important: Execution will **not** be blocked, the copy operation will be committed to the command queue and
  176. /// executed at some point after this function returns.
  177. @_cdecl("device_copy_texture_region")
  178. public func device_copy_texture_region(
  179. device: UnsafeRawPointer, dst: UnsafeRawPointer, dst_x: UInt32, dst_y: UInt32, src: UnsafeRawPointer, src_x: UInt32,
  180. src_y: UInt32, src_w: UInt32, src_h: UInt32
  181. ) {
  182. let device: MetalDevice = unretained(device)
  183. let source: MetalTexture = unretained(src)
  184. let destination: MetalTexture = unretained(dst)
  185. var sourceRegion = MTLRegion(
  186. origin: .init(x: Int(src_x), y: Int(src_y), z: 0),
  187. size: .init(width: Int(src_w), height: Int(src_h), depth: 1)
  188. )
  189. let destinationRegion = MTLRegion(
  190. origin: .init(x: Int(dst_x), y: Int(dst_y), z: 0),
  191. size: .init(width: destination.texture.width, height: destination.texture.height, depth: 1)
  192. )
  193. if sourceRegion.size.width == 0 {
  194. sourceRegion.size.width = source.texture.width - sourceRegion.origin.x
  195. }
  196. if sourceRegion.size.height == 0 {
  197. sourceRegion.size.height = source.texture.height - sourceRegion.origin.y
  198. }
  199. guard
  200. destinationRegion.size.width - destinationRegion.origin.x > sourceRegion.size.width
  201. && destinationRegion.size.height - destinationRegion.origin.y > sourceRegion.size.height
  202. else {
  203. OBSLog(
  204. .error,
  205. "device_copy_texture_region: Destination texture \(destinationRegion.size) is not large enough to hold source region (\(sourceRegion.size) at origin \(destinationRegion.origin)"
  206. )
  207. return
  208. }
  209. do {
  210. try device.copyTextureRegion(
  211. source: source,
  212. sourceRegion: sourceRegion,
  213. destination: destination,
  214. destinationRegion: destinationRegion)
  215. } catch let error as MetalError.MTLDeviceError {
  216. OBSLog(.error, "device_clear: \(error.description)")
  217. } catch {
  218. OBSLog(.error, "device_clear: Unknown error occurred")
  219. }
  220. }
  221. /// Copies the image data from the source ``MetalTexture`` into the destination ``MetalTexture``
  222. /// - Parameters:
  223. /// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs`
  224. /// - dst: Opaque pointer to ``MetalTexture`` instance shared with `libobs`, used as destination for the copy
  225. /// operation
  226. /// - src: Opaque pointer to ``MetalTexture`` instance shared with `libobs`, used as source for the copy operation
  227. ///
  228. /// > Warning: This function requires that the source and destination texture dimensions are identical, otherwise the
  229. /// copy operation will fail.
  230. ///
  231. /// > Important: Execution will **not** be blocked, the copy operation will be committed to the command queue and
  232. /// executed at some point after this function returns.
  233. @_cdecl("device_copy_texture")
  234. public func device_copy_texture(device: UnsafeRawPointer, dst: UnsafeRawPointer, src: UnsafeRawPointer) {
  235. let device: MetalDevice = unretained(device)
  236. let source: MetalTexture = unretained(src)
  237. let destination: MetalTexture = unretained(dst)
  238. do {
  239. try device.copyTexture(source: source, destination: destination)
  240. } catch let error as MetalError.MTLDeviceError {
  241. OBSLog(.error, "device_clear: \(error.description)")
  242. } catch {
  243. OBSLog(.error, "device_clear: Unknown error occurred")
  244. }
  245. }
  246. /// Copies the image data from the source ``MetalTexture`` into the destination ``MetalTexture`` and blocks execution
  247. /// until the copy operation has finished.
  248. /// - Parameters:
  249. /// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs`
  250. /// - dst: Opaque pointer to ``MetalStageBuffer`` instance shared with `libobs`, used as destination for the copy
  251. /// operation
  252. /// - src: Opaque pointer to ``MetalTexture`` instance shared with `libobs`, used as source for the copy operation
  253. ///
  254. /// > Important: Execution will be blocked by waiting for the blit command encoder to finish the copy operation.
  255. @_cdecl("device_stage_texture")
  256. public func device_stage_texture(device: UnsafeRawPointer, dst: UnsafeRawPointer, src: UnsafeRawPointer) {
  257. let device: MetalDevice = unretained(device)
  258. let source: MetalTexture = unretained(src)
  259. let destination: MetalStageBuffer = unretained(dst)
  260. do {
  261. try device.stageTextureToBuffer(source: source, destination: destination)
  262. } catch let error as MetalError.MTLDeviceError {
  263. OBSLog(.error, "device_clear: \(error.description)")
  264. } catch {
  265. OBSLog(.error, "device_clear: Unknown error occurred")
  266. }
  267. }
  268. /// Gets the width of the texture wrapped by the ``MetalTexture`` instance
  269. /// - Parameter tex: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  270. /// - Returns: Width of the texture
  271. @_cdecl("gs_texture_get_width")
  272. public func device_texture_get_width(tex: UnsafeRawPointer) -> UInt32 {
  273. let texture: MetalTexture = unretained(tex)
  274. return UInt32(texture.texture.width)
  275. }
  276. /// Gets the height of the texture wrapped by the ``MetalTexture`` instance
  277. /// - Parameter tex: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  278. /// - Returns: Height of the texture
  279. @_cdecl("gs_texture_get_height")
  280. public func device_texture_get_height(tex: UnsafeRawPointer) -> UInt32 {
  281. let texture: MetalTexture = unretained(tex)
  282. return UInt32(texture.texture.height)
  283. }
  284. /// Gets the color format of the texture wrapped by the ``MetalTexture`` instance
  285. /// - Parameter tex: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  286. /// - Returns: Color format as defined by the `gs_color_format` enumeration
  287. @_cdecl("gs_texture_get_color_format")
  288. public func gs_texture_get_color_format(tex: UnsafeRawPointer) -> gs_color_format {
  289. let texture: MetalTexture = unretained(tex)
  290. return texture.texture.pixelFormat.gsColorFormat
  291. }
  292. /// Allocates memory for an update of the texture's image data wrapped by the ``MetalTexture`` instance.
  293. /// - Parameters:
  294. /// - tex: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  295. /// - ptr: Pointer to memory for the raw image data
  296. /// - linesize: Pointer to integer for the row size of the texture
  297. /// - Returns: `true` if the mapping memory was allocated successfully, `false` otherwise
  298. ///
  299. /// Metal does not provide "map" and "unmap" operations as they exist in Direct3D11, as resource management and
  300. /// synchronization needs to be handled explicitly by the application. Thus "mapping" just means that enough memory for
  301. /// raw image data is allocated and an unmanaged pointer to that memory is shared with `libobs` for writing the image data.
  302. ///
  303. /// To ensure that the data written into the memory provided by this function is actually used to update the texture,
  304. /// the corresponding function `gs_texture_unmap` needs to be used.
  305. ///
  306. /// > Important: This function can only be used to **push** new image data into the texture. To _pull_ image data from
  307. /// the texture, use a stage surface instead.
  308. @_cdecl("gs_texture_map")
  309. public func gs_texture_map(
  310. tex: UnsafeRawPointer, ptr: UnsafeMutablePointer<UnsafeMutableRawPointer>, linesize: UnsafeMutablePointer<UInt32>
  311. ) -> Bool {
  312. let texture: MetalTexture = unretained(tex)
  313. guard texture.texture.textureType == .type2D, let device = texture.device else {
  314. return false
  315. }
  316. let stageBuffer: MetalStageBuffer
  317. if texture.stageBuffer == nil
  318. || (texture.stageBuffer!.width != texture.texture.width
  319. && texture.stageBuffer!.height != texture.texture.height)
  320. {
  321. guard
  322. let buffer = MetalStageBuffer(
  323. device: device,
  324. width: texture.texture.width,
  325. height: texture.texture.height,
  326. format: texture.texture.pixelFormat
  327. )
  328. else {
  329. OBSLog(.error, "gs_texture_map: Unable to create MetalStageBuffer for mapping texture")
  330. return false
  331. }
  332. texture.stageBuffer = buffer
  333. stageBuffer = buffer
  334. } else {
  335. stageBuffer = texture.stageBuffer!
  336. }
  337. ptr.pointee = stageBuffer.buffer.contents()
  338. linesize.pointee = UInt32(stageBuffer.width * stageBuffer.format.bytesPerPixel!)
  339. return true
  340. }
  341. /// Writes back raw image data into the texture wrapped by the ``MetalTexture`` instance
  342. /// - Parameter tex: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  343. ///
  344. /// This function needs to be used in tandem with `gs_texture_map`, which allocates memory for raw image data that
  345. /// should be used in an update of the wrapped `MTLTexture`. This function will then actually replace the image data
  346. /// in the texture with that raw image data and deallocate the memory that was allocated during `gs_texture_map`.
  347. @_cdecl("gs_texture_unmap")
  348. public func gs_texture_unmap(tex: UnsafeRawPointer) {
  349. let texture: MetalTexture = unretained(tex)
  350. guard texture.texture.textureType == .type2D, let stageBuffer = texture.stageBuffer, let device = texture.device
  351. else {
  352. return
  353. }
  354. do {
  355. try device.stageBufferToTexture(source: stageBuffer, destination: texture)
  356. } catch let error as MetalError.MTLDeviceError {
  357. OBSLog(.error, "gs_texture_unmap: \(error.description)")
  358. } catch {
  359. OBSLog(.error, "gs_texture_unmap: Unknown error occurred")
  360. }
  361. }
  362. /// Gets an opaque pointer to the ``MTLTexture`` instance wrapped by the provided ``MetalTexture`` instance
  363. /// - Parameter tex: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  364. /// - Returns: Opaque pointer to ``MTLTexture`` instance
  365. ///
  366. /// > Important: The opaque pointer returned by this function is **unretained**, which means that the ``MTLTexture``
  367. /// instance it refers to might be deinitialized at any point when no other Swift code holds a strong reference to it.
  368. @_cdecl("gs_texture_get_obj")
  369. public func gs_texture_get_obj(tex: UnsafeRawPointer) -> OpaquePointer {
  370. let texture: MetalTexture = unretained(tex)
  371. let unretained = Unmanaged.passUnretained(texture.texture).toOpaque()
  372. return OpaquePointer(unretained)
  373. }
  374. /// Requests deinitialization of the ``MetalTexture`` instance shared with `libobs`
  375. /// - Parameter cubetex: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  376. ///
  377. /// The ownership of the shared pointer is transferred into this function and the instance is placed under
  378. /// Swift's memory management again.
  379. @_cdecl("gs_cubetexture_destroy")
  380. public func gs_cubetexture_destroy(cubetex: UnsafeRawPointer) {
  381. let _ = retained(cubetex) as MetalTexture
  382. }
  383. /// Gets the edge size of the cube texture wrapped by the ``MetalTexture`` instance
  384. /// - Parameter cubetex: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  385. /// - Returns: Edge size of the cube
  386. @_cdecl("gs_cubetexture_get_size")
  387. public func gs_cubetexture_get_size(cubetex: UnsafeRawPointer) -> UInt32 {
  388. let texture: MetalTexture = unretained(cubetex)
  389. return UInt32(texture.texture.width)
  390. }
  391. /// Gets the color format of the cube texture wrapped by the ``MetalTexture`` instance
  392. /// - Parameter cubetex: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  393. /// - Returns: Color format value
  394. @_cdecl("gs_cubetexture_get_color_format")
  395. public func gs_cubetexture_get_color_format(cubetex: UnsafeRawPointer) -> gs_color_format {
  396. let texture: MetalTexture = unretained(cubetex)
  397. return texture.texture.pixelFormat.gsColorFormat
  398. }
  399. /// Gets the device capability state for shared textures
  400. /// - Parameter device: Opaque pointer to ``MetalDevice`` instance shared with `libobs`
  401. /// - Returns: Always `true`
  402. ///
  403. /// While Metal provides a specific "shared texture" type, OBS Studio understands this to mean "textures shared between
  404. /// processes", which is usually achieved using ``IOSurface`` references on macOS. Metal textures can be created from
  405. /// these references, so this is always `true`.
  406. @_cdecl("device_shared_texture_available")
  407. public func device_shared_texture_available(device: UnsafeRawPointer) -> Bool {
  408. return true
  409. }
  410. /// Creates a ``MetalTexture`` wrapping an ``MTLTexture`` that was created using the provided ``IOSurface`` reference.
  411. /// - Parameters:
  412. /// - device: Opaque pointer to ``MetalDevice`` instance shared with `libobs`
  413. /// - iosurf: ``IOSurface`` reference to use as the image data source for the texture
  414. /// - Returns: An opaque pointer to a ``MetalTexture`` instance on success, `nil` otherwise
  415. ///
  416. /// If the provided ``IOSurface`` uses a video image format that has no compatible ``Metal`` pixel format, creation of
  417. /// the texture will fail.
  418. @_cdecl("device_texture_create_from_iosurface")
  419. public func device_texture_create_from_iosurface(device: UnsafeRawPointer, iosurf: IOSurfaceRef) -> OpaquePointer? {
  420. let device: MetalDevice = unretained(device)
  421. let texture = MetalTexture(device: device, surface: iosurf)
  422. guard let texture else {
  423. return nil
  424. }
  425. return texture.getRetained()
  426. }
  427. /// Replaces the current ``IOSurface``-based ``MTLTexture`` wrapped by the provided ``MetalTexture`` instance with a
  428. /// new instance.
  429. /// - Parameters:
  430. /// - texture: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  431. /// - iosurf: ``IOSurface`` reference to use as the image data source for the texture
  432. /// - Returns: An opaque pointer to a ``MetalTexture`` instance on success, `nil` otherwise
  433. ///
  434. /// The "rebind" mentioned in the function name is limited to the ``MTLTexture`` instance wrapped inside the
  435. /// ``MetalTexture`` instance, as textures are immutable objects (but their underlying data is mutable). This allows
  436. /// `libobs` to hold onto the same opaque ``MetalTexture`` pointer even though the backing surface might have changed.
  437. @_cdecl("gs_texture_rebind_iosurface")
  438. public func gs_texture_rebind_iosurface(texture: UnsafeRawPointer, iosurf: IOSurfaceRef) -> Bool {
  439. let texture: MetalTexture = unretained(texture)
  440. return texture.rebind(surface: iosurf)
  441. }
  442. /// Creates a new ``MetalTexture`` instance with an opaque shared texture "handle"
  443. /// - Parameters:
  444. /// - device: Opaque pointer to ``MetalTexture`` instance shared with `libobs`
  445. /// - handle: Arbitrary handle value that needs to be reinterpreted into the correct platform specific shared
  446. /// reference type
  447. /// - Returns: An opaque pointer to a ``MetalTexture`` instance on success, `nil` otherwise
  448. ///
  449. /// The "handle" is a generalised argument used on all platforms and needs to be converted into a platform-specific
  450. /// type before the "shared" texture can be created. In case of macOS this means converting the unsigned integer into
  451. /// a ``IOSurface`` address.
  452. ///
  453. /// > Warning: As the handle is a 32-bit integer, this can break on 64-bit systems if the ``IOSurface`` pointer
  454. /// address does not fit into a 32-bit number.
  455. @_cdecl("device_texture_open_shared")
  456. public func device_texture_open_shared(device: UnsafeRawPointer, handle: UInt32) -> OpaquePointer? {
  457. if let reference = IOSurfaceLookupFromMachPort(handle) {
  458. let texture = device_texture_create_from_iosurface(device: device, iosurf: reference)
  459. return texture
  460. } else {
  461. return nil
  462. }
  463. }