├── .spi.yml ├── Sources └── SharedGraphicsTools │ ├── Extensions │ ├── CoreML │ │ ├── MLMultiArray+DataSize.swift │ │ └── MLMultiArrayDataType+Size.swift │ ├── UIKit │ │ └── UIImage+GraphicsDataProvider.swift │ ├── Foundation │ │ └── UnsafeRawPointer+PageAligned.swift │ ├── Accelerate │ │ └── vImageBuffer+GraphicsDataProvider.swift │ ├── CoreGraphics │ │ └── CGContext+GraphicsDataProvider.swift │ ├── Metal │ │ └── MTLTexture+GraphicsDataProvider.swift │ └── CoreVideo │ │ ├── CVPixelBuffer+GraphicsDataProvider.swift │ │ └── IOSurface+GraphicsDataProvider.swift │ ├── SharedGraphicsTools.docc │ ├── Resources │ │ └── documentation-art │ │ │ └── shared-graphics-tools@2x.png │ ├── SharedGraphicsTools.md │ ├── WorkingWithSharedGraphicsBuffer.md │ └── WorkingWithGraphicsDataProvider.md │ ├── GraphicsDataProvider.swift │ ├── MultiplanarPlanarGraphicsDataProvider.swift │ ├── GraphicsData.swift │ └── MTLSharedGraphicsBuffer.swift ├── .github └── workflows │ └── test.yml ├── LICENSE ├── Package.swift ├── .gitignore ├── Tests └── SharedGraphicsToolsTests │ └── SharedGraphicsToolsTests.swift └── README.md /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [shared-graphics-tools] 5 | scheme: shared-graphics-tools 6 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/Extensions/CoreML/MLMultiArray+DataSize.swift: -------------------------------------------------------------------------------- 1 | import CoreML 2 | 3 | public extension MLMultiArray { 4 | var dataLength: Int { self.shape.map(\.intValue).reduce(1, *) * self.dataType.stride } 5 | } 6 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/SharedGraphicsTools.docc/Resources/documentation-art/shared-graphics-tools@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/computer-graphics-tools/shared-graphics-tools/HEAD/Sources/SharedGraphicsTools/SharedGraphicsTools.docc/Resources/documentation-art/shared-graphics-tools@2x.png -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/Extensions/UIKit/UIImage+GraphicsDataProvider.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | 3 | import UIKit 4 | 5 | extension UIImage: GraphicsDataProvider { 6 | public func graphicsData() throws -> GraphicsData { 7 | guard let cgImage = self.cgImage 8 | else { throw GraphicsDataProviderError.missingData } 9 | return try cgImage.graphicsData() 10 | } 11 | } 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/Extensions/Foundation/UnsafeRawPointer+PageAligned.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension UnsafeRawPointer { 4 | var isPageAligned: Bool { 5 | Int(bitPattern: self) % Int(getpagesize()) == 0 6 | } 7 | } 8 | 9 | public extension UnsafeMutableRawPointer { 10 | var isPageAligned: Bool { 11 | Int(bitPattern: self) % Int(getpagesize()) == 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test Swift Package 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | test: 9 | runs-on: macos-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: swift-actions/setup-swift@v2 14 | with: 15 | swift-version: '5.9' 16 | 17 | - name: Build 18 | run: swift build 19 | 20 | - name: Run tests 21 | run: swift test -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/Extensions/Accelerate/vImageBuffer+GraphicsDataProvider.swift: -------------------------------------------------------------------------------- 1 | import Accelerate 2 | 3 | extension vImage_Buffer: GraphicsDataProvider { 4 | public func graphicsData() throws -> GraphicsData { 5 | return .init( 6 | width: self.width, 7 | height: self.height, 8 | baseAddress: self.data, 9 | bytesPerRow: UInt(self.rowBytes) 10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/Extensions/CoreML/MLMultiArrayDataType+Size.swift: -------------------------------------------------------------------------------- 1 | import CoreML 2 | 3 | public extension MLMultiArrayDataType { 4 | var size: Int { 5 | switch self { 6 | case .double: return MemoryLayout.size 7 | case .float64: return MemoryLayout.size 8 | case .float32: return MemoryLayout.size 9 | case .float16: return MemoryLayout.size 10 | case .float: return MemoryLayout.size 11 | case .int32: return MemoryLayout.size 12 | @unknown default: return 0 13 | } 14 | } 15 | var stride: Int { 16 | switch self { 17 | case .double: return MemoryLayout.stride 18 | case .float64: return MemoryLayout.stride 19 | case .float32: return MemoryLayout.stride 20 | case .float16: return MemoryLayout.stride 21 | case .float: return MemoryLayout.stride 22 | case .int32: return MemoryLayout.stride 23 | @unknown default: return 0 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Eugene Bokhan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/Extensions/CoreGraphics/CGContext+GraphicsDataProvider.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | import Foundation 3 | 4 | extension CGContext: GraphicsDataProvider { 5 | public func graphicsData() throws -> GraphicsData { 6 | guard let baseAddress = self.data 7 | else { throw GraphicsDataProviderError.missingData } 8 | return .init( 9 | width: UInt(self.width), 10 | height: UInt(self.height), 11 | baseAddress: baseAddress, 12 | bytesPerRow: UInt(self.bytesPerRow) 13 | ) 14 | } 15 | } 16 | 17 | extension CGImage: GraphicsDataProvider { 18 | public func graphicsData() throws -> GraphicsData { 19 | guard let dataProvider = self.dataProvider, 20 | let data = dataProvider.data, 21 | let readOnlyBaseAddress = CFDataGetBytePtr(data) 22 | else { throw GraphicsDataProviderError.missingData } 23 | 24 | let mutableBaseAddress = UnsafeMutablePointer(mutating: readOnlyBaseAddress) 25 | 26 | return .init( 27 | width: UInt(self.width), 28 | height: UInt(self.height), 29 | baseAddress: mutableBaseAddress, 30 | bytesPerRow: UInt(self.bytesPerRow) 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/SharedGraphicsTools.docc/SharedGraphicsTools.md: -------------------------------------------------------------------------------- 1 | # ``SharedGraphicsTools`` 2 | 3 | ![SharedGraphicsTools](shared-graphics-tools.png) 4 | 5 | Efficiently share graphics memory between different APIs on Apple platforms. 6 | 7 | ## Overview 8 | 9 | SharedGraphicsTools is a powerful library that provides a convenient way to share graphics memory between different APIs on Apple platforms. It's designed to reduce memory traffic in computer vision pipelines and other graphics-intensive applications. 10 | 11 | The library introduces the ``GraphicsDataProvider`` protocol, which allows you to reinterpret graphics data in various formats without unnecessary memory copies. It also provides the ``MTLSharedGraphicsBuffer`` class for working with page-aligned memory buffers across multiple graphics APIs. 12 | 13 | ## Topics 14 | 15 | ### Essentials 16 | 17 | - 18 | - 19 | 20 | ### Core Protocols 21 | 22 | - ``GraphicsDataProvider`` 23 | - ``MultiplanarPlanarGraphicsDataProvider`` 24 | 25 | ### Main Classes 26 | 27 | - ``MTLSharedGraphicsBuffer`` 28 | - ``GraphicsData`` 29 | 30 | 31 | SharedGraphicsTools is built on top of [MetalTools](https://github.com/eugenebokhan/metal-tools) and [CoreVideoTools](https://github.com/eugenebokhan/core-video-tools), which provide additional functionality for working with Metal and Core Video respectively. 32 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | 3 | import PackageDescription 4 | let package = Package( 5 | name: "shared-graphics-tools", 6 | platforms: [ 7 | .iOS(.v13), 8 | .macOS(.v10_15), 9 | .macCatalyst(.v13) 10 | ], 11 | products: [ 12 | .library( 13 | name: "SharedGraphicsTools", 14 | targets: ["SharedGraphicsTools"] 15 | ), 16 | ], 17 | dependencies: [ 18 | .package( 19 | url: "https://github.com/eugenebokhan/metal-tools.git", 20 | .upToNextMinor(from: "1.3.1") 21 | ), 22 | .package( 23 | url: "https://github.com/eugenebokhan/core-video-tools.git", 24 | .upToNextMinor(from: "0.1.0") 25 | ) 26 | ], 27 | targets: [ 28 | .target( 29 | name: "SharedGraphicsTools", 30 | dependencies: [ 31 | .product( 32 | name: "MetalTools", 33 | package: "metal-tools" 34 | ), 35 | .product( 36 | name: "CoreVideoTools", 37 | package: "core-video-tools" 38 | ) 39 | ] 40 | ), 41 | .testTarget( 42 | name: "SharedGraphicsToolsTests", 43 | dependencies: [ 44 | .target(name: "SharedGraphicsTools"), 45 | .product(name: "MetalComputeTools", package: "metal-tools") 46 | ] 47 | ) 48 | ] 49 | ) 50 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/Extensions/Metal/MTLTexture+GraphicsDataProvider.swift: -------------------------------------------------------------------------------- 1 | import MetalTools 2 | import Accelerate 3 | import CoreVideoTools 4 | import CoreML 5 | 6 | public extension MTLTexture { 7 | func graphicsData() throws -> GraphicsData { 8 | guard let buffer = self.buffer 9 | else { throw GraphicsDataProviderError.missingData } 10 | 11 | return .init( 12 | width: UInt(self.width), 13 | height: UInt(self.height), 14 | baseAddress: buffer.contents(), 15 | bytesPerRow: UInt(self.bufferBytesPerRow) 16 | ) 17 | } 18 | 19 | func vImageBufferView() throws -> vImage_Buffer { 20 | return try self.graphicsData().vImageBufferView() 21 | } 22 | 23 | func cvPixelBufferView(cvPixelFormat: CVPixelFormat) throws -> CVPixelBuffer { 24 | return try self.graphicsData().cvPixelBufferView(cvPixelFormat: cvPixelFormat) 25 | } 26 | 27 | func mlMultiArrayView( 28 | shape: [Int], 29 | dataType: MLMultiArrayDataType 30 | ) throws -> MLMultiArray { 31 | return try self.graphicsData().mlMultiArrayView( 32 | shape: shape, 33 | dataType: dataType 34 | ) 35 | } 36 | 37 | func mlMultiArrayView( 38 | shape: [Int], 39 | strides: [Int], 40 | dataType: MLMultiArrayDataType 41 | ) throws -> MLMultiArray { 42 | return try self.graphicsData().mlMultiArrayView( 43 | shape: shape, 44 | strides: strides, 45 | dataType: dataType 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/GraphicsDataProvider.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CoreVideoTools 3 | import CoreVideo 4 | import MetalTools 5 | import CoreML 6 | import Accelerate 7 | 8 | public enum GraphicsDataProviderError: Error { 9 | case missingData 10 | case missingDataOfPlane(Int) 11 | } 12 | 13 | public protocol GraphicsDataProvider { 14 | func graphicsData() throws -> GraphicsData 15 | } 16 | 17 | public extension GraphicsDataProvider { 18 | func vImageBufferView() throws -> vImage_Buffer { 19 | return try self.graphicsData().vImageBufferView() 20 | } 21 | 22 | func cvPixelBufferView(cvPixelFormat: CVPixelFormat) throws -> CVPixelBuffer { 23 | return try self.graphicsData().cvPixelBufferView(cvPixelFormat: cvPixelFormat) 24 | } 25 | 26 | func mlMultiArrayView( 27 | shape: [Int], 28 | dataType: MLMultiArrayDataType 29 | ) throws -> MLMultiArray { 30 | return try self.graphicsData().mlMultiArrayView( 31 | shape: shape, 32 | dataType: dataType 33 | ) 34 | } 35 | 36 | func mlMultiArrayView( 37 | shape: [Int], 38 | strides: [Int], 39 | dataType: MLMultiArrayDataType 40 | ) throws -> MLMultiArray { 41 | return try self.graphicsData().mlMultiArrayView( 42 | shape: shape, 43 | strides: strides, 44 | dataType: dataType 45 | ) 46 | } 47 | 48 | func mtlBufferView(device: MTLDevice) throws -> MTLBuffer { 49 | return try self.graphicsData().mtlBufferView(device: device) 50 | } 51 | 52 | func mtlTextureView( 53 | device: MTLDevice, 54 | pixelFormat: MTLPixelFormat, 55 | usage: MTLTextureUsage = [] 56 | ) throws -> MTLTexture { 57 | return try self.graphicsData().mtlTextureView( 58 | device: device, 59 | pixelFormat: pixelFormat, 60 | usage: usage 61 | ) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/Extensions/CoreVideo/CVPixelBuffer+GraphicsDataProvider.swift: -------------------------------------------------------------------------------- 1 | import CoreVideoTools 2 | 3 | extension CVPixelBuffer: GraphicsDataProvider { 4 | public func graphicsData() throws -> GraphicsData { 5 | try self.lockBaseAddress(lockFlags: []) 6 | 7 | let width = self.width 8 | let height = self.height 9 | let bytesPerRow = self.bytesPerRow 10 | guard let baseAddress = self.baseAddress, 11 | width > 0, height > 0, bytesPerRow > 0 12 | else { throw GraphicsDataProviderError.missingData } 13 | 14 | let graphicsData = GraphicsData( 15 | width: UInt(width), 16 | height: UInt(height), 17 | baseAddress: baseAddress, 18 | bytesPerRow: UInt(bytesPerRow) 19 | ) 20 | 21 | try self.unlockBaseAddress(unlockFlags: []) 22 | 23 | return graphicsData 24 | } 25 | } 26 | 27 | extension CVPixelBuffer: MultiplanarPlanarGraphicsDataProvider { 28 | public func graphicsData(of planeIndex: Int) throws -> GraphicsData { 29 | try self.lockBaseAddress(lockFlags: []) 30 | 31 | guard planeIndex < self.planeCount 32 | else { throw GraphicsDataProviderError.missingDataOfPlane(planeIndex) } 33 | 34 | let width = self.width(of: planeIndex) 35 | let height = self.height(of: planeIndex) 36 | let bytesPerRow = self.bytesPerRow(of: planeIndex) 37 | guard let baseAddress = self.baseAddress(of: planeIndex), 38 | width > 0, height > 0, bytesPerRow > 0 39 | else { throw GraphicsDataProviderError.missingDataOfPlane(planeIndex) } 40 | 41 | let graphicsData = GraphicsData( 42 | width: UInt(width), 43 | height: UInt(height), 44 | baseAddress: baseAddress, 45 | bytesPerRow: UInt(bytesPerRow) 46 | ) 47 | 48 | try self.unlockBaseAddress(unlockFlags: []) 49 | 50 | return graphicsData 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/MultiplanarPlanarGraphicsDataProvider.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Accelerate 3 | import CoreVideoTools 4 | import CoreML 5 | 6 | public protocol MultiplanarPlanarGraphicsDataProvider { 7 | func graphicsData(of planeIndex: Int) throws -> GraphicsData 8 | } 9 | 10 | public extension MultiplanarPlanarGraphicsDataProvider { 11 | func vImageBufferView(planeIndex: Int) throws -> vImage_Buffer { 12 | return try self.graphicsData(of: planeIndex).vImageBufferView() 13 | } 14 | 15 | func cvPixelBufferView( 16 | planeIndex: Int, 17 | cvPixelFormat: CVPixelFormat 18 | ) throws -> CVPixelBuffer { 19 | return try self.graphicsData(of: planeIndex).cvPixelBufferView(cvPixelFormat: cvPixelFormat) 20 | } 21 | 22 | func mlMultiArrayView( 23 | planeIndex: Int, 24 | shape: [Int], 25 | dataType: MLMultiArrayDataType 26 | ) throws -> MLMultiArray { 27 | return try self.graphicsData(of: planeIndex).mlMultiArrayView( 28 | shape: shape, 29 | dataType: dataType 30 | ) 31 | } 32 | 33 | func mlMultiArrayView( 34 | planeIndex: Int, 35 | shape: [Int], 36 | strides: [Int], 37 | dataType: MLMultiArrayDataType 38 | ) throws -> MLMultiArray { 39 | return try self.graphicsData(of: planeIndex).mlMultiArrayView( 40 | shape: shape, 41 | strides: strides, 42 | dataType: dataType 43 | ) 44 | } 45 | 46 | func mtlBufferView( 47 | planeIndex: Int, 48 | device: MTLDevice 49 | ) throws -> MTLBuffer { 50 | return try self.graphicsData(of: planeIndex).mtlBufferView(device: device) 51 | } 52 | 53 | func mtlTextureView( 54 | planeIndex: Int, 55 | device: MTLDevice, 56 | pixelFormat: MTLPixelFormat, 57 | usage: MTLTextureUsage = [] 58 | ) throws -> MTLTexture { 59 | return try self.graphicsData(of: planeIndex).mtlTextureView( 60 | device: device, 61 | pixelFormat: pixelFormat, 62 | usage: usage 63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/Extensions/CoreVideo/IOSurface+GraphicsDataProvider.swift: -------------------------------------------------------------------------------- 1 | import CoreVideoTools 2 | 3 | extension IOSurface: GraphicsDataProvider { 4 | public func graphicsData() throws -> GraphicsData { 5 | self.lock(options: [], seed: nil) 6 | 7 | let width = self.width 8 | let height = self.height 9 | let baseAddress = self.baseAddress 10 | let bytesPerRow = self.bytesPerRow 11 | let bytesPerElement = self.bytesPerElement 12 | guard width > 0, height > 0, bytesPerRow > 0, bytesPerElement > 0 13 | else { throw GraphicsDataProviderError.missingData } 14 | 15 | let graphicsData = GraphicsData( 16 | width: UInt(width), 17 | height: UInt(height), 18 | baseAddress: baseAddress, 19 | bytesPerRow: UInt(bytesPerRow) 20 | ) 21 | 22 | self.unlock(options: [], seed: nil) 23 | 24 | return graphicsData 25 | } 26 | } 27 | extension IOSurface: MultiplanarPlanarGraphicsDataProvider { 28 | public func graphicsData(of planeIndex: Int) throws -> GraphicsData { 29 | self.lock(options: [], seed: nil) 30 | 31 | guard planeIndex < self.planeCount 32 | else { throw GraphicsDataProviderError.missingDataOfPlane(planeIndex) } 33 | 34 | let width = self.widthOfPlane(at: planeIndex) 35 | let height = self.heightOfPlane(at: planeIndex) 36 | let baseAddress = self.baseAddressOfPlane(at: planeIndex) 37 | let bytesPerRow = self.bytesPerRowOfPlane(at: planeIndex) 38 | let bytesPerElement = self.bytesPerElementOfPlane(at: planeIndex) 39 | guard width > 0, height > 0, bytesPerRow > 0, bytesPerElement > 0 40 | else { throw GraphicsDataProviderError.missingDataOfPlane(planeIndex) } 41 | 42 | let graphicsData = GraphicsData( 43 | width: UInt(width), 44 | height: UInt(height), 45 | baseAddress: baseAddress, 46 | bytesPerRow: UInt(bytesPerRow) 47 | ) 48 | 49 | self.unlock(options: [], seed: nil) 50 | 51 | return graphicsData 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | .swiftpm 92 | *.DS_Store 93 | Package.resolved 94 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/SharedGraphicsTools.docc/WorkingWithSharedGraphicsBuffer.md: -------------------------------------------------------------------------------- 1 | # Working with SharedGraphicsBuffer 2 | 3 | Learn how to use `MTLSharedGraphicsBuffer` to efficiently share graphics memory between different APIs. 4 | 5 | ## Overview 6 | 7 | ``MTLSharedGraphicsBuffer`` is a powerful tool in the SharedGraphicsTools library that allows you to work with graphics data across multiple APIs without unnecessary memory copies. It provides a page-aligned memory buffer that can be accessed by Metal, Core Graphics, vImage, and Core Video simultaneously. 8 | 9 | ## Creating a SharedGraphicsBuffer 10 | 11 | To create a `MTLSharedGraphicsBuffer`, you need to specify the dimensions, pixel format, and Metal device: 12 | 13 | ```swift 14 | import SharedGraphicsTools 15 | import MetalTools 16 | 17 | let context = try MTLContext() 18 | 19 | let sharedBuffer = try MTLSharedGraphicsBuffer( 20 | device: context.device, 21 | width: 600, 22 | height: 600, 23 | pixelFormat: .bgra8Unorm 24 | ) 25 | ``` 26 | 27 | ## Accessing Different Views of the Buffer 28 | 29 | Once you've created a ``MTLSharedGraphicsBuffer``, you can access its contents through various APIs: 30 | 31 | ### Metal Texture 32 | 33 | ```swift 34 | let metalTexture: MTLTexture = sharedBuffer.texture 35 | ``` 36 | 37 | ### Core Graphics Context 38 | 39 | ```swift 40 | let cgContext: CGContext = sharedBuffer.cgContext 41 | ``` 42 | 43 | ### vImage Buffer 44 | 45 | ```swift 46 | let vImageBuffer: vImage_Buffer = sharedBuffer.vImageBuffer 47 | ``` 48 | 49 | ### Core Video Pixel Buffer 50 | 51 | ```swift 52 | let pixelBuffer: CVPixelBuffer = sharedBuffer.pixelBuffer 53 | ``` 54 | 55 | ## Example: Combining Core Graphics and Metal 56 | 57 | Here's an example of how you can use ``MTLSharedGraphicsBuffer`` to combine Core Graphics drawing with Metal processing without any memory transfers: 58 | 59 | ```swift 60 | // Draw a white rectangle using Core Graphics 61 | let rect = CGRect(x: 125, y: 125, width: 300, height: 300) 62 | let whiteColor = CGColor(red: 1, green: 1, blue: 1, alpha: 1) 63 | sharedBuffer.cgContext.setFillColor(whiteColor) 64 | sharedBuffer.cgContext.fill(rect) 65 | 66 | // Process the result using Metal 67 | try context.schedule { commandBuffer in 68 | someFancyMetalFilter.encode( 69 | source: sharedBuffer.texture, 70 | destination: resultTexture, 71 | in: commandBuffer 72 | ) 73 | } 74 | ``` 75 | 76 | ## Important Considerations 77 | 78 | - The memory used by ``MTLSharedGraphicsBuffer`` is page-aligned, which is required for certain operations in Metal. 79 | - While ``MTLSharedGraphicsBuffer`` reduces the need for memory copies, be mindful of potential synchronization issues when accessing the buffer from multiple APIs concurrently. 80 | The pixel format specified when creating the buffer should be compatible with all the APIs you intend to use. 81 | 82 | By leveraging ``MTLSharedGraphicsBuffer``, you can create more efficient graphics pipelines, especially in scenarios involving multiple graphics APIs. 83 | -------------------------------------------------------------------------------- /Tests/SharedGraphicsToolsTests/SharedGraphicsToolsTests.swift: -------------------------------------------------------------------------------- 1 | #if !targetEnvironment(simulator) 2 | 3 | import XCTest 4 | import MetalTools 5 | @testable import SharedGraphicsTools 6 | 7 | @available(iOS 14.0, *) 8 | final class SharedGraphicsToolsTests: XCTestCase { 9 | 10 | enum Error: Swift.Error { 11 | case missingResultValues 12 | } 13 | 14 | func testSharedBuffer() throws { 15 | let contextSize = CGSize(width: 40, height: 40) 16 | let rect = CGRect(x: 10, y: 10, width: 20, height: 20) 17 | 18 | let context = try MTLContext() 19 | let statisticsKernel = MPSImageStatisticsMeanAndVariance(device: context.device) 20 | let sharedBuffer = try MTLSharedGraphicsBuffer( 21 | device: context.device, 22 | width: Int(contextSize.width), 23 | height: Int(contextSize.height), 24 | pixelFormat: .r16Float 25 | ) 26 | 27 | sharedBuffer.cgContext.setFillColor(CGColor(red: 0, green: 0, blue: 0, alpha: 1)) 28 | sharedBuffer.cgContext.fill(CGRect(origin: .zero, size: contextSize)) 29 | sharedBuffer.cgContext.setFillColor(CGColor(red: 1, green: 1, blue: 1, alpha: 1)) 30 | sharedBuffer.cgContext.fill(rect) 31 | 32 | let bufferElementCount = sharedBuffer.bytesPerRow * sharedBuffer.height / MemoryLayout.stride 33 | let values = sharedBuffer.buffer.array(of: Float16.self, count: bufferElementCount) ?? [] 34 | let valuesSum = values.map(Double.init).reduce(0, +) 35 | let expectedSum = rect.width * rect.height 36 | 37 | XCTAssertEqual(valuesSum, expectedSum) 38 | 39 | let resultTexture = try sharedBuffer.texture.matchingTexture() 40 | let resultBuffer = try context.buffer( 41 | length: 2 * MemoryLayout.size, 42 | options: .storageModeShared 43 | ) 44 | 45 | try context.scheduleAndWait { commandBuffer in 46 | statisticsKernel( 47 | source: sharedBuffer.texture, 48 | destination: resultTexture, 49 | in: commandBuffer 50 | ) 51 | commandBuffer.blit { blitEncoder in 52 | blitEncoder.copy( 53 | from: resultTexture, 54 | sourceSlice: 0, 55 | sourceLevel: 0, 56 | sourceOrigin: .zero, 57 | sourceSize: MTLSize(width: 2, height: 1, depth: 1), 58 | to: resultBuffer, 59 | destinationOffset: 0, 60 | destinationBytesPerRow: 2 * MemoryLayout.size, 61 | destinationBytesPerImage: 2 * MemoryLayout.size) 62 | } 63 | } 64 | 65 | guard let meanAndVarianceValues = resultBuffer.array(of: Float16.self, count: 2) 66 | else { throw Error.missingResultValues } 67 | 68 | let expectedMeanValue = Float32((rect.size.width * rect.size.height) / (contextSize.width * contextSize.height)) 69 | 70 | XCTAssertEqual(Float32(meanAndVarianceValues[0]), expectedMeanValue, accuracy: 0.0001) 71 | } 72 | } 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/SharedGraphicsTools.docc/WorkingWithGraphicsDataProvider.md: -------------------------------------------------------------------------------- 1 | # Working with GraphicsDataProvider 2 | 3 | Learn how to use the `GraphicsDataProvider` protocol to efficiently work with and convert between different graphics data formats. 4 | 5 | ## Overview 6 | 7 | The ``GraphicsDataProvider`` protocol is a core component of SharedGraphicsTools, allowing you to reinterpret graphics data in various formats without unnecessary memory copies. This can significantly improve performance in graphics-intensive applications. 8 | 9 | ## Conforming Types 10 | 11 | By default, the following types conform to ``GraphicsDataProvider``: 12 | 13 | - `vImage_Buffer` 14 | - `CGContext` 15 | - `CGImage` 16 | - `CVPixelBuffer` 17 | - `IOSurface` 18 | - `MTLTexture` 19 | - `UIImage` (iOS only) 20 | 21 | ## Accessing Different Views 22 | 23 | Once you have an object conforming to ``GraphicsDataProvider``, you can access its data in various formats: 24 | 25 | ### vImage Buffer View 26 | 27 | ```swift 28 | let vImageBuffer: vImage_Buffer = try graphicsDataProvider.vImageBufferView() 29 | ``` 30 | 31 | ### CVPixelBuffer View 32 | 33 | ```swift 34 | let pixelBuffer: CVPixelBuffer = try graphicsDataProvider.cvPixelBufferView(cvPixelFormat: .type_32BGRA) 35 | ``` 36 | 37 | ### MLMultiArray View 38 | 39 | ```swift 40 | let multiArray: MLMultiArray = try graphicsDataProvider.mlMultiArrayView( 41 | shape: [1, 3, height, width], 42 | dataType: .float32 43 | ) 44 | ``` 45 | 46 | ### MTLBuffer View 47 | 48 | ```swift 49 | let metalBuffer: MTLBuffer = try graphicsDataProvider.mtlBufferView(device: metalDevice) 50 | ``` 51 | 52 | ### MTLTexture View 53 | 54 | ```swift 55 | let metalTexture: MTLTexture = try graphicsDataProvider.mtlTextureView( 56 | device: metalDevice, 57 | pixelFormat: .bgra8Unorm, 58 | usage: [.shaderRead, .shaderWrite] 59 | ) 60 | ``` 61 | 62 | ## Example: Converting Between Formats 63 | 64 | Here's an example of how you can use ``GraphicsDataProvider`` to efficiently convert between different graphics formats: 65 | 66 | ```swift 67 | let ioSurface: IOSurface = // ... create or obtain an IOSurface 68 | 69 | // Convert to vImage_Buffer 70 | let vImageBuffer: vImage_Buffer = try ioSurface.vImageBufferView() 71 | 72 | // Convert to CVPixelBuffer 73 | let pixelBuffer: CVPixelBuffer = try vImageBuffer.cvPixelBufferView(cvPixelFormat: .type_32BGRA) 74 | 75 | // Convert to MTLTexture 76 | let texture: MTLTexture = try vImageBuffer.mtlTextureView( 77 | device: metalDevice, 78 | pixelFormat: .bgra8Unorm, 79 | usage: [.shaderRead, .shaderWrite] 80 | ) 81 | ``` 82 | 83 | In this example, we start with an `IOSurface` and convert it to a `vImage_Buffer`, `CVPixelBuffer`, and `MTLTexture` without any memory copies.\ 84 | 85 | ## Important Considerations 86 | 87 | - While GraphicsDataProvider reduces the need for memory copies, be mindful of potential synchronization issues when accessing the data from multiple APIs concurrently. 88 | - Some conversions may have specific requirements or limitations. Always check the documentation for each method and handle potential errors. 89 | - The pixel format or data type should be compatible across different views of the same data. 90 | 91 | By leveraging ``GraphicsDataProvider``, you can create more efficient graphics pipelines, especially in scenarios involving multiple graphics APIs. 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shared Graphics Tools 2 | 3 | [![Platform Compatibility](https://img.shields.io/badge/Platforms-iOS%20|%20macOS%20|%20watchOS%20|%20tvOS-brightgreen)](https://swift.org/platforms/) 4 | [![Swift Version](https://img.shields.io/badge/Swift-5.9-orange)](https://swift.org) 5 | 6 |

7 | 8 |

9 | 10 | ## Description 11 | 12 | A set of tools and extensions that allow sharing page-aligned memory allocation between different graphics APIs and provide view interop for them. This library helps [ZERO10](https://zero10.ar) to reduce memory traffic in its computer vision pipelines. 13 | 14 | ## Usage 15 | 16 | ### Reinterpret Graphics Data 17 | 18 | This library introduces a `GraphicsDataProvider` protocol. Every type that conforms to it allows to make a view of its contents as: 19 | 20 | - `vImage_Buffer` 21 | - `CVPixelBuffer` 22 | - `MLMultiArray` 23 | - `MTLBuffer` 24 | - `MTLTexture` 25 | 26 | By default, these types are `vImage_Buffer`, `CGContext`, `CGImage`, `CVPixelBuffer`, `IOSurface`, `MTLTexture`, and `UIImage`. 27 | 28 | That means it is possible to create an `IOSurface` and reinterpret its contents, such as `CVPixelBuffer` or `vImage_Buffer`. 29 | 30 | ```swift 31 | let vImageBuffer: vImage_Buffer = try ioSurface.vImageBufferView() 32 | let pixelBuffer: CVPixelBuffer = try vImageBuffer.cvPixelBufferView(cvPixelFormat: .type_32BGRA) 33 | let texture: MTLTexture = try vImageBuffer.mtlTextureView( 34 | device: context.device, 35 | pixelFormat: .bgra8Unorm, 36 | usage: [.shaderRead, .shaderWrite] 37 | ) 38 | ``` 39 | 40 | This is quite handy as it allows for the reduction of boilerplate code as well as memory copy operations. 41 | 42 | ### Page-aligned Memory Buffer 43 | 44 | In some cases, it is impossible to make an `MTLBuffer` or `MTLTexture` view on some graphics data as it is [requires the allocation pointer of the data to be page-aligned](https://developer.apple.com/documentation/metal/mtldevice/1433382-makebuffer). This requirement is done because internally, before the creation of `MTLBuffer`, Metal marks certain memory pages as accessible to GPU, and the shared memory address beginning should align with the page address. 45 | 46 | To overcome such situations, `MTLSharedGraphicsBuffer` is provided. Inside, this class encapsulates the allocation of page-aligned memory and initialises an `MTLBuffer`, `MTLTexture`, `vImage_Buffer`, `CVPixelBuffer` and `CGContext` in such a way so they look at the same memory. 47 | 48 | Given that, you can do such tricks as combining `CoreGraphics` rendering with `Metal` without memory transfers: 49 | 50 | ```swift 51 | let context = try MTLContext() 52 | 53 | let sharedBuffer = try MTLSharedGraphicsBuffer( 54 | device: context.device, 55 | width: 600, 56 | height: 600, 57 | pixelFormat: .bgra8Unorm 58 | ) 59 | 60 | let rect = CGRect( 61 | x: 125, 62 | y: 125, 63 | width: 300, 64 | height: 300 65 | ) 66 | let whiteColor = CGColor( 67 | red: 1, 68 | green: 1, 69 | blue: 1, 70 | alpha: 1 71 | ) 72 | 73 | // Draw with CoreGraphics 74 | sharedBuffer.cgContext.setFillColor(whiteColor) 75 | sharedBuffer.cgContext.fill(rect) 76 | 77 | // Continue with Metal 78 | try context.schedule { commandBuffer in 79 | someFancyMetalFilter.encode( 80 | source: sharedBuffer.texture, 81 | destination: resultTexture, 82 | in: commandBuffer 83 | ) 84 | } 85 | ``` 86 | 87 | ## License 88 | 89 | `SharedGraphicsTools` is licensed under [MIT license](LICENSE). 90 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/GraphicsData.swift: -------------------------------------------------------------------------------- 1 | import CoreVideoTools 2 | import MetalTools 3 | import Accelerate 4 | import CoreML 5 | 6 | public struct GraphicsData { 7 | 8 | public enum Error: Swift.Error { 9 | case outOfBounds 10 | case bufferSizeIsNot4096ByteAligned 11 | } 12 | 13 | public let width: UInt 14 | public let height: UInt 15 | public let baseAddress: UnsafeMutableRawPointer 16 | public let bytesPerRow: UInt 17 | public var dataLength: UInt { self.bytesPerRow * self.height } 18 | 19 | public func vImageBufferView() -> vImage_Buffer { 20 | return .init( 21 | data: self.baseAddress, 22 | height: vImagePixelCount(self.height), 23 | width: vImagePixelCount(self.width), 24 | rowBytes: Int(self.bytesPerRow) 25 | ) 26 | } 27 | 28 | public func cvPixelBufferView(cvPixelFormat: CVPixelFormat) throws -> CVPixelBuffer { 29 | return try .create( 30 | width: Int(self.width), 31 | height: Int(self.height), 32 | cvPixelFormat: cvPixelFormat, 33 | baseAddress: self.baseAddress, 34 | bytesPerRow: Int(self.bytesPerRow) 35 | ) 36 | } 37 | 38 | public func mlMultiArrayView( 39 | shape: [Int], 40 | dataType: MLMultiArrayDataType 41 | ) throws -> MLMultiArray { 42 | var reversedStrides = Array(repeating: 1, count: shape.count) 43 | let reversedShape = Array(shape.reversed()) 44 | for i in 1 ..< reversedStrides.count { 45 | reversedStrides[i] = reversedStrides[i - 1] * reversedShape[i - 1] 46 | } 47 | let strides = Array(reversedStrides.reversed()) 48 | 49 | return try MLMultiArray( 50 | dataPointer: self.baseAddress, 51 | shape: shape.map(NSNumber.init(value:)), 52 | dataType: dataType, 53 | strides: strides.map(NSNumber.init(value:)) 54 | ) 55 | } 56 | 57 | public func mlMultiArrayView( 58 | shape: [Int], 59 | strides: [Int], 60 | dataType: MLMultiArrayDataType 61 | ) throws -> MLMultiArray { 62 | let dataLength = shape.reduce(1, *) * dataType.stride 63 | guard dataLength <= self.dataLength else { throw Error.outOfBounds } 64 | 65 | return try MLMultiArray( 66 | dataPointer: self.baseAddress, 67 | shape: shape.map(NSNumber.init(value:)), 68 | dataType: dataType, 69 | strides: strides.map(NSNumber.init(value:)) 70 | ) 71 | } 72 | 73 | public func mtlBufferView(device: MTLDevice) throws -> MTLBuffer { 74 | let allocationSize = Int(self.bytesPerRow * self.height) 75 | 76 | guard allocationSize & (4096 - 1) == 0 77 | else { throw Error.bufferSizeIsNot4096ByteAligned } 78 | 79 | guard let buffer = device.makeBuffer( 80 | bytesNoCopy: self.baseAddress, 81 | length: allocationSize, 82 | options: .storageModeShared, 83 | deallocator: nil 84 | ) else { throw MetalError.MTLDeviceError.bufferCreationFailed } 85 | 86 | return buffer 87 | } 88 | 89 | public func mtlTextureView( 90 | device: MTLDevice, 91 | pixelFormat: MTLPixelFormat, 92 | usage: MTLTextureUsage = [] 93 | ) throws -> MTLTexture { 94 | let buffer = try self.mtlBufferView(device: device) 95 | 96 | let descriptor = MTLTextureDescriptor() 97 | descriptor.width = Int(self.width) 98 | descriptor.height = Int(self.height) 99 | descriptor.pixelFormat = pixelFormat 100 | descriptor.usage = usage 101 | descriptor.storageMode = .shared 102 | 103 | guard let texture = buffer.makeTexture( 104 | descriptor: descriptor, 105 | offset: 0, 106 | bytesPerRow: Int(self.bytesPerRow) 107 | ) else { throw MetalError.MTLDeviceError.textureCreationFailed } 108 | 109 | return texture 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/SharedGraphicsTools/MTLSharedGraphicsBuffer.swift: -------------------------------------------------------------------------------- 1 | #if !targetEnvironment(simulator) 2 | 3 | import Accelerate 4 | import CoreVideoTools 5 | import MetalTools 6 | 7 | public final class MTLSharedGraphicsBuffer: NSObject { 8 | // MARK: - Type Definitions 9 | 10 | public enum Error: Swift.Error { 11 | case allocationFailed 12 | case cgContextCreationFailed 13 | case unsupportedPixelFormat 14 | } 15 | 16 | public enum PixelFormat: CaseIterable { 17 | case r8Unorm 18 | case r16Float 19 | case r32Float 20 | case bgra8Unorm 21 | case bgra8Unorm_srgb 22 | case rgba16Float 23 | case rgba32Float 24 | 25 | fileprivate var mtlPixelFormat: MTLPixelFormat { 26 | switch self { 27 | case .r8Unorm: return .r8Unorm 28 | case .r16Float: return .r16Float 29 | case .r32Float: return .r32Float 30 | case .bgra8Unorm: return .bgra8Unorm 31 | case .bgra8Unorm_srgb: return .bgra8Unorm_srgb 32 | case .rgba16Float: return .rgba16Float 33 | case .rgba32Float: return .rgba32Float 34 | } 35 | } 36 | } 37 | 38 | // MARK: - Internal Properties 39 | 40 | public let allocationPointer: UnsafeMutableRawPointer 41 | public let bytesPerRow: Int 42 | public let buffer: MTLBuffer 43 | public let texture: MTLTexture 44 | public let vImageBuffer: vImage_Buffer 45 | public let pixelBuffer: CVPixelBuffer 46 | public let cgContext: CGContext 47 | public let colorSpace: CGColorSpace 48 | 49 | public var width: Int { self.texture.width } 50 | public var height: Int { self.texture.height } 51 | public var usage: MTLTextureUsage { self.texture.usage } 52 | public var resourceOptions: MTLResourceOptions { self.texture.resourceOptions } 53 | public var mtlPixelFormat: MTLPixelFormat { self.texture.pixelFormat } 54 | public var cvPixelFormat: CVPixelFormat { self.pixelBuffer.cvPixelFormat } 55 | public var bitmapInfo: CGBitmapInfo { self.cgContext.bitmapInfo } 56 | public var alfaInfo: CGImageAlphaInfo { self.cgContext.alphaInfo } 57 | 58 | private let allocationAddress: UInt 59 | private let allocationSize: vm_size_t 60 | 61 | // MARK: - Init 62 | 63 | public init( 64 | device: MTLDevice, 65 | width: Int, 66 | height: Int, 67 | pixelFormat: PixelFormat, 68 | forceColorSpace: CGColorSpace? = nil, 69 | usage: MTLTextureUsage = [.shaderRead, .shaderWrite, .renderTarget] 70 | ) throws { 71 | let mtlPixelFormat = pixelFormat.mtlPixelFormat 72 | guard let rawCVPixelFormat = mtlPixelFormat.compatibleCVPixelFormat, 73 | let bytesPerPixel = mtlPixelFormat.bytesPerPixel, 74 | let bitsPerComponent = mtlPixelFormat.bitsPerComponent, 75 | let bitmapInfo = mtlPixelFormat.compatibleBitmapInfo, 76 | let colorSpace = forceColorSpace ?? mtlPixelFormat.compatibleColorSpace 77 | else { throw Error.unsupportedPixelFormat } 78 | let cvPixelFormat = CVPixelFormat(rawValue: rawCVPixelFormat) 79 | 80 | let resourceOptions: MTLResourceOptions = [.crossPlatformSharedOrManaged] 81 | let textureDescriptor = MTLTextureDescriptor() 82 | textureDescriptor.pixelFormat = mtlPixelFormat 83 | textureDescriptor.usage = usage 84 | textureDescriptor.width = width 85 | textureDescriptor.height = height 86 | textureDescriptor.resourceOptions = resourceOptions 87 | 88 | // MARK: - Calculate bytes per row. 89 | 90 | /// Minimum texture alignment. 91 | /// 92 | /// The minimum alignment required when creating a texture buffer from a buffer. 93 | let textureBufferAlignment = max( 94 | device.minimumTextureBufferAlignment(for: mtlPixelFormat), 95 | device.minimumLinearTextureAlignment(for: mtlPixelFormat) 96 | ) 97 | 98 | var vImageBuffer = vImage_Buffer() 99 | 100 | /// Minimum vImage buffer alignment. 101 | /// 102 | /// Get the minimum data alignment required for buffer's contents, 103 | /// by passing `kvImageNoAllocate` to `vImage` constructor. 104 | let vImageBufferAlignment = vImageBuffer_Init( 105 | &vImageBuffer, 106 | vImagePixelCount(height), 107 | vImagePixelCount(width), 108 | UInt32(bitsPerComponent), 109 | vImage_Flags(kvImageNoAllocate) 110 | ) 111 | 112 | /// Pixel row alignment. 113 | /// 114 | /// Choose the maximum of previosly calculated alignments. 115 | let pixelRowAlignment = max(textureBufferAlignment, vImageBufferAlignment) 116 | 117 | /// Bytes per row. 118 | /// 119 | /// Calculate bytes per row by aligning row size with previously calculated `pixelRowAlignment`. 120 | let bytesPerRow = alignUp( 121 | size: bytesPerPixel * width, 122 | align: pixelRowAlignment 123 | ) 124 | 125 | // MARK: - Page align allocation pointer. 126 | 127 | let alloacationSize = max( 128 | bytesPerRow * height, 129 | device.heapTextureSizeAndAlign(descriptor: textureDescriptor).size 130 | ) 131 | 132 | /// Current system's RAM page size. 133 | let pageSize = Int(getpagesize()) 134 | 135 | /// Page aligned texture size. 136 | /// 137 | /// Get page aligned texture size. 138 | /// It might be more than raw texture size, but we'll alloccate memory in reserve. 139 | let pageAlignedTextureSize = alignUp( 140 | size: alloacationSize, 141 | align: pageSize 142 | ) 143 | 144 | var allocationAddress: UInt = 0 145 | let allocationSize = vm_size_t(pageAlignedTextureSize) 146 | let status = vm_allocate( 147 | mach_task_self_, 148 | &allocationAddress, 149 | allocationSize, 150 | VM_FLAGS_ANYWHERE 151 | ) 152 | 153 | guard status == KERN_SUCCESS, 154 | let allocationPointer = UnsafeMutableRawPointer(bitPattern: allocationAddress) 155 | else { throw Error.allocationFailed } 156 | 157 | vImageBuffer.rowBytes = bytesPerRow 158 | vImageBuffer.data = allocationPointer 159 | 160 | guard let buffer = device.makeBuffer( 161 | bytesNoCopy: allocationPointer, 162 | length: pageAlignedTextureSize, 163 | options: resourceOptions, 164 | deallocator: { _, _ in } 165 | ) 166 | else { throw MetalError.MTLDeviceError.bufferCreationFailed } 167 | 168 | guard let texture = buffer.makeTexture( 169 | descriptor: textureDescriptor, 170 | offset: 0, 171 | bytesPerRow: bytesPerRow 172 | ) 173 | else { throw MetalError.MTLBufferError.textureCreationFailed } 174 | 175 | let pixelBuffer = try CVPixelBuffer.create( 176 | width: width, 177 | height: height, 178 | cvPixelFormat: cvPixelFormat, 179 | baseAddress: allocationPointer, 180 | bytesPerRow: bytesPerRow, 181 | pixelBufferAttributes: [ 182 | .cGImageCompatibility: true, 183 | .cGBitmapContextCompatibility: true, 184 | .metalCompatibility: true 185 | ] 186 | ) 187 | 188 | guard let cgContext = CGContext( 189 | data: allocationPointer, 190 | width: width, 191 | height: height, 192 | bitsPerComponent: bitsPerComponent, 193 | bytesPerRow: bytesPerRow, 194 | space: colorSpace, 195 | bitmapInfo: bitmapInfo 196 | ) 197 | else { throw Error.cgContextCreationFailed } 198 | 199 | self.allocationPointer = allocationPointer 200 | self.allocationAddress = allocationAddress 201 | self.allocationSize = allocationSize 202 | self.bytesPerRow = bytesPerRow 203 | self.buffer = buffer 204 | self.texture = texture 205 | self.vImageBuffer = vImageBuffer 206 | self.pixelBuffer = pixelBuffer 207 | self.cgContext = cgContext 208 | self.colorSpace = colorSpace 209 | } 210 | 211 | deinit { 212 | vm_deallocate(mach_task_self_, self.allocationAddress, self.allocationSize) 213 | } 214 | } 215 | 216 | #endif 217 | --------------------------------------------------------------------------------