├── .gitattributes ├── .gitignore ├── Demos ├── CGLFW │ ├── module.modulemap │ └── shim.h ├── DemoBoids │ ├── main.swift │ └── shaders.swift ├── DemoClearColor │ ├── color.swift │ └── main.swift ├── DemoCube │ ├── geometry.swift │ └── main.swift ├── DemoInfo │ ├── formatting.swift │ └── main.swift ├── DemoTriangle │ └── main.swift └── WindowUtils │ └── WindowUtils.swift ├── LICENSE ├── Package.swift ├── Plugins └── GenerateWebGPUPlugin │ └── plugin.swift ├── README.md └── Sources ├── CDawnNative ├── dawn_native.cpp └── include │ └── dawn_native.h ├── CDawnProc ├── module.modulemap └── shim.h ├── CWebGPU ├── module.modulemap └── shim.h ├── DawnNative └── DawnNative.swift ├── WebGPU ├── Array+Helpers.swift ├── Bool+Convertible.swift ├── Convertible.swift ├── Extensible.swift ├── Extensions.swift ├── Optional+Helpers.swift ├── RequestError.swift ├── String+Convertible.swift └── UserData.swift └── generate-webgpu ├── Decoding ├── DawnData.swift ├── DefaultFallback.swift ├── HasDefaultValue.swift ├── String+CodingKey.swift └── Taggable.swift ├── Generate ├── CodeBuilder.swift ├── Conversion.swift ├── GenerateCallbackInfo.swift ├── GenerateCallbacks.swift ├── GenerateClasses.swift ├── GenerateEnums.swift ├── GenerateFunction.swift ├── GenerateFunctionTypes.swift ├── GenerateFunctions.swift ├── GenerateOptionSets.swift └── GenerateStructs.swift ├── GenerateWebGPU.swift └── Model ├── BitmaskType.swift ├── CallbackFunctionType.swift ├── CallbackInfoType.swift ├── EnumType.swift ├── FunctionPointerType.swift ├── FunctionType.swift ├── Model.swift ├── NativeType.swift ├── ObjectType.swift ├── Record.swift ├── StringUtils.swift ├── StructureType.swift └── Type.swift /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | .swiftpm/ 7 | Package.resolved 8 | 9 | # Byte-compiled / optimized / DLL files 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # C extensions 15 | *.so 16 | 17 | # Distribution / packaging 18 | .Python 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | wheels/ 31 | share/python-wheels/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | MANIFEST 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .nox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *.cover 57 | *.py,cover 58 | .hypothesis/ 59 | .pytest_cache/ 60 | cover/ 61 | 62 | # Translations 63 | *.mo 64 | *.pot 65 | 66 | # Django stuff: 67 | *.log 68 | local_settings.py 69 | db.sqlite3 70 | db.sqlite3-journal 71 | 72 | # Flask stuff: 73 | instance/ 74 | .webassets-cache 75 | 76 | # Scrapy stuff: 77 | .scrapy 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # PyBuilder 83 | .pybuilder/ 84 | target/ 85 | 86 | # Jupyter Notebook 87 | .ipynb_checkpoints 88 | 89 | # IPython 90 | profile_default/ 91 | ipython_config.py 92 | 93 | # pyenv 94 | # For a library or package, you might want to ignore these files since the code is 95 | # intended to run in multiple environments; otherwise, check them in: 96 | # .python-version 97 | 98 | # pipenv 99 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 100 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 101 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 102 | # install all needed dependencies. 103 | #Pipfile.lock 104 | 105 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 106 | __pypackages__/ 107 | 108 | # Celery stuff 109 | celerybeat-schedule 110 | celerybeat.pid 111 | 112 | # SageMath parsed files 113 | *.sage.py 114 | 115 | # Environments 116 | .env 117 | .venv 118 | env/ 119 | venv/ 120 | ENV/ 121 | env.bak/ 122 | venv.bak/ 123 | 124 | # Spyder project settings 125 | .spyderproject 126 | .spyproject 127 | 128 | # Rope project settings 129 | .ropeproject 130 | 131 | # mkdocs documentation 132 | /site 133 | 134 | # mypy 135 | .mypy_cache/ 136 | .dmypy.json 137 | dmypy.json 138 | 139 | # Pyre type checker 140 | .pyre/ 141 | 142 | # pytype static type analyzer 143 | .pytype/ 144 | 145 | # Cython debug symbols 146 | cython_debug/ 147 | 148 | # PyCharm 149 | .idea/ 150 | 151 | # Generated sources 152 | Sources/WebGpu/Generated -------------------------------------------------------------------------------- /Demos/CGLFW/module.modulemap: -------------------------------------------------------------------------------- 1 | module CGLFW [system] { 2 | header "shim.h" 3 | link "glfw3" 4 | } 5 | -------------------------------------------------------------------------------- /Demos/CGLFW/shim.h: -------------------------------------------------------------------------------- 1 | #define GLFW_INCLUDE_NONE 2 | #include 3 | 4 | #ifdef __APPLE__ 5 | #define GLFW_EXPOSE_NATIVE_COCOA 6 | #elif __linux__ 7 | #define GLFW_EXPOSE_NATIVE_X11 8 | #elif _WIN32 9 | #define GLFW_EXPOSE_NATIVE_WIN32 10 | #endif 11 | 12 | #include 13 | -------------------------------------------------------------------------------- /Demos/DemoBoids/main.swift: -------------------------------------------------------------------------------- 1 | import WebGPU 2 | import WindowUtils 3 | 4 | typealias Vector2 = (Float, Float) 5 | 6 | struct Particle { 7 | var position: Vector2 8 | var velocity: Vector2 9 | } 10 | 11 | 12 | let numParticles = 1500 13 | 14 | let simParams = ( 15 | deltaT: Float(0.025), 16 | rule1Distance: Float(0.1), 17 | rule2Distance: Float(0.025), 18 | rule3Distance: Float(0.025), 19 | rule1Scale: Float(0.02), 20 | rule2Scale: Float(0.05), 21 | rule3Scale: Float(0.005) 22 | ) 23 | 24 | let vertexData = [ 25 | Vector2(-0.01, -0.02), 26 | Vector2(0.01, -0.02), 27 | Vector2(0.0, 0.02) 28 | ] 29 | 30 | 31 | let instance = createInstance() 32 | 33 | let adapter = try await instance.requestAdapter() 34 | print("Using adapter: \(adapter.info.device)") 35 | 36 | let uncapturedErrorCallback: UncapturedErrorCallback = { device, errorType, errorMessage in 37 | print("Error (\(errorType)): \(errorMessage)") 38 | } 39 | 40 | let device = try await adapter.requestDevice(options: .init( 41 | uncapturedErrorCallback: uncapturedErrorCallback 42 | )) 43 | 44 | try withGLFW { 45 | let window = Window(width: 800, height: 600, title: "DemoBoids") 46 | let surface = instance.createSurface(descriptor: window.surfaceDescriptor) 47 | surface.configure(config: .init(device: device, format: window.preferredTextureFormat, width: 800, height: 600)) 48 | 49 | let renderShader = device.createShaderModule( 50 | descriptor: ShaderModuleDescriptor( 51 | label: nil, 52 | nextInChain: ShaderSourceWgsl(code: renderShaderSource))) 53 | 54 | let renderPipeline = device.createRenderPipeline( 55 | descriptor: RenderPipelineDescriptor( 56 | vertex: VertexState( 57 | module: renderShader, 58 | entryPoint: "vert_main", 59 | buffers: [ 60 | VertexBufferLayout( 61 | stepMode: .instance, 62 | arrayStride: UInt64(MemoryLayout.stride), 63 | attributes: [ 64 | VertexAttribute( 65 | format: .float32x2, 66 | offset: UInt64(MemoryLayout.offset(of: \Particle.position)!), 67 | shaderLocation: 0), 68 | VertexAttribute( 69 | format: .float32x2, 70 | offset: UInt64(MemoryLayout.offset(of: \Particle.velocity)!), 71 | shaderLocation: 1)]), 72 | VertexBufferLayout( 73 | stepMode: .vertex, 74 | arrayStride: UInt64(MemoryLayout.stride), 75 | attributes: [ 76 | VertexAttribute( 77 | format: .float32x2, 78 | offset: 0, 79 | shaderLocation: 2)])]), 80 | fragment: FragmentState( 81 | module: renderShader, 82 | entryPoint: "frag_main", 83 | targets: [ColorTargetState(format: window.preferredTextureFormat)]))) 84 | 85 | 86 | let computeShader = device.createShaderModule( 87 | descriptor: ShaderModuleDescriptor( 88 | label: nil, 89 | nextInChain: ShaderSourceWgsl(code: computeShaderSource))) 90 | 91 | let computePipeline = device.createComputePipeline( 92 | descriptor: ComputePipelineDescriptor( 93 | compute: ComputeState( 94 | module: computeShader, 95 | entryPoint: "main"))) 96 | 97 | 98 | let vertexBuffer = vertexData.withUnsafeBytes { bytes -> Buffer in 99 | let buffer = device.createBuffer(descriptor: BufferDescriptor( 100 | usage: .vertex, 101 | size: UInt64(bytes.count), 102 | mappedAtCreation: true)) 103 | buffer.getMappedRange().copyMemory(from: bytes.baseAddress!, byteCount: bytes.count) 104 | buffer.unmap() 105 | return buffer 106 | } 107 | 108 | let simParamBuffer = withUnsafeBytes(of: simParams) { bytes in 109 | let buffer = device.createBuffer(descriptor: BufferDescriptor( 110 | usage: .uniform, 111 | size: UInt64(bytes.count), 112 | mappedAtCreation: true)) 113 | buffer.getMappedRange().copyMemory(from: bytes.baseAddress!, byteCount: bytes.count) 114 | buffer.unmap() 115 | return buffer 116 | } 117 | 118 | var particles = [Particle]() 119 | particles.reserveCapacity(numParticles) 120 | for _ in 0.., 4 | @location(1) a_particleVel : vec2, 5 | @location(2) a_pos : vec2 6 | ) -> @builtin(position) vec4 { 7 | let angle = -atan2(a_particleVel.x, a_particleVel.y); 8 | let pos = vec2( 9 | (a_pos.x * cos(angle)) - (a_pos.y * sin(angle)), 10 | (a_pos.x * sin(angle)) + (a_pos.y * cos(angle)) 11 | ); 12 | return vec4(pos + a_particlePos, 0.0, 1.0); 13 | } 14 | 15 | @fragment fn frag_main() -> @location(0) vec4 { 16 | return vec4(1.0, 1.0, 1.0, 1.0); 17 | } 18 | """ 19 | 20 | let computeShaderSource = """ 21 | struct Particle { 22 | pos : vec2, 23 | vel : vec2, 24 | } 25 | 26 | struct SimParams { 27 | deltaT : f32, 28 | rule1Distance : f32, 29 | rule2Distance : f32, 30 | rule3Distance : f32, 31 | rule1Scale : f32, 32 | rule2Scale : f32, 33 | rule3Scale : f32, 34 | } 35 | 36 | struct Particles { 37 | particles : array, 38 | } 39 | 40 | @binding(0) @group(0) var params : SimParams; 41 | @binding(1) @group(0) var particlesA : Particles; 42 | @binding(2) @group(0) var particlesB : Particles; 43 | 44 | // https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp 45 | @compute @workgroup_size(64) 46 | fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3) { 47 | var index = GlobalInvocationID.x; 48 | 49 | var vPos = particlesA.particles[index].pos; 50 | var vVel = particlesA.particles[index].vel; 51 | var cMass = vec2(0.0); 52 | var cVel = vec2(0.0); 53 | var colVel = vec2(0.0); 54 | var cMassCount = 0u; 55 | var cVelCount = 0u; 56 | var pos : vec2; 57 | var vel : vec2; 58 | 59 | for (var i = 0u; i < arrayLength(&particlesA.particles); i++) { 60 | if (i == index) { 61 | continue; 62 | } 63 | 64 | pos = particlesA.particles[i].pos.xy; 65 | vel = particlesA.particles[i].vel.xy; 66 | if (distance(pos, vPos) < params.rule1Distance) { 67 | cMass += pos; 68 | cMassCount++; 69 | } 70 | if (distance(pos, vPos) < params.rule2Distance) { 71 | colVel -= pos - vPos; 72 | } 73 | if (distance(pos, vPos) < params.rule3Distance) { 74 | cVel += vel; 75 | cVelCount++; 76 | } 77 | } 78 | if (cMassCount > 0) { 79 | cMass = (cMass / vec2(f32(cMassCount))) - vPos; 80 | } 81 | if (cVelCount > 0) { 82 | cVel /= f32(cVelCount); 83 | } 84 | vVel += (cMass * params.rule1Scale) + (colVel * params.rule2Scale) + (cVel * params.rule3Scale); 85 | 86 | // clamp velocity for a more pleasing simulation 87 | vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1); 88 | // kinematic update 89 | vPos = vPos + (vVel * params.deltaT); 90 | // Wrap around boundary 91 | if (vPos.x < -1.0) { 92 | vPos.x = 1.0; 93 | } 94 | if (vPos.x > 1.0) { 95 | vPos.x = -1.0; 96 | } 97 | if (vPos.y < -1.0) { 98 | vPos.y = 1.0; 99 | } 100 | if (vPos.y > 1.0) { 101 | vPos.y = -1.0; 102 | } 103 | // Write back 104 | particlesB.particles[index].pos = vPos; 105 | particlesB.particles[index].vel = vVel; 106 | } 107 | """ 108 | -------------------------------------------------------------------------------- /Demos/DemoClearColor/color.swift: -------------------------------------------------------------------------------- 1 | import WebGPU 2 | 3 | extension Color { 4 | init(h: Double, s: Double, v: Double, a: Double) { 5 | if s == 0 { // Achromatic grey 6 | self.init(r: v, g: v, b: v, a: a) 7 | return 8 | } 9 | 10 | let angle = (h >= 360 ? 0 : h) 11 | let sector = angle / 60 // Sector 12 | let i = sector.rounded(.down) 13 | let f = sector - i // Factorial part of h 14 | 15 | let p = v * (1 - s) 16 | let q = v * (1 - (s * f)) 17 | let t = v * (1 - (s * (1 - f))) 18 | 19 | switch(i) { 20 | case 0: 21 | self.init(r: v, g: t, b: p, a: a) 22 | case 1: 23 | self.init(r: q, g: v, b: p, a: a) 24 | case 2: 25 | self.init(r: p, g: v, b: t, a: a) 26 | case 3: 27 | self.init(r: p, g: q, b: v, a: a) 28 | case 4: 29 | self.init(r: t, g: p, b: v, a: a) 30 | default: 31 | self.init(r: v, g: p, b: q, a: a) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Demos/DemoClearColor/main.swift: -------------------------------------------------------------------------------- 1 | import WebGPU 2 | import WindowUtils 3 | 4 | let instance = createInstance() 5 | 6 | let adapter = try await instance.requestAdapter() 7 | print("Using adapter: \(adapter.info.device)") 8 | 9 | let uncapturedErrorCallback: UncapturedErrorCallback = { device, errorType, errorMessage in 10 | print("Error (\(errorType)): \(errorMessage)") 11 | } 12 | 13 | let device = try await adapter.requestDevice(options: .init( 14 | uncapturedErrorCallback: uncapturedErrorCallback 15 | )) 16 | 17 | try withGLFW { 18 | let window = Window(width: 800, height: 600, title: "DemoClearColor") 19 | let surface = instance.createSurface(descriptor: window.surfaceDescriptor) 20 | surface.configure(config: .init(device: device, format: window.preferredTextureFormat, width: 800, height: 600)) 21 | 22 | var hue = 0.0 23 | 24 | try window.loop { 25 | let encoder = device.createCommandEncoder() 26 | 27 | let renderPass = encoder.beginRenderPass(descriptor: RenderPassDescriptor( 28 | colorAttachments: [ 29 | RenderPassColorAttachment( 30 | view: try surface.getCurrentTexture().texture.createView(), 31 | loadOp: .clear, 32 | storeOp: .store, 33 | clearValue: Color(h: hue, s: 0.9, v: 0.9, a: 1.0))])) 34 | renderPass.end() 35 | 36 | let commandBuffer = encoder.finish() 37 | device.queue.submit(commands: [commandBuffer]) 38 | 39 | surface.present() 40 | 41 | hue = (hue + 0.5).truncatingRemainder(dividingBy: 360) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Demos/DemoCube/geometry.swift: -------------------------------------------------------------------------------- 1 | import SwiftMath 2 | 3 | struct Vertex { 4 | var position: vec3 5 | var color: vec3 6 | } 7 | 8 | let cubeVertices = [ 9 | Vertex(position: vec3(-1.0, -1.0, 1.0), color: vec3(1.0, 0.0, 0.0)), 10 | Vertex(position: vec3(1.0, -1.0, 1.0), color: vec3(1.0, 0.0, 0.0)), 11 | Vertex(position: vec3(1.0, 1.0, 1.0), color: vec3(1.0, 0.0, 0.0)), 12 | Vertex(position: vec3(-1.0, 1.0, 1.0), color: vec3(1.0, 0.0, 0.0)), 13 | 14 | Vertex(position: vec3(-1.0, -1.0, -1.0), color: vec3(1.0, 1.0, 0.0)), 15 | Vertex(position: vec3(-1.0, 1.0, -1.0), color: vec3(1.0, 1.0, 0.0)), 16 | Vertex(position: vec3(1.0, 1.0, -1.0), color: vec3(1.0, 1.0, 0.0)), 17 | Vertex(position: vec3(1.0, -1.0, -1.0), color: vec3(1.0, 1.0, 0.0)), 18 | 19 | Vertex(position: vec3(-1.0, 1.0, -1.0), color: vec3(0.0, 1.0, 0.0)), 20 | Vertex(position: vec3(-1.0, 1.0, 1.0), color: vec3(0.0, 1.0, 0.0)), 21 | Vertex(position: vec3(1.0, 1.0, 1.0), color: vec3(0.0, 1.0, 0.0)), 22 | Vertex(position: vec3(1.0, 1.0, -1.0), color: vec3(0.0, 1.0, 0.0)), 23 | 24 | Vertex(position: vec3(-1.0, -1.0, -1.0), color: vec3(0.0, 1.0, 1.0)), 25 | Vertex(position: vec3(1.0, -1.0, -1.0), color: vec3(0.0, 1.0, 1.0)), 26 | Vertex(position: vec3(1.0, -1.0, 1.0), color: vec3(0.0, 1.0, 1.0)), 27 | Vertex(position: vec3(-1.0, -1.0, 1.0), color: vec3(0.0, 1.0, 1.0)), 28 | 29 | Vertex(position: vec3(1.0, -1.0, -1.0), color: vec3(0.0, 0.0, 1.0)), 30 | Vertex(position: vec3(1.0, 1.0, -1.0), color: vec3(0.0, 0.0, 1.0)), 31 | Vertex(position: vec3(1.0, 1.0, 1.0), color: vec3(0.0, 0.0, 1.0)), 32 | Vertex(position: vec3(1.0, -1.0, 1.0), color: vec3(0.0, 0.0, 1.0)), 33 | 34 | Vertex(position: vec3(-1.0, -1.0, -1.0), color: vec3(1.0, 1.0, 1.0)), 35 | Vertex(position: vec3(-1.0, -1.0, 1.0), color: vec3(1.0, 1.0, 1.0)), 36 | Vertex(position: vec3(-1.0, 1.0, 1.0), color: vec3(1.0, 1.0, 1.0)), 37 | Vertex(position: vec3(-1.0, 1.0, -1.0), color: vec3(1.0, 1.0, 1.0)) 38 | ] 39 | 40 | let cubeIndices: [UInt32] = [ 41 | 0, 1, 2, 0, 2, 3, 42 | 4, 5, 6, 4, 6, 7, 43 | 8, 9, 10, 8, 10, 11, 44 | 12, 13, 14, 12, 14, 15, 45 | 16, 17, 18, 16, 18, 19, 46 | 20, 21, 22, 20, 22, 23 47 | ] 48 | -------------------------------------------------------------------------------- /Demos/DemoCube/main.swift: -------------------------------------------------------------------------------- 1 | import WebGPU 2 | import WindowUtils 3 | import SwiftMath 4 | 5 | struct Camera { 6 | var view: Matrix4x4f 7 | var projection: Matrix4x4f 8 | } 9 | 10 | let instance = createInstance() 11 | 12 | let adapter = try await instance.requestAdapter() 13 | print("Using adapter: \(adapter.info.device)") 14 | 15 | let uncapturedErrorCallback: UncapturedErrorCallback = { device, errorType, errorMessage in 16 | print("Error (\(errorType)): \(errorMessage)") 17 | } 18 | 19 | let device = try await adapter.requestDevice(options: .init( 20 | uncapturedErrorCallback: uncapturedErrorCallback 21 | )) 22 | 23 | try withGLFW { 24 | let window = Window(width: 800, height: 600, title: "DemoCube") 25 | let surface = instance.createSurface(descriptor: window.surfaceDescriptor) 26 | surface.configure(config: .init(device: device, format: window.preferredTextureFormat, width: 800, height: 600)) 27 | 28 | let vertexShaderSource = """ 29 | struct Camera { 30 | view : mat4x4, 31 | projection: mat4x4 32 | }; 33 | @group(0) @binding(0) var camera : Camera; 34 | 35 | struct VertexOut { 36 | @builtin(position) position : vec4, 37 | @location(0) color : vec4 38 | }; 39 | 40 | @vertex fn main( 41 | @location(0) position : vec4, 42 | @location(1) color : vec4) -> VertexOut { 43 | var output : VertexOut; 44 | output.position = camera.projection * camera.view * position; 45 | output.color = color; 46 | return output; 47 | } 48 | """ 49 | 50 | let fragmentShaderSource = """ 51 | @fragment fn main( 52 | @location(0) color : vec4) -> @location(0) vec4 { 53 | return color; 54 | } 55 | """ 56 | 57 | let vertexShader = device.createShaderModule( 58 | descriptor: ShaderModuleDescriptor( 59 | label: nil, 60 | nextInChain: ShaderSourceWgsl(code: vertexShaderSource))) 61 | 62 | let fragmentShader = device.createShaderModule( 63 | descriptor: ShaderModuleDescriptor( 64 | label: nil, 65 | nextInChain: ShaderSourceWgsl(code: fragmentShaderSource))) 66 | 67 | let bindGroupLayout = device.createBindGroupLayout(descriptor: BindGroupLayoutDescriptor( 68 | entries: [ 69 | BindGroupLayoutEntry( 70 | binding: 0, 71 | visibility: .vertex, 72 | buffer: BufferBindingLayout(type: .uniform))])) 73 | 74 | let pipelineLayout = device.createPipelineLayout(descriptor: PipelineLayoutDescriptor( 75 | bindGroupLayouts: [bindGroupLayout])) 76 | 77 | let pipeline = device.createRenderPipeline(descriptor: RenderPipelineDescriptor( 78 | layout: pipelineLayout, 79 | vertex: VertexState( 80 | module: vertexShader, 81 | entryPoint: "main", 82 | buffers: [ 83 | VertexBufferLayout( 84 | stepMode: .vertex, 85 | arrayStride: UInt64(MemoryLayout.stride), 86 | attributes: [ 87 | VertexAttribute( 88 | format: .float32x3, 89 | offset: UInt64(MemoryLayout.offset(of: \Vertex.position)!), 90 | shaderLocation: 0), 91 | VertexAttribute( 92 | format: .float32x3, 93 | offset: UInt64(MemoryLayout.offset(of: \Vertex.color)!), 94 | shaderLocation: 1)])]), 95 | depthStencil: DepthStencilState( 96 | format: .depth24Plus, 97 | depthWriteEnabled: true, 98 | depthCompare: .less), 99 | fragment: FragmentState( 100 | module: fragmentShader, 101 | entryPoint: "main", 102 | targets: [ 103 | ColorTargetState( 104 | format: window.preferredTextureFormat)]))) 105 | 106 | let vertexBuffer = cubeVertices.withUnsafeBytes { vertexBytes -> Buffer in 107 | let vertexBuffer = device.createBuffer(descriptor: BufferDescriptor( 108 | usage: .vertex, 109 | size: UInt64(vertexBytes.count), 110 | mappedAtCreation: true)) 111 | let ptr = vertexBuffer.getMappedRange(offset: 0, size: 0) 112 | ptr?.copyMemory(from: vertexBytes.baseAddress!, byteCount: vertexBytes.count) 113 | vertexBuffer.unmap() 114 | return vertexBuffer 115 | } 116 | 117 | let indexBuffer = cubeIndices.withUnsafeBytes { indexBytes -> Buffer in 118 | let indexBuffer = device.createBuffer(descriptor: BufferDescriptor( 119 | usage: .index, 120 | size: UInt64(indexBytes.count), 121 | mappedAtCreation: true)) 122 | let ptr = indexBuffer.getMappedRange(offset: 0, size: 0) 123 | ptr?.copyMemory(from: indexBytes.baseAddress!, byteCount: indexBytes.count) 124 | indexBuffer.unmap() 125 | return indexBuffer 126 | } 127 | 128 | var camera = Camera( 129 | view: Matrix4x4f(), 130 | projection: Matrix4x4f.proj(fovy: Angle(degrees: 45), aspect: 800/600, near: 1, far: 100)) 131 | 132 | let cameraBuffer = device.createBuffer(descriptor: BufferDescriptor( 133 | usage: [.uniform, .copyDst], 134 | size: UInt64(MemoryLayout.size))) 135 | 136 | let bindGroup = device.createBindGroup(descriptor: BindGroupDescriptor( 137 | layout: bindGroupLayout, 138 | entries: [ 139 | BindGroupEntry(binding: 0, 140 | buffer: cameraBuffer, 141 | size: UInt64(MemoryLayout.size))])) 142 | 143 | let depthStencilView = device.createTexture(descriptor: TextureDescriptor( 144 | usage: .renderAttachment, 145 | size: Extent3d(width: 800, height: 600), 146 | format: .depth24Plus) 147 | ).createView() 148 | 149 | var rotation = Angle(degrees: 0) 150 | 151 | try window.loop { 152 | rotation = (rotation + Angle(degrees: 0.5)) % Angle(degrees: 360) 153 | camera.view = Matrix4x4f.lookAt( 154 | eye: vec3(6 * sin(rotation), 2, 6 * cos(rotation)), 155 | at: vec3(0, 0, 0)) 156 | 157 | withUnsafeBytes(of: &camera) { cameraBytes in 158 | device.queue.writeBuffer(cameraBuffer, bufferOffset: 0, data: cameraBytes) 159 | } 160 | 161 | let encoder = device.createCommandEncoder() 162 | 163 | let renderPass = encoder.beginRenderPass(descriptor: RenderPassDescriptor( 164 | colorAttachments: [ 165 | RenderPassColorAttachment( 166 | view: try surface.getCurrentTexture().texture.createView(), 167 | loadOp: .clear, 168 | storeOp: .store, 169 | clearValue: Color(r: 0, g: 0, b: 0, a: 1))], 170 | depthStencilAttachment: RenderPassDepthStencilAttachment( 171 | view: depthStencilView, 172 | depthLoadOp: .clear, 173 | depthStoreOp: .store, 174 | depthClearValue: 1))) 175 | renderPass.setPipeline(pipeline) 176 | renderPass.setBindGroup(groupIndex: 0, group: bindGroup) 177 | renderPass.setVertexBuffer(slot: 0, buffer: vertexBuffer) 178 | renderPass.setIndexBuffer(indexBuffer, format: .uint32) 179 | renderPass.drawIndexed(indexCount: UInt32(cubeIndices.count)) 180 | renderPass.end() 181 | 182 | let commandBuffer = encoder.finish() 183 | device.queue.submit(commands: [commandBuffer]) 184 | 185 | surface.present() 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Demos/DemoInfo/formatting.swift: -------------------------------------------------------------------------------- 1 | var indent = 0 2 | 3 | func print(_ line: String) { 4 | Swift.print(String(repeating: " ", count: indent) + line) 5 | } 6 | 7 | func withIndent(_ body: () -> ()) { 8 | indent += 2 9 | body() 10 | indent -= 2 11 | } 12 | 13 | func print(title: String) { 14 | print(title) 15 | print(String(repeating: "=", count: title.count)) 16 | } 17 | 18 | func print(subtitle: String) { 19 | print(subtitle) 20 | print(String(repeating: "-", count: subtitle.count)) 21 | } 22 | 23 | func print(key: String, value: Any) { 24 | print("\(key): \(value)") 25 | } 26 | 27 | func hex(_ value: T) -> String { 28 | return "0x" + String(value, radix: 16, uppercase: false) 29 | } 30 | -------------------------------------------------------------------------------- /Demos/DemoInfo/main.swift: -------------------------------------------------------------------------------- 1 | import DawnNative 2 | 3 | let instance = Instance() 4 | let adapters = instance.adapters 5 | 6 | print("===========") 7 | print("WebGPU Info") 8 | print("===========") 9 | print() 10 | 11 | print(title: "Adapters (\(adapters.count))") 12 | withIndent { 13 | for (i, adapter) in adapters.enumerated() { 14 | let info = adapter.webGpuAdapter.info 15 | 16 | print(subtitle: "[\(i)] \(info.device)") 17 | withIndent { 18 | print(info.description) 19 | print(key: "vendor", value: info.vendor) 20 | print(key: "vendorId", value: hex(info.vendorId)) 21 | print(key: "deviceId", value: hex(info.deviceId)) 22 | print(key: "adapterType", value: info.adapterType) 23 | print(key: "backendType", value: info.backendType) 24 | print(key: "architecture", value: info.architecture) 25 | } 26 | print() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Demos/DemoTriangle/main.swift: -------------------------------------------------------------------------------- 1 | import WebGPU 2 | import WindowUtils 3 | 4 | struct Vertex { 5 | var position: (Float, Float, Float) 6 | var color: (Float, Float, Float) 7 | } 8 | 9 | let instance = createInstance() 10 | 11 | let adapter = try await instance.requestAdapter() 12 | print("Using adapter: \(adapter.info.device)") 13 | 14 | let uncapturedErrorCallback: UncapturedErrorCallback = { device, errorType, errorMessage in 15 | print("Error (\(errorType)): \(errorMessage)") 16 | } 17 | 18 | let device = try await adapter.requestDevice(options: .init( 19 | uncapturedErrorCallback: uncapturedErrorCallback 20 | )) 21 | 22 | try withGLFW { 23 | let window = Window(width: 800, height: 600, title: "DemoTriangle") 24 | let surface = instance.createSurface(descriptor: window.surfaceDescriptor) 25 | surface.configure(config: .init(device: device, format: window.preferredTextureFormat, width: 800, height: 600)) 26 | 27 | let vertexShaderSource = """ 28 | struct VertexOut { 29 | @builtin(position) position : vec4, 30 | @location(0) color: vec4 31 | }; 32 | 33 | @vertex fn main( 34 | @location(0) position : vec4, 35 | @location(1) color : vec4) -> VertexOut { 36 | var output : VertexOut; 37 | output.position = position; 38 | output.color = color; 39 | return output; 40 | } 41 | """ 42 | 43 | let fragmentShaderSource = """ 44 | @fragment fn main( 45 | @location(0) color : vec4) -> @location(0) vec4 { 46 | return color; 47 | } 48 | """ 49 | 50 | let vertexShader = device.createShaderModule( 51 | descriptor: ShaderModuleDescriptor( 52 | label: nil, 53 | nextInChain: ShaderSourceWgsl(code: vertexShaderSource))) 54 | 55 | let fragmentShader = device.createShaderModule( 56 | descriptor: ShaderModuleDescriptor( 57 | label: nil, 58 | nextInChain: ShaderSourceWgsl(code: fragmentShaderSource))) 59 | 60 | let pipeline = device.createRenderPipeline(descriptor: RenderPipelineDescriptor( 61 | vertex: VertexState( 62 | module: vertexShader, 63 | entryPoint: "main", 64 | buffers: [ 65 | VertexBufferLayout( 66 | stepMode: .vertex, 67 | arrayStride: UInt64(MemoryLayout.stride), 68 | attributes: [ 69 | VertexAttribute( 70 | format: .float32x3, 71 | offset: UInt64(MemoryLayout.offset(of: \Vertex.position)!), 72 | shaderLocation: 0), 73 | VertexAttribute( 74 | format: .float32x3, 75 | offset: UInt64(MemoryLayout.offset(of: \Vertex.color)!), 76 | shaderLocation: 1)])]), 77 | fragment: FragmentState( 78 | module: fragmentShader, 79 | entryPoint: "main", 80 | targets: [ 81 | ColorTargetState(format: window.preferredTextureFormat)]))) 82 | 83 | let vertexData = [ 84 | Vertex(position: (0, 0.5, 0), color: (1, 0, 0)), 85 | Vertex(position: (-0.5, -0.5, 0), color: (0, 1, 0)), 86 | Vertex(position: (0.5, -0.5, 0), color: (0, 0, 1)) 87 | ] 88 | 89 | let vertexBuffer = vertexData.withUnsafeBytes { vertexBytes -> Buffer in 90 | let vertexBuffer = device.createBuffer(descriptor: BufferDescriptor( 91 | usage: .vertex, 92 | size: UInt64(vertexBytes.count), 93 | mappedAtCreation: true)) 94 | let ptr = vertexBuffer.getMappedRange(offset: 0, size: 0) 95 | ptr?.copyMemory(from: vertexBytes.baseAddress!, byteCount: vertexBytes.count) 96 | vertexBuffer.unmap() 97 | return vertexBuffer 98 | } 99 | 100 | try window.loop { 101 | let encoder = device.createCommandEncoder() 102 | 103 | let renderPass = encoder.beginRenderPass(descriptor: RenderPassDescriptor( 104 | colorAttachments: [ 105 | RenderPassColorAttachment( 106 | view: try surface.getCurrentTexture().texture.createView(), 107 | loadOp: .clear, 108 | storeOp: .store, 109 | clearValue: Color(r: 0, g: 0, b: 0, a: 1))])) 110 | renderPass.setPipeline(pipeline) 111 | renderPass.setVertexBuffer(slot: 0, buffer: vertexBuffer) 112 | renderPass.draw(vertexCount: 3) 113 | renderPass.end() 114 | 115 | let commandBuffer = encoder.finish() 116 | device.queue.submit(commands: [commandBuffer]) 117 | 118 | surface.present() 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Demos/WindowUtils/WindowUtils.swift: -------------------------------------------------------------------------------- 1 | import CGLFW 2 | import WebGPU 3 | 4 | #if os(macOS) 5 | import AppKit 6 | #elseif os(Windows) 7 | import WinSDK 8 | #endif 9 | 10 | @MainActor 11 | public func withGLFW(_ body: () throws -> R) rethrows -> R { 12 | glfwInit() 13 | defer { glfwTerminate() } 14 | return try body() 15 | } 16 | 17 | @MainActor 18 | public class Window { 19 | let handle: OpaquePointer! 20 | 21 | public init(width: Int, height: Int, title: String) { 22 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API) 23 | handle = glfwCreateWindow(Int32(width), Int32(height), title, nil, nil) 24 | } 25 | 26 | deinit { 27 | glfwDestroyWindow(handle) 28 | } 29 | 30 | public var surfaceDescriptor: SurfaceDescriptor { 31 | var surfaceDescriptor = SurfaceDescriptor() 32 | 33 | #if os(macOS) 34 | let nsWindow = glfwGetCocoaWindow(handle) as! NSWindow 35 | let view = nsWindow.contentView! 36 | 37 | if view.layer == nil { 38 | view.wantsLayer = true 39 | view.layer = CAMetalLayer() 40 | } 41 | 42 | surfaceDescriptor.nextInChain = SurfaceSourceMetalLayer( 43 | layer: Unmanaged.passUnretained(view.layer!).toOpaque() 44 | ) 45 | #elseif os(Linux) 46 | surfaceDescriptor.nextInChain = SurfaceSourceXlibWindow( 47 | display: UnsafeMutableRawPointer(glfwGetX11Display()), 48 | window: UInt64(glfwGetX11Window(handle)) 49 | ) 50 | #elseif os(Windows) 51 | surfaceDescriptor.nextInChain = SurfaceSourceWindowsHwnd( 52 | hinstance: GetModuleHandleW(nil), 53 | hwnd: glfwGetWin32Window(handle) 54 | ) 55 | #endif 56 | 57 | return surfaceDescriptor 58 | } 59 | 60 | public var preferredTextureFormat: TextureFormat { 61 | return .bgra8Unorm 62 | } 63 | 64 | public var shouldClose: Bool { 65 | return glfwWindowShouldClose(handle) == GLFW_TRUE 66 | } 67 | 68 | public func loop(body: () throws -> ()) rethrows { 69 | repeat { 70 | try _autoreleasepool { 71 | try body() 72 | pollEvents() 73 | } 74 | } while !shouldClose 75 | } 76 | } 77 | 78 | @MainActor 79 | public func pollEvents() { 80 | glfwPollEvents() 81 | } 82 | 83 | func _autoreleasepool(invoking body: () throws -> ()) rethrows { 84 | #if os(macOS) 85 | try autoreleasepool { 86 | try body() 87 | } 88 | #else 89 | try body() 90 | #endif 91 | } 92 | 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Henry Betts 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "swift-webgpu", 7 | platforms: [.macOS("10.15")], 8 | products: [ 9 | .library( 10 | name: "WebGPU", 11 | targets: ["WebGPU"]), 12 | .library( 13 | name: "DawnNative", 14 | targets: ["DawnNative", "WebGPU"]), 15 | ], 16 | dependencies: [ 17 | .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"), 18 | .package(url: "https://github.com/SwiftGFX/SwiftMath", from: "3.3.0") // for demos only 19 | ], 20 | targets: [ 21 | .systemLibrary( 22 | name: "CWebGPU", 23 | pkgConfig: "webgpu" 24 | ), 25 | .target( 26 | name: "WebGPU", 27 | dependencies: ["CWebGPU"], 28 | plugins: [.plugin(name: "GenerateWebGPUPlugin")] 29 | ), 30 | 31 | .target( 32 | name: "CDawnNative", 33 | dependencies: ["CWebGPU"] 34 | ), 35 | .target( 36 | name: "DawnNative", 37 | dependencies: ["WebGPU", "CDawnNative"] 38 | ), 39 | 40 | .executableTarget( 41 | name: "generate-webgpu", 42 | dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")] 43 | ), 44 | .plugin( 45 | name: "GenerateWebGPUPlugin", 46 | capability: .buildTool(), 47 | dependencies: ["generate-webgpu"] 48 | ), 49 | 50 | .systemLibrary( 51 | name: "CGLFW", 52 | path: "Demos/CGLFW", 53 | pkgConfig: "glfw3", 54 | providers: [ 55 | .brew(["glfw"])] 56 | ), 57 | .target( 58 | name: "WindowUtils", 59 | dependencies: ["WebGPU", "CGLFW"], 60 | path: "Demos/WindowUtils" 61 | ), 62 | 63 | .executableTarget( 64 | name: "DemoInfo", 65 | dependencies: ["DawnNative"], 66 | path: "Demos/DemoInfo" 67 | ), 68 | .executableTarget( 69 | name: "DemoClearColor", 70 | dependencies: ["WindowUtils"], 71 | path: "Demos/DemoClearColor", 72 | linkerSettings: [ 73 | // Surely Swift PM should be linking this automatically? 74 | .linkedLibrary("m", .when(platforms: [.linux])) 75 | ] 76 | ), 77 | .executableTarget( 78 | name: "DemoTriangle", 79 | dependencies: ["WindowUtils"], 80 | path: "Demos/DemoTriangle" 81 | ), 82 | .executableTarget( 83 | name: "DemoCube", 84 | dependencies: ["WindowUtils", "SwiftMath"], 85 | path: "Demos/DemoCube" 86 | ), 87 | .executableTarget( 88 | name: "DemoBoids", 89 | dependencies: ["WindowUtils"], 90 | path: "Demos/DemoBoids", 91 | linkerSettings: [ 92 | // Surely Swift PM should be linking this automatically? 93 | .linkedLibrary("m", .when(platforms: [.linux])) 94 | ] 95 | ) 96 | ], 97 | cxxLanguageStandard: .cxx17 98 | ) 99 | -------------------------------------------------------------------------------- /Plugins/GenerateWebGPUPlugin/plugin.swift: -------------------------------------------------------------------------------- 1 | import PackagePlugin 2 | import Foundation 3 | 4 | @main struct GenerateWebGPUPlugin: BuildToolPlugin { 5 | func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { 6 | let dawnJsonPath: Path 7 | 8 | if let dawnJsonEnv = ProcessInfo.processInfo.environment["DAWN_JSON"] { 9 | dawnJsonPath = Path(dawnJsonEnv) 10 | } else { 11 | dawnJsonPath = Path("/usr/local/share/dawn/dawn.json") 12 | } 13 | 14 | let generateTool = try context.tool(named: "generate-webgpu") 15 | let outputDir = context.pluginWorkDirectory.appending("Generated") 16 | 17 | let outputFiles = [ 18 | outputDir.appending("Enums.swift"), 19 | outputDir.appending("OptionSets.swift"), 20 | outputDir.appending("Structs.swift"), 21 | outputDir.appending("Classes.swift"), 22 | outputDir.appending("Functions.swift"), 23 | outputDir.appending("FunctionTypes.swift"), 24 | outputDir.appending("Callbacks.swift"), 25 | outputDir.appending("CallbackInfo.swift"), 26 | ] 27 | 28 | return [ 29 | .buildCommand( 30 | displayName: "Generating WebGPU", 31 | executable: generateTool.path, 32 | arguments: ["--dawn-json", dawnJsonPath, "--output-dir", outputDir], 33 | inputFiles: [dawnJsonPath], 34 | outputFiles: outputFiles)] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swift-webgpu 2 | 3 | Swift bindings for [WebGPU](https://gpuweb.github.io/gpuweb/); a new graphics and compute API. 4 | 5 | Despite being designed for the web, WebGPU can also be used natively, enabling developers with a modern, cross-platform API, without some of the complexities of lower-level graphics libraries. 6 | 7 | Efforts are being made to define a standard native version of the API via a [shared header](https://github.com/webgpu-native/webgpu-headers). Note, however, that the specification is still work-in-progress. 8 | 9 | Currently, swift-webgpu is based on the [Dawn](https://dawn.googlesource.com/dawn/) implementation, and generated from [dawn.json](https://dawn.googlesource.com/dawn/+/refs/heads/main/src/dawn/dawn.json). 10 | 11 | 12 | ## Requirements 13 | 14 | ### Dawn 15 | 16 | To use swift-webgpu, you'll first need to build Dawn. Assuming you don't need to work on Dawn itself, the easiest way to build it is by following the [Quickstart with CMake guide](https://dawn.googlesource.com/dawn/+/HEAD/docs/quickstart-cmake.md). 17 | 18 | swift-webgpu depends on the bundled `libwebgpu_dawn` library, which can be built and installed like so; 19 | 20 | ```sh 21 | git clone https://dawn.googlesource.com/dawn 22 | cd dawn 23 | cmake -S . -B out/Release -DDAWN_FETCH_DEPENDENCIES=ON -DDAWN_ENABLE_INSTALL=ON -DDAWN_BUILD_SAMPLES=OFF -DCMAKE_BUILD_TYPE=Release 24 | cmake --build out/Release 25 | [sudo] cmake --install out/Release 26 | ``` 27 | 28 | This should install the library and headers to `/usr/local/` - this is probably the simplest way to allow Swift to find the library currently. 29 | 30 | On macOS, you may need to correct the install name of the library, like so: 31 | 32 | ``` sh 33 | sudo install_name_tool -id /usr/local/lib/libwebgpu_dawn.dylib /usr/local/lib/libwebgpu_dawn.dylib 34 | ``` 35 | 36 | Otherwise you will likely need to place the library next to your executable, or configure rpaths appropriately for your executable. 37 | 38 | 39 | #### pkg-config 40 | You may need to manually create a pkg-config file, depending on which tools you are using. For example, running `swift build` directly from the command line seems to search `/usr/local/` automatically, whereas building with Xcode does not. If you run into this issue, create a file at `/usr/local/lib/pkgconfig/webgpu.pc` with the following contents; 41 | ``` 42 | prefix=/usr/local 43 | includedir=${prefix}/include 44 | libdir=${prefix}/lib 45 | 46 | Cflags: -I${includedir} 47 | Libs: -L${libdir} -lwebgpu_dawn 48 | ``` 49 | 50 | #### dawn.json 51 | This file contains a description of the WebGPU native API. By default, swift-webgpu will look for it in `/usr/local/share/dawn/`, so you will need to copy it there manually; 52 | ```sh 53 | sudo install -d /usr/local/share/dawn 54 | sudo install src/dawn/dawn.json /usr/local/share/dawn/ 55 | ``` 56 | 57 | Alternatively, you can specify a `DAWN_JSON` environment variable when building swift-webgpu. 58 | 59 | 60 | ## Running the Demos 61 | 62 | First, clone this project; 63 | 64 | ```sh 65 | git clone https://github.com/henrybetts/swift-webgpu.git 66 | cd swift-webgpu 67 | ``` 68 | 69 | Build the package (assuming that Dawn is installed and Swift can find it automatically); 70 | ```sh 71 | swift build -c release 72 | ``` 73 | 74 | Otherwise, you may need to specify the search paths manually; 75 | ```sh 76 | DAWN_JSON=/path/to/dawn/src/dawn/dawn.json \ 77 | swift build -c release \ 78 | -Xcc -I/path/to/dawn/include \ 79 | -Xcc -I/path/to/dawn/out/Release/gen/include \ 80 | -Xlinker -L/path/to/dawn/out/Release/src/dawn/native \ 81 | -Xlinker -rpath -Xlinker /path/to/dawn/out/Release/src/dawn/native 82 | ``` 83 | 84 | Finally, run the demos; 85 | 86 | ```sh 87 | cd .build/release 88 | ./DemoInfo 89 | ./DemoClearColor 90 | ./DemoTriangle 91 | ./DemoCube 92 | ./DemoBoids 93 | ``` 94 | 95 | 96 | ## Installation 97 | 98 | To use swift-webgpu with Swift Package Manager, add it to your `Package.swift` file's dependencies; 99 | 100 | ```swift 101 | .package(url: "https://github.com/henrybetts/swift-webgpu.git", branch: "main") 102 | ``` 103 | 104 | Then add `WebGPU` as a dependency of your target; 105 | 106 | ```swift 107 | .executableTarget( 108 | name: "MyApp", 109 | dependencies: [.product(name: "WebGPU", package: "swift-webgpu")], 110 | ), 111 | ``` 112 | 113 | 114 | ## Basic Usage 115 | 116 | Import the WebGPU module where needed; 117 | 118 | ```swift 119 | import WebGPU 120 | ``` 121 | 122 | A typical application will start by creating an `Instance`, requesting an `Adapter`, and then requesting a `Device`. For example; 123 | 124 | ```swift 125 | // create an instance 126 | let instance = createInstance() 127 | 128 | // find an adapter 129 | let adapter = try await instance.requestAdapter() 130 | print("Using adapter: \(adapter.info.device)") 131 | 132 | // create a device 133 | let device = try await adapter.requestDevice() 134 | ``` 135 | 136 | You'll usually want to set an error handler, to log any errors produced by the device; 137 | 138 | ```swift 139 | let uncapturedErrorCallback: UncapturedErrorCallback = { device, errorType, errorMessage in 140 | print("Error (\(errorType)): \(errorMessage)") 141 | } 142 | 143 | let device = try await adapter.requestDevice(descriptor: .init( 144 | uncapturedErrorCallback: uncapturedErrorCallback 145 | )) 146 | ``` 147 | 148 | With the device obtained, you can create most of the other types of WebGPU objects. A shader module, for example; 149 | 150 | ```swift 151 | let shaderModule = device.createShaderModule( 152 | descriptor: .init( 153 | nextInChain: ShaderSourceWgsl( 154 | code: """ 155 | @vertex 156 | fn vertexMain() -> @builtin(position) vec4f { 157 | return vec4f(0, 0, 0, 1); 158 | } 159 | 160 | @fragment 161 | fn fragmentMain() -> @location(0) vec4f { 162 | return vec4f(1, 0, 0, 1); 163 | } 164 | """ 165 | ) 166 | ) 167 | ) 168 | ``` 169 | 170 | Or a render pipeline; 171 | 172 | ```swift 173 | let renderPipeline = device.createRenderPipeline( 174 | descriptor: .init( 175 | vertex: VertexState( 176 | module: shaderModule, 177 | entryPoint: "vertexMain" 178 | ), 179 | fragment: FragmentState( 180 | module: shaderModule, 181 | entryPoint: "fragmentMain", 182 | targets: [ColorTargetState(format: .bgra8Unorm)] 183 | ) 184 | ) 185 | ) 186 | ``` 187 | 188 | Most objects are created with a descriptor. In some cases, WebGPU uses a chainable struct pattern to support future extensions or platform specific features. This is indicated by the `nextInChain` property. (There is scope to improve the ergonomics of this in Swift.) 189 | 190 | See the demos for further usage examples. 191 | -------------------------------------------------------------------------------- /Sources/CDawnNative/dawn_native.cpp: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | #include "dawn_native.h" 3 | } 4 | #include 5 | 6 | const DawnProcTable* dawnNativeGetProcs() { 7 | return &dawn::native::GetProcs(); 8 | } 9 | 10 | DawnNativeInstance dawnNativeCreateInstance() { 11 | auto instance = new dawn::native::Instance(); 12 | return reinterpret_cast(instance); 13 | } 14 | 15 | WGPUInstance dawnNativeInstanceGet(DawnNativeInstance cInstance) { 16 | auto instance = reinterpret_cast(cInstance); 17 | return instance->Get(); 18 | } 19 | 20 | void dawnNativeInstanceEnumerateAdapters(DawnNativeInstance cInstance, size_t* adaptersCount, DawnNativeAdapter* cAdapters) { 21 | auto instance = reinterpret_cast(cInstance); 22 | auto adapters = instance->EnumerateAdapters(); 23 | if (cAdapters == NULL) { 24 | *adaptersCount = adapters.size(); 25 | } else { 26 | size_t count = std::min(*adaptersCount, adapters.size()); 27 | for (size_t i=0; i(adapter); 30 | } 31 | *adaptersCount = count; 32 | } 33 | } 34 | 35 | void dawnNativeInstanceRelease(DawnNativeInstance cInstance) { 36 | auto instance = reinterpret_cast(cInstance); 37 | delete instance; 38 | } 39 | 40 | WGPUAdapter dawnNativeAdapterGet(DawnNativeAdapter cAdapter) { 41 | auto adapter = reinterpret_cast(cAdapter); 42 | return adapter->Get(); 43 | } 44 | 45 | WGPUDevice dawnNativeAdapterCreateDevice(DawnNativeInstance cAdapter) { 46 | auto adapter = reinterpret_cast(cAdapter); 47 | return adapter->CreateDevice(); 48 | } 49 | 50 | void dawnNativeAdapterRelease(DawnNativeAdapter cAdapter) { 51 | auto adapter = reinterpret_cast(cAdapter); 52 | delete adapter; 53 | } 54 | -------------------------------------------------------------------------------- /Sources/CDawnNative/include/dawn_native.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | typedef struct DawnNativeInstanceImpl* DawnNativeInstance; 5 | typedef struct DawnNativeAdapterImpl* DawnNativeAdapter; 6 | 7 | const DawnProcTable* dawnNativeGetProcs(); 8 | 9 | DawnNativeInstance dawnNativeCreateInstance(); 10 | WGPUInstance dawnNativeInstanceGet(DawnNativeInstance instance); 11 | void dawnNativeInstanceEnumerateAdapters(DawnNativeInstance instance, size_t* adaptersCount, DawnNativeAdapter* adapters); 12 | void dawnNativeInstanceRelease(DawnNativeInstance instance); 13 | 14 | WGPUAdapter dawnNativeAdapterGet(DawnNativeAdapter adapter); 15 | WGPUDevice dawnNativeAdapterCreateDevice(DawnNativeInstance adapter); 16 | void dawnNativeAdapterRelease(DawnNativeAdapter adapter); 17 | 18 | -------------------------------------------------------------------------------- /Sources/CDawnProc/module.modulemap: -------------------------------------------------------------------------------- 1 | module CDawnProc [system] { 2 | header "shim.h" 3 | link "dawn_proc" 4 | } 5 | -------------------------------------------------------------------------------- /Sources/CDawnProc/shim.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /Sources/CWebGPU/module.modulemap: -------------------------------------------------------------------------------- 1 | module CWebGPU [system] { 2 | header "shim.h" 3 | export * 4 | link "webgpu_dawn" 5 | } 6 | -------------------------------------------------------------------------------- /Sources/CWebGPU/shim.h: -------------------------------------------------------------------------------- 1 | // On some platforms (Linux), UINT64_MAX is defined via a "complex" macro that Swift cannot parse. 2 | // Therefore it is redefined here as a simple constant, to allow Swift to use the webgpu constants. 3 | #include 4 | #define UINT64_MAX 18446744073709551615ULL 5 | 6 | #include 7 | -------------------------------------------------------------------------------- /Sources/DawnNative/DawnNative.swift: -------------------------------------------------------------------------------- 1 | import WebGPU 2 | import CDawnNative 3 | 4 | public var procs: UnsafePointer { 5 | return dawnNativeGetProcs() 6 | } 7 | 8 | public class Instance { 9 | let instance: DawnNativeInstance! 10 | 11 | public init() { 12 | self.instance = dawnNativeCreateInstance() 13 | } 14 | 15 | deinit { 16 | dawnNativeInstanceRelease(self.instance) 17 | } 18 | 19 | public var webGpuInstance: WebGPU.Instance { 20 | let object = dawnNativeInstanceGet(self.instance) 21 | wgpuInstanceAddRef(object) 22 | return WebGPU.Instance(handle: object!) 23 | } 24 | 25 | public var adapters: [Adapter] { 26 | var count: Int = 0 27 | dawnNativeInstanceEnumerateAdapters(self.instance, &count, nil) 28 | 29 | let adapters = Array(unsafeUninitializedCapacity: count) { (buffer, initializedCount) in 30 | dawnNativeInstanceEnumerateAdapters(self.instance, &count, buffer.baseAddress) 31 | initializedCount = count 32 | } 33 | 34 | return adapters.map { Adapter(adapter: $0) } 35 | } 36 | } 37 | 38 | public class Adapter { 39 | let adapter: DawnNativeAdapter! 40 | 41 | init(adapter: DawnNativeAdapter!) { 42 | self.adapter = adapter 43 | } 44 | 45 | deinit { 46 | dawnNativeAdapterRelease(self.adapter) 47 | } 48 | 49 | public var webGpuAdapter: WebGPU.Adapter { 50 | let object = dawnNativeAdapterGet(self.adapter) 51 | wgpuAdapterAddRef(object) 52 | return WebGPU.Adapter(handle: object!) 53 | } 54 | 55 | public func createDevice() -> Device? { 56 | guard let device = dawnNativeAdapterCreateDevice(self.adapter) else { 57 | return nil 58 | } 59 | return Device(handle: device) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/WebGPU/Array+Helpers.swift: -------------------------------------------------------------------------------- 1 | extension Array where Element: ConvertibleFromC { 2 | init(cValues: S) where S.Element == Element.CType { 3 | self = cValues.map { .init(cValue: $0) } 4 | } 5 | } 6 | 7 | extension Array where Element: ConvertibleToC { 8 | func withCValues(_ body: (UnsafeBufferPointer) throws -> R) rethrows -> R { 9 | return try self.map { $0.cValue }.withUnsafeBufferPointer(body) 10 | } 11 | } 12 | 13 | extension Array where Element: ConvertibleToCWithClosure { 14 | func withCValues(_ body: (UnsafeBufferPointer) throws -> R) rethrows -> R { 15 | var cValues: [Element.CType] = [] 16 | cValues.reserveCapacity(count) 17 | var iterator = makeIterator() 18 | return try _withCValues(&cValues, appending: &iterator, body: body) 19 | } 20 | } 21 | 22 | func _withCValues(_ cValues: inout [I.Element.CType], appending iterator: inout I, body: (UnsafeBufferPointer) throws -> R) rethrows -> R where I.Element: ConvertibleToCWithClosure { 23 | if let value = iterator.next() { 24 | return try value.withCValue{ cValue in 25 | cValues.append(cValue) 26 | return try _withCValues(&cValues, appending: &iterator, body: body) 27 | } 28 | }else{ 29 | return try cValues.withUnsafeBufferPointer { buffer in 30 | try body(buffer) 31 | } 32 | } 33 | } 34 | 35 | // UnsafeRawBufferPointer is treated like an array 36 | extension UnsafeRawBufferPointer { 37 | func withUnsafeBufferPointer(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { 38 | return try body(self) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/WebGPU/Bool+Convertible.swift: -------------------------------------------------------------------------------- 1 | import CWebGPU 2 | 3 | extension Bool: ConvertibleFromC, ConvertibleToC { 4 | typealias CType = WGPUBool 5 | 6 | init(cValue: CType) { 7 | self = cValue != 0 8 | } 9 | 10 | var cValue: WGPUBool { 11 | return self ? 1 : 0 12 | } 13 | } 14 | 15 | extension Optional { 16 | init(cValue: WGPUOptionalBool) { 17 | switch cValue { 18 | case WGPUOptionalBool_True: 19 | self = true 20 | case WGPUOptionalBool_False: 21 | self = false 22 | default: 23 | self = nil 24 | } 25 | } 26 | 27 | var cValue: WGPUOptionalBool { 28 | switch self { 29 | case .some(true): 30 | return WGPUOptionalBool_True 31 | case .some(false): 32 | return WGPUOptionalBool_False 33 | case nil: 34 | return WGPUOptionalBool_Undefined 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/WebGPU/Convertible.swift: -------------------------------------------------------------------------------- 1 | protocol ConvertibleFromC { 2 | associatedtype CType 3 | init(cValue: CType) 4 | } 5 | 6 | extension ConvertibleFromC { 7 | init(cPointer: UnsafePointer) { 8 | self.init(cValue: cPointer.pointee) 9 | } 10 | } 11 | 12 | 13 | protocol ConvertibleToC { 14 | associatedtype CType 15 | var cValue: CType { get } 16 | } 17 | 18 | 19 | protocol ConvertibleToCWithClosure { 20 | associatedtype CType 21 | func withCValue(_ body: (CType) throws -> R) rethrows -> R 22 | } 23 | 24 | extension ConvertibleToCWithClosure { 25 | func withCPointer(_ body: (UnsafePointer) throws -> R) rethrows -> R { 26 | return try withCValue { value in 27 | return try withUnsafePointer(to: value) { 28 | return try body($0) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/WebGPU/Extensible.swift: -------------------------------------------------------------------------------- 1 | import CWebGPU 2 | 3 | public protocol Extensible { 4 | var nextInChain: Chained? { get } 5 | } 6 | 7 | public protocol Chained: Extensible { 8 | func withChainedStruct(_ body: (UnsafePointer) throws -> R) rethrows -> R 9 | } 10 | -------------------------------------------------------------------------------- /Sources/WebGPU/Extensions.swift: -------------------------------------------------------------------------------- 1 | // Extensions for missing features / more natural Swift interfaces 2 | 3 | import CWebGPU 4 | 5 | extension DeviceDescriptor { 6 | public init(deviceLostCallback: DeviceLostCallback? = nil, uncapturedErrorCallback: UncapturedErrorCallback? = nil) { 7 | self.init( 8 | deviceLostCallbackInfo: .init(callback: deviceLostCallback), 9 | uncapturedErrorCallbackInfo: .init(callback: uncapturedErrorCallback) 10 | ) 11 | } 12 | } 13 | 14 | extension Surface { 15 | public func getCurrentTexture() throws -> SurfaceTexture { 16 | // TODO: surfaceTexture.texture isn't marked as optional upstream, which is why this extension is needed 17 | var surfaceTexture = WGPUSurfaceTexture() 18 | withUnsafeHandle { handle in 19 | wgpuSurfaceGetCurrentTexture(handle, &surfaceTexture) 20 | } 21 | guard surfaceTexture.texture != nil else { 22 | throw RequestError(status: SurfaceGetCurrentTextureStatus(cValue: surfaceTexture.status), message: "Could not get current surface texture") 23 | } 24 | return SurfaceTexture(cValue: surfaceTexture) 25 | } 26 | 27 | public func getCapabilities(adapter: Adapter) -> SurfaceCapabilities { 28 | var capabilities = WGPUSurfaceCapabilities() 29 | defer { wgpuSurfaceCapabilitiesFreeMembers(capabilities) } 30 | 31 | let status = getCapabilities(adapter: adapter, capabilities: &capabilities) 32 | 33 | // this shouldn't fail as we are not passing in any chained structs 34 | precondition(status == .success, "Failed to get surface capabilities") 35 | 36 | return SurfaceCapabilities(cValue: capabilities) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/WebGPU/Optional+Helpers.swift: -------------------------------------------------------------------------------- 1 | import CWebGPU 2 | 3 | extension Optional { 4 | init(_ buffer: UnsafeBufferPointer) where Wrapped == Array { 5 | if !buffer.isEmpty { 6 | self = Wrapped(buffer) 7 | } else { 8 | self = nil 9 | } 10 | } 11 | 12 | func withUnsafeBufferPointer(_ body: (UnsafeBufferPointer) throws -> R) rethrows -> R where Wrapped == Array { 13 | if let value = self { 14 | return try value.withUnsafeBufferPointer(body) 15 | } else { 16 | return try body(UnsafeBufferPointer(start: nil, count: 0)) 17 | } 18 | } 19 | } 20 | 21 | extension Optional where Wrapped: ConvertibleFromC { 22 | init(cValue: Wrapped.CType) where Wrapped.CType == Optional { 23 | if let cValue = cValue { 24 | self = Wrapped(cValue: cValue) 25 | } else { 26 | self = nil 27 | } 28 | } 29 | 30 | init(cPointer: UnsafePointer?) { 31 | if let cPointer = cPointer { 32 | self = Wrapped(cPointer: cPointer) 33 | } else { 34 | self = nil 35 | } 36 | } 37 | } 38 | 39 | extension Optional { 40 | init(cValues: UnsafeBufferPointer) where Wrapped == Array { 41 | if !cValues.isEmpty { 42 | self = Wrapped(cValues: cValues) 43 | } else { 44 | self = nil 45 | } 46 | } 47 | } 48 | 49 | extension Optional where Wrapped: ConvertibleToCWithClosure { 50 | func withCValue(_ body: (Wrapped.CType) throws -> R) rethrows -> R where Wrapped.CType == Optional { 51 | if let value = self { 52 | return try value.withCValue(body) 53 | } else { 54 | return try body(nil) 55 | } 56 | } 57 | 58 | func withCPointer(_ body: (UnsafePointer?) throws -> R) rethrows -> R { 59 | if let value = self { 60 | return try value.withCPointer(body) 61 | } else { 62 | return try body(nil) 63 | } 64 | } 65 | } 66 | 67 | extension Optional where Wrapped == Chained { 68 | func withChainedStruct(_ body: (UnsafePointer?) throws -> R) rethrows -> R { 69 | if let chainedStruct = self { 70 | return try chainedStruct.withChainedStruct(body) 71 | } else { 72 | return try body(nil) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/WebGPU/RequestError.swift: -------------------------------------------------------------------------------- 1 | public struct RequestError: Error { 2 | public let status: Status 3 | public let message: String? 4 | } 5 | 6 | extension RequestError: CustomStringConvertible { 7 | public var description: String { 8 | if let message = message { 9 | return "\(message) (\(status))" 10 | } else { 11 | return String(describing: status) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/WebGPU/String+Convertible.swift: -------------------------------------------------------------------------------- 1 | import CWebGPU 2 | 3 | extension String: ConvertibleFromC, ConvertibleToCWithClosure { 4 | typealias CType = WGPUStringView 5 | 6 | init(cValue: CType) { 7 | if cValue.length == WGPU_STRLEN { 8 | self.init(cString: cValue.data) 9 | } else { 10 | let bytes = UnsafeRawBufferPointer(start: cValue.data, count: cValue.length) 11 | self.init(decoding: bytes, as: UTF8.self) 12 | } 13 | } 14 | 15 | func withCValue(_ body: (CType) throws -> R) rethrows -> R { 16 | var copy = self 17 | return try copy.withUTF8 { utf8String in 18 | return try utf8String.withMemoryRebound(to: CChar.self) { cString in 19 | let cValue = CType(data: cString.baseAddress, length: cString.count) 20 | return try body(cValue) 21 | } 22 | } 23 | } 24 | } 25 | 26 | extension Optional { 27 | init(cValue: String.CType) { 28 | if cValue.data == nil && cValue.length == WGPU_STRLEN { 29 | self = nil 30 | } else { 31 | self = String(cValue: cValue) 32 | } 33 | } 34 | 35 | func withCValue(_ body: (String.CType) throws -> R) rethrows -> R { 36 | if let value = self { 37 | return try value.withCValue(body) 38 | } else { 39 | return try body(String.CType(data: nil, length: Int(bitPattern: UInt(WGPU_STRLEN)))) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/WebGPU/UserData.swift: -------------------------------------------------------------------------------- 1 | class UserData { 2 | let value: T 3 | let retained: Bool 4 | 5 | init(_ value: T, retained: Bool = false) { 6 | self.value = value 7 | self.retained = retained 8 | } 9 | 10 | func toOpaque() -> UnsafeMutableRawPointer { 11 | if self.retained { 12 | return Unmanaged.passRetained(self).toOpaque() 13 | } else { 14 | return Unmanaged.passUnretained(self).toOpaque() 15 | } 16 | } 17 | 18 | static func passRetained(_ value: T) -> UnsafeMutableRawPointer { 19 | return UserData(value, retained: true).toOpaque() 20 | } 21 | 22 | static func takeValue(_ ptr: UnsafeRawPointer) -> T { 23 | let unmanaged = Unmanaged>.fromOpaque(ptr) 24 | let userData = unmanaged.takeUnretainedValue() 25 | if userData.retained { 26 | unmanaged.release() 27 | } 28 | return userData.value 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Decoding/DawnData.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | 4 | // MARK: Primitives 5 | 6 | enum Category: String, Decodable { 7 | case native 8 | case typedef 9 | case `enum` 10 | case bitmask 11 | case structure 12 | case object 13 | case constant 14 | case function 15 | case functionPointer = "function pointer" 16 | case callbackFunction = "callback function" 17 | case callbackInfo = "callback info" 18 | } 19 | 20 | enum Length: Equatable { 21 | case fixed(Int) 22 | case member(String) 23 | 24 | static let single = Length.fixed(1) 25 | } 26 | 27 | extension Length: HasDefaultValue { 28 | init() { 29 | self = .single 30 | } 31 | } 32 | 33 | extension Length: Decodable { 34 | init(from decoder: Decoder) throws { 35 | let container = try decoder.singleValueContainer() 36 | do { 37 | self = try .fixed(container.decode(Int.self)) 38 | } catch DecodingError.typeMismatch { 39 | self = try .member(container.decode(String.self)) 40 | } 41 | } 42 | } 43 | 44 | enum Annotation: String, Decodable { 45 | case pointer = "const*" 46 | case mutablePointer = "*" 47 | case pointerToPointer = "const*const*" 48 | } 49 | 50 | enum Extensibility: String { 51 | case none 52 | case `in` 53 | case out 54 | } 55 | 56 | extension Extensibility: HasDefaultValue { 57 | init() { 58 | self = .none 59 | } 60 | } 61 | 62 | extension Extensibility: Decodable { 63 | init(from decoder: Decoder) throws { 64 | let container = try decoder.singleValueContainer() 65 | do { 66 | let value = try container.decode(String.self) 67 | if let extensibility = Extensibility(rawValue: value) { 68 | self = extensibility 69 | return 70 | } 71 | } catch DecodingError.typeMismatch { 72 | let value = try container.decode(Bool.self) 73 | if !value { 74 | self = .none 75 | return 76 | } 77 | } 78 | 79 | throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Invalid value")) 80 | } 81 | } 82 | 83 | 84 | // MARK: Custom Decoders 85 | 86 | @propertyWrapper 87 | struct DefaultValueDecoder { 88 | // "default" values in dawn.json can be strings or ints 89 | var wrappedValue: String? 90 | } 91 | 92 | extension DefaultValueDecoder: DefaultFallbackProtocol { 93 | init(from decoder: Decoder) throws { 94 | let container = try decoder.singleValueContainer() 95 | do { 96 | self.wrappedValue = try container.decode(String.self) 97 | } catch DecodingError.typeMismatch { 98 | self.wrappedValue = try String(container.decode(Int.self)) 99 | } 100 | } 101 | } 102 | 103 | 104 | // MARK: Secondary Data 105 | 106 | struct EnumValueData: Taggable, Decodable { 107 | @DefaultFallback var tags: Set 108 | var name: String 109 | var value: Int 110 | } 111 | 112 | struct RecordMemberData: Taggable, Decodable { 113 | @DefaultFallback var tags: Set 114 | var name: String 115 | var type: String 116 | var annotation: Annotation? 117 | @DefaultFallback var length: Length 118 | @DefaultFallback var optional: Bool 119 | @DefaultValueDecoder var `default`: String? 120 | } 121 | 122 | typealias RecordData = [RecordMemberData] 123 | 124 | struct MethodData: Taggable, Decodable { 125 | @DefaultFallback var tags: Set 126 | var name: String 127 | var returns: String? 128 | @DefaultFallback var args: RecordData 129 | } 130 | 131 | 132 | // MARK: Type Data 133 | 134 | protocol TypeData: Taggable { 135 | var category: Category { get } 136 | var tags: Set { get } 137 | } 138 | 139 | struct NativeTypeData: TypeData, Decodable { 140 | var category: Category 141 | @DefaultFallback var tags: Set 142 | } 143 | 144 | struct TypedefTypeData: TypeData, Decodable { 145 | var category: Category 146 | @DefaultFallback var tags: Set 147 | var type: String 148 | } 149 | 150 | struct EnumTypeData: TypeData, Decodable { 151 | var category: Category 152 | @DefaultFallback var tags: Set 153 | var values: [EnumValueData] 154 | } 155 | 156 | struct StructureTypeData: TypeData, Decodable { 157 | var category: Category 158 | @DefaultFallback var tags: Set 159 | var members: RecordData 160 | @DefaultFallback var extensible: Extensibility 161 | @DefaultFallback var chained: Extensibility 162 | } 163 | 164 | struct ObjectTypeData: TypeData, Decodable { 165 | var category: Category 166 | @DefaultFallback var tags: Set 167 | @DefaultFallback var methods: [MethodData] 168 | } 169 | 170 | struct ConstantTypeData: TypeData, Decodable { 171 | var category: Category 172 | @DefaultFallback var tags: Set 173 | var type: String 174 | var value: String 175 | } 176 | 177 | struct FunctionTypeData: TypeData, Decodable { 178 | var category: Category 179 | @DefaultFallback var tags: Set 180 | var returns: String? 181 | @DefaultFallback var args: RecordData 182 | } 183 | 184 | struct CallbackFunctionTypeData: TypeData, Decodable { 185 | var category: Category 186 | @DefaultFallback var tags: Set 187 | @DefaultFallback var args: RecordData 188 | } 189 | 190 | struct CallbackInfoTypeData: TypeData, Decodable { 191 | var category: Category 192 | @DefaultFallback var tags: Set 193 | var members: RecordData 194 | } 195 | 196 | 197 | // MARK: Dawn Data 198 | 199 | struct DawnData: Decodable { 200 | var types: [String: TypeData] 201 | 202 | init(from decoder: Decoder) throws { 203 | let container = try decoder.container(keyedBy: String.self) 204 | var types: [String: TypeData] = [:] 205 | 206 | for key in container.allKeys { 207 | if key.starts(with: "_") { continue } 208 | 209 | let unresolved = try container.nestedContainer(keyedBy: String.self, forKey: key) 210 | let category = try unresolved.decode(Category.self, forKey: "category") 211 | 212 | switch category { 213 | case .native: 214 | types[key] = try container.decode(NativeTypeData.self, forKey: key) 215 | case .typedef: 216 | types[key] = try container.decode(TypedefTypeData.self, forKey: key) 217 | case .enum, .bitmask: 218 | types[key] = try container.decode(EnumTypeData.self, forKey: key) 219 | case .structure: 220 | types[key] = try container.decode(StructureTypeData.self, forKey: key) 221 | case .object: 222 | types[key] = try container.decode(ObjectTypeData.self, forKey: key) 223 | case .constant: 224 | types[key] = try container.decode(ConstantTypeData.self, forKey: key) 225 | case .function, .functionPointer: 226 | types[key] = try container.decode(FunctionTypeData.self, forKey: key) 227 | case .callbackFunction: 228 | types[key] = try container.decode(CallbackFunctionTypeData.self, forKey: key) 229 | case .callbackInfo: 230 | types[key] = try container.decode(CallbackInfoTypeData.self, forKey: key) 231 | } 232 | } 233 | 234 | self.types = types 235 | } 236 | 237 | init(from data: Data) throws { 238 | let decoder = JSONDecoder() 239 | self = try decoder.decode(Self.self, from: data) 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Decoding/DefaultFallback.swift: -------------------------------------------------------------------------------- 1 | protocol DefaultFallbackProtocol: HasDefaultValue & Decodable {} 2 | 3 | @propertyWrapper 4 | struct DefaultFallback { 5 | var wrappedValue: T 6 | } 7 | 8 | extension DefaultFallback: DefaultFallbackProtocol { 9 | init() { 10 | self.wrappedValue = T() 11 | } 12 | 13 | init(from decoder: Decoder) throws { 14 | let container = try decoder.singleValueContainer() 15 | self.wrappedValue = (try? container.decode(T.self)) ?? T() 16 | } 17 | } 18 | 19 | extension KeyedDecodingContainer { 20 | // This is used to override the default decoding behavior to allow a value to avoid a missing key Error 21 | func decode(_ type: T.Type, forKey key: KeyedDecodingContainer.Key) throws -> T where T: DefaultFallbackProtocol { 22 | return try decodeIfPresent(T.self, forKey: key) ?? T() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Decoding/HasDefaultValue.swift: -------------------------------------------------------------------------------- 1 | protocol HasDefaultValue { 2 | init() 3 | } 4 | 5 | extension Array: HasDefaultValue {} 6 | extension Set: HasDefaultValue {} 7 | extension Bool: HasDefaultValue {} 8 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Decoding/String+CodingKey.swift: -------------------------------------------------------------------------------- 1 | extension String: CodingKey { 2 | public init?(stringValue: String) { 3 | self.init(stringValue) 4 | } 5 | 6 | public init?(intValue: Int) { 7 | return nil 8 | } 9 | 10 | public var stringValue: String { 11 | return self 12 | } 13 | 14 | public var intValue: Int? { 15 | return nil 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Decoding/Taggable.swift: -------------------------------------------------------------------------------- 1 | protocol Taggable { 2 | var tags: Set { get } 3 | } 4 | 5 | extension Taggable { 6 | var isEnabled: Bool { 7 | if tags.isEmpty { 8 | return true 9 | } 10 | // TODO: This could be configurable 11 | return tags.isSubset(of: ["native", "deprecated"]) 12 | } 13 | 14 | var isUpstream: Bool { 15 | return tags.contains("upstream") 16 | } 17 | 18 | var isDeprecated: Bool { 19 | return tags.contains("deprecated") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Generate/CodeBuilder.swift: -------------------------------------------------------------------------------- 1 | @resultBuilder 2 | struct CodeBuilder { 3 | static func buildExpression(_ expression: [String]) -> [String] { 4 | return expression 5 | } 6 | 7 | static func buildExpression(_ expression: String) -> [String] { 8 | return [expression] 9 | } 10 | 11 | static func buildExpression(_ expression: String?) -> [String] { 12 | guard let expression = expression else { return [] } 13 | return [expression] 14 | } 15 | 16 | static func buildExpression(_ expression: ()) -> [String] { 17 | return [] 18 | } 19 | 20 | static func buildBlock(_ components: [String]...) -> [String] { 21 | return components.flatMap { $0 } 22 | } 23 | 24 | static func buildOptional(_ component: [String]?) -> [String] { 25 | return component ?? [] 26 | } 27 | 28 | static func buildEither(first component: [String]?) -> [String] { 29 | return component ?? [] 30 | } 31 | 32 | static func buildEither(second component: [String]?) -> [String] { 33 | return component ?? [] 34 | } 35 | 36 | static func buildArray(_ components: [[String]]) -> [String] { 37 | return components.flatMap { $0 } 38 | } 39 | } 40 | 41 | func code(@CodeBuilder builder: () -> [String]) -> String { 42 | return builder().joined(separator: "\n") 43 | } 44 | 45 | func indented(size: Int = 4, @CodeBuilder builder: () -> [String]) -> [String] { 46 | let indent = String(repeating: " ", count: size) 47 | return builder().flatMap { $0.split(separator: "\n", omittingEmptySubsequences: false).map { indent + $0 } } 48 | } 49 | 50 | func block(_ prefix: String? = nil, _ suffix: String? = nil, condition: Bool = true, @CodeBuilder builder: () -> [String]) -> String { 51 | guard condition else { return code(builder: builder) } 52 | return code { 53 | line { 54 | if let prefix = prefix { 55 | prefix 56 | " " 57 | } 58 | "{" 59 | if let suffix = suffix { 60 | " " 61 | suffix 62 | } 63 | } 64 | indented(builder: builder) 65 | "}" 66 | } 67 | } 68 | 69 | func commaSeparated(@CodeBuilder builder: () -> [String]) -> String { 70 | return builder().joined(separator: ", ") 71 | } 72 | 73 | func line(@CodeBuilder builder: () -> [String]) -> String { 74 | return builder().joined() 75 | } 76 | 77 | extension String { 78 | func swiftSafe() -> String { 79 | if (["repeat", "internal", "false", "true"].contains(self)) { 80 | return "`\(self)`" 81 | } 82 | return self 83 | } 84 | } 85 | 86 | func availability(of type: Taggable) -> String? { 87 | if type.isDeprecated { 88 | return "@available(*, deprecated)" 89 | } else { 90 | return nil 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Generate/Conversion.swift: -------------------------------------------------------------------------------- 1 | func convertCToSwift(cValue: String, swiftType: String, typeConversion: TypeConversion, count: String? = nil) -> String { 2 | var cValue = cValue 3 | 4 | if (typeConversion == .array || typeConversion == .nativeArray), let count = count { 5 | cValue = "UnsafeBufferPointer(start: \(cValue), count: Int(\(count)))" 6 | } 7 | 8 | switch typeConversion { 9 | case .value, .valueWithClosure: 10 | return "\(swiftType)(cValue: \(cValue))" 11 | case .pointerWithClosure: 12 | return "\(swiftType)(cPointer: \(cValue))" 13 | case .array: 14 | return "\(swiftType)(cValues: \(cValue))" 15 | case .nativeArray: 16 | return "\(swiftType)(\(cValue))" 17 | default: 18 | return cValue 19 | } 20 | } 21 | 22 | func convertCToSwift(member: RecordMember, prefix: String = "") -> String { 23 | let count: String? 24 | if let lengthMember = member.lengthMember { 25 | count = prefix + lengthMember.cName 26 | } else if case .fixed(let length) = member.length { 27 | count = String(length) 28 | } else { 29 | count = nil 30 | } 31 | 32 | return convertCToSwift(cValue: prefix + member.cName, swiftType: member.swiftType, typeConversion: member.typeConversion, count: count) 33 | } 34 | 35 | 36 | func convertSwiftToC(members: Record, prefix: String = "", throws: Bool = false, @CodeBuilder builder: ([String]) -> [String]) -> String { 37 | return code { 38 | 39 | var indentationSize = 0 40 | let returnTry = `throws` ? "return try" : "return" 41 | 42 | for member in members { 43 | indented(size: indentationSize) { 44 | switch member.typeConversion { 45 | case .valueWithClosure: 46 | "\(returnTry) \(prefix)\(member.swiftName).withCValue { c_\(member.swiftName) in" 47 | indentationSize += 4 48 | case .pointerWithClosure: 49 | "\(returnTry) \(prefix)\(member.swiftName).withCPointer { c_\(member.swiftName) in" 50 | indentationSize += 4 51 | case .array: 52 | "\(returnTry) \(prefix)\(member.swiftName).withCValues { c_\(member.swiftName) in" 53 | indentationSize += 4 54 | case .nativeArray: 55 | "\(returnTry) \(prefix)\(member.swiftName).withUnsafeBufferPointer { c_\(member.swiftName) in" 56 | indentationSize += 4 57 | default: 58 | () 59 | } 60 | } 61 | } 62 | 63 | let cValues = members.map { (member) -> String in 64 | switch member.typeConversion { 65 | case .native: 66 | return prefix + member.swiftName 67 | case .value: 68 | return "\(prefix)\(member.swiftName).cValue" 69 | case .valueWithClosure, .pointerWithClosure: 70 | return "c_\(member.swiftName)" 71 | case .array, .nativeArray: 72 | return "c_\(member.swiftName).baseAddress" 73 | case .length: 74 | return "\(member.cType)(c_\(member.parentMember!.swiftName).count)" 75 | case .callback: 76 | let callback = member.type as! CallbackFunctionType 77 | if member.isOptional { 78 | return "\(prefix)\(member.swiftName) != nil ? \(callback.callbackFunctionName) : nil" 79 | } else { 80 | return callback.callbackFunctionName 81 | } 82 | } 83 | } 84 | 85 | indented(size: indentationSize) { builder(cValues) } 86 | 87 | for member in members { 88 | switch member.typeConversion { 89 | case .valueWithClosure, .pointerWithClosure, .array, .nativeArray: 90 | indentationSize -= 4 91 | indented(size: indentationSize) { "}" } 92 | default: 93 | () 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Generate/GenerateCallbackInfo.swift: -------------------------------------------------------------------------------- 1 | func generateCallbackInfo(model: Model) -> String { 2 | return code { 3 | "import CWebGPU" 4 | "" 5 | 6 | for type in model.types(of: CallbackInfoType.self) { 7 | availability(of: type) 8 | block("public struct \(type.swiftName): ConvertibleToCWithClosure") { 9 | "typealias CType = \(type.cName)" 10 | "" 11 | 12 | for member in type.members.removingHidden { 13 | "public var \(member.swiftName): \(member.swiftType)" 14 | } 15 | "" 16 | 17 | block("public init(\(generateParameters(type.members)))") { 18 | for member in type.members.removingHidden { 19 | "self.\(member.swiftName) = \(member.swiftName)" 20 | } 21 | } 22 | "" 23 | 24 | block("func withCValue(_ body: (\(type.cName)) throws -> R) rethrows -> R") { 25 | convertSwiftToC(members: type.members, prefix: "self.", throws: true) { cValues in 26 | let structArgs = commaSeparated { 27 | "nextInChain: nil" 28 | for (member, cValue) in zip(type.members, cValues) { 29 | "\(member.cName): \(cValue)" 30 | } 31 | if type.name.hasPrefix("uncaptured error callback info") { 32 | // uncaptured error callback is a special case, since it can be called multiple times 33 | // TODO: The userdata is currently leaked - need some way of managing the memory for this 34 | "userdata1: self.callback != nil ? Unmanaged.passRetained(UserData(self.callback)).toOpaque() : nil" 35 | } else { 36 | if type.callbackMember.isOptional { 37 | "userdata1: self.callback != nil ? UserData.passRetained(self.callback) : nil" 38 | } else { 39 | "userdata1: UserData.passRetained(self.callback)" 40 | } 41 | } 42 | "userdata2: nil" 43 | } 44 | 45 | "let cStruct = \(type.cName)(\(structArgs))" 46 | "return try body(cStruct)" 47 | } 48 | } 49 | } 50 | "" 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Generate/GenerateCallbacks.swift: -------------------------------------------------------------------------------- 1 | func generateCallbacks(model: Model) -> String { 2 | return code { 3 | "import CWebGPU" 4 | "" 5 | 6 | for type in model.types(of: CallbackFunctionType.self) { 7 | let callbackArgumentTypes = commaSeparated { 8 | for arg in type.arguments.removingHidden { 9 | arg.swiftType 10 | } 11 | } 12 | 13 | "public typealias \(type.swiftName) = (\(callbackArgumentTypes)) -> ()" 14 | "" 15 | 16 | 17 | let callbackArguments = commaSeparated { 18 | for arg in type.arguments { 19 | "\(arg.swiftName): \(arg.cType)" 20 | } 21 | "userdata1: UnsafeMutableRawPointer!" 22 | "userdata2: UnsafeMutableRawPointer!" 23 | } 24 | 25 | block("func \(type.callbackFunctionName)(\(callbackArguments))") { 26 | "let _callback = UserData<\(type.swiftName)>.takeValue(userdata1)" 27 | let callbackArguments = commaSeparated { 28 | for arg in type.arguments.removingHidden { 29 | convertCToSwift(member: arg) 30 | } 31 | } 32 | "_callback(\(callbackArguments))" 33 | } 34 | "" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Generate/GenerateClasses.swift: -------------------------------------------------------------------------------- 1 | func generateClasses(model: Model) -> String { 2 | return code { 3 | "import CWebGPU" 4 | "" 5 | 6 | for type in model.types(of: ObjectType.self) { 7 | availability(of: type) 8 | block("public class \(type.swiftName): ConvertibleFromC, ConvertibleToCWithClosure") { 9 | "typealias CType = \(type.cName)?" 10 | 11 | "private let handle: \(type.cName)" 12 | "" 13 | 14 | "/// Create a wrapper around an existing handle." 15 | "///" 16 | "/// The ownership of the handle is transferred to this class." 17 | "///" 18 | "/// - Parameter handle: The handle to wrap." 19 | block("public init(handle: \(type.cName))") { 20 | "self.handle = handle" 21 | } 22 | "" 23 | 24 | block("required convenience init(cValue: \(type.cName)?)") { 25 | "self.init(handle: cValue!)" 26 | } 27 | "" 28 | 29 | block("deinit") { 30 | "\(type.releaseFunctionName)(handle)" 31 | } 32 | "" 33 | 34 | "/// Calls the given closure with the underlying handle." 35 | "///" 36 | "/// The underlying handle is guaranteed not to be released before the closure returns." 37 | "///" 38 | "/// - Parameter body: A closure to call with the underlying handle." 39 | block("public func withUnsafeHandle(_ body: (\(type.cName)) throws -> R) rethrows -> R") { 40 | block("return try withExtendedLifetime(self)") { 41 | "return try body(handle)" 42 | } 43 | } 44 | "" 45 | 46 | block("func withCValue(_ body: (\(type.cName)?) throws -> R) rethrows -> R") { 47 | "return try withUnsafeHandle(body)" 48 | } 49 | 50 | for method in type.methods { 51 | "" 52 | generateFunction(method, isMethod: true) 53 | } 54 | } 55 | "" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Generate/GenerateEnums.swift: -------------------------------------------------------------------------------- 1 | func generateEnums(model: Model) -> String { 2 | return code { 3 | "import CWebGPU" 4 | "" 5 | 6 | for type in model.types(of: EnumType.self) { 7 | availability(of: type) 8 | block("public enum \(type.swiftName): \(type.cName).RawValue, ConvertibleFromC, ConvertibleToC") { 9 | "typealias CType = \(type.cName)" 10 | "" 11 | 12 | for value in type.values { 13 | "case \(value.swiftName.swiftSafe()) = \(value.value)" 14 | } 15 | "" 16 | 17 | block("init(cValue: \(type.cName))") { 18 | "self.init(rawValue: cValue.rawValue)!" 19 | } 20 | "" 21 | 22 | block("var cValue: \(type.cName)") { 23 | "return \(type.cName)(rawValue: self.rawValue)" 24 | } 25 | } 26 | "" 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Generate/GenerateFunction.swift: -------------------------------------------------------------------------------- 1 | func generateFunction(_ function: FunctionType, isMethod: Bool = false) -> String { 2 | if function.isGetter { 3 | return generateGetter(function: function, isMethod: isMethod) 4 | } else if function.isExtensibleGetter { 5 | return generateExtensibleGetter(function: function, isMethod: isMethod) 6 | } else if function.isEnumerator { 7 | return generateEnumerator(function: function, isMethod: isMethod) 8 | } else { 9 | return generateStandard(function: function, isMethod: isMethod) 10 | } 11 | } 12 | 13 | func generateParameters(_ record: Record, hideFirstLabel: Bool = false) -> String { 14 | commaSeparated { 15 | for (index, arg) in record.enumerated() { 16 | line { 17 | if index == 0 && hideFirstLabel { 18 | "_ " 19 | } 20 | "\(arg.swiftName): " 21 | if (arg.type?.category == .functionPointer || arg.type?.category == .callbackFunction) && !arg.isOptional { 22 | "@escaping " 23 | } 24 | arg.swiftType 25 | if let defaultValue = arg.defaultSwiftValue { 26 | " = \(defaultValue)" 27 | } 28 | } 29 | } 30 | } 31 | } 32 | 33 | fileprivate func generateStandard(function: FunctionType, isMethod: Bool) -> String { 34 | code { 35 | let functionParams = generateParameters(function.arguments.removingHidden, hideFirstLabel: function.hideFirstArgumentLabel) 36 | 37 | 38 | let functionDefinition = line { 39 | "public func \(function.swiftFunctionName)(\(functionParams))" 40 | if let returnType = function.swiftReturnType { 41 | " -> \(returnType)" 42 | } 43 | } 44 | 45 | availability(of: function) 46 | block(functionDefinition) { 47 | block("return withUnsafeHandle", "_handle in", condition: isMethod) { 48 | convertSwiftToC(members: function.arguments) { cValues in 49 | 50 | let functionArgs = commaSeparated { 51 | if isMethod { "_handle" } 52 | cValues 53 | } 54 | 55 | line { 56 | if function.returnConversion != nil { 57 | "let _result = " 58 | } 59 | "\(function.cFunctionName)(\(functionArgs))" 60 | } 61 | 62 | if let returnConversion = function.returnConversion, let returnType = function.swiftReturnType { 63 | "return \(convertCToSwift(cValue: "_result", swiftType: returnType, typeConversion: returnConversion))" 64 | } 65 | } 66 | } 67 | } 68 | 69 | if function.isRequest { 70 | "" 71 | generateRequestOverloads(function: function) 72 | } 73 | 74 | } 75 | } 76 | 77 | fileprivate func generateRequestOverloads(function: FunctionType) -> String { 78 | code { 79 | let functionArgs = function.arguments.dropLast().removingHidden 80 | 81 | let callbackInfo = function.arguments.last!.type as! CallbackInfoType 82 | let callback = callbackInfo.callbackMember.type as! CallbackFunctionType 83 | let callbackArgs = callback.arguments.removingHidden 84 | 85 | let statusArg = callbackArgs[0] 86 | let successArg = callbackArgs.count > 1 ? callbackArgs[1] : nil 87 | let messageArg = callbackArgs.count > 2 ? callbackArgs[2] : nil 88 | 89 | let successType = successArg?.unwrappedSwiftType ?? "Void" 90 | let failureType = "RequestError<\(statusArg.swiftType)>" 91 | let resultType = "Result<\(successType), \(failureType)>" 92 | 93 | let functionParams = generateParameters(functionArgs, hideFirstLabel: function.hideFirstArgumentLabel) 94 | 95 | let functionParamsWithCallback = commaSeparated { 96 | if functionArgs.count > 0 { 97 | functionParams 98 | } 99 | "callback: @escaping (\(resultType)) -> Void" 100 | } 101 | 102 | let functionCallArgs = commaSeparated { 103 | for (index, arg) in functionArgs.enumerated() { 104 | if index == 0 && function.hideFirstArgumentLabel { 105 | arg.swiftName 106 | } else { 107 | "\(arg.swiftName): \(arg.swiftName)" 108 | } 109 | } 110 | } 111 | 112 | availability(of: function) 113 | "@discardableResult" 114 | block("public func \(function.swiftFunctionName)(\(functionParamsWithCallback)) -> Future") { 115 | 116 | let callbackArgNames = commaSeparated { 117 | for arg in callbackArgs { 118 | arg.swiftName 119 | } 120 | } 121 | 122 | block("let _callbackWrapper: \(callback.swiftName) = ", "\(callbackArgNames) in") { 123 | block("if status == .success") { 124 | let successArg = line { 125 | "\(successArg?.swiftName ?? "()")" 126 | if successArg?.isOptional == true { 127 | "!" 128 | } 129 | } 130 | "callback(Result.success(\(successArg)))" 131 | } 132 | block("else") { 133 | "callback(Result.failure(RequestError(status: \(statusArg.swiftName), message: \(messageArg?.swiftName ?? "nil"))))" 134 | } 135 | } 136 | 137 | let functionCallArgsWithCallback = commaSeparated { 138 | if functionArgs.count > 0 { 139 | functionCallArgs 140 | } 141 | "callbackInfo: \(callbackInfo.swiftName)(callback: _callbackWrapper)" 142 | } 143 | 144 | "return \(function.swiftFunctionName)(\(functionCallArgsWithCallback))" 145 | } 146 | "" 147 | 148 | availability(of: function) 149 | block("public func \(function.swiftFunctionName)(\(functionParams)) async throws -> \(successType)") { 150 | block("return try await withUnsafeThrowingContinuation", "_continuation in") { 151 | block("\(function.swiftFunctionName)(\(functionCallArgs))", "_result in") { 152 | "_continuation.resume(with: _result)" 153 | } 154 | } 155 | } 156 | } 157 | } 158 | 159 | fileprivate func generateGetter(function: FunctionType, isMethod: Bool) -> String { 160 | code { 161 | availability(of: function) 162 | block("public var \(function.swiftFunctionName): \(function.swiftReturnType!)") { 163 | block("return withUnsafeHandle", "_handle in", condition: isMethod) { 164 | let functionArgs = isMethod ? "_handle" : "" 165 | 166 | "let _result = \(function.cFunctionName)(\(functionArgs))" 167 | 168 | "return \(convertCToSwift(cValue: "_result", swiftType: function.swiftReturnType!, typeConversion: function.returnConversion!))" 169 | } 170 | } 171 | } 172 | } 173 | 174 | fileprivate func generateExtensibleGetter(function: FunctionType, isMethod: Bool) -> String { 175 | code { 176 | let structType = function.arguments[0].type! 177 | 178 | availability(of: function) 179 | block("public var \(function.swiftFunctionName): \(structType.swiftName)") { 180 | block("return withUnsafeHandle", "_handle in", condition: isMethod) { 181 | "var _cStruct = \(structType.cName)()" 182 | 183 | if let s = structType as? StructureType { 184 | if s.extensible != .none { 185 | "_cStruct.nextInChain = nil" 186 | } 187 | } 188 | 189 | let functionArgs = commaSeparated { 190 | if isMethod { "_handle" } 191 | "&_cStruct" 192 | } 193 | 194 | "\(function.cFunctionName)(\(functionArgs))" 195 | 196 | "return \(convertCToSwift(cValue: "_cStruct", swiftType: structType.swiftName, typeConversion: .value))" 197 | } 198 | } 199 | } 200 | } 201 | 202 | fileprivate func generateEnumerator(function: FunctionType, isMethod: Bool) -> String { 203 | code { 204 | let enumeratedType = function.arguments[0].type! 205 | let returnType = function.swiftReturnType! 206 | 207 | availability(of: function) 208 | block("public var \(function.swiftFunctionName): \(returnType)") { 209 | block("return withUnsafeHandle", "_handle in", condition: isMethod) { 210 | 211 | let functionArgs = commaSeparated { 212 | if isMethod { "_handle" } 213 | "nil" 214 | } 215 | "let _count = \(function.cFunctionName)(\(functionArgs))" 216 | 217 | block("let _cValues = [\(enumeratedType.cName)](unsafeUninitializedCapacity: _count)", "_buffer, _initializedCount in") { 218 | let functionArgs = commaSeparated { 219 | if isMethod { "_handle" } 220 | "_buffer.baseAddress" 221 | } 222 | "_initializedCount = \(function.cFunctionName)(\(functionArgs))" 223 | } 224 | 225 | "return \(convertCToSwift(cValue: "_cValues", swiftType: returnType, typeConversion: function.returnConversion!))" 226 | } 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Generate/GenerateFunctionTypes.swift: -------------------------------------------------------------------------------- 1 | func generateFunctionTypes(model: Model) -> String { 2 | return code { 3 | "import CWebGPU" 4 | "" 5 | 6 | for type in model.types(of: FunctionPointerType.self) { 7 | let functionArgs = commaSeparated { 8 | for arg in type.arguments.removingHidden { 9 | arg.swiftType 10 | } 11 | } 12 | let returnType = type.swiftReturnType ?? "()" 13 | 14 | line { 15 | "public typealias \(type.swiftName) = (\(functionArgs)) -> \(returnType)" 16 | } 17 | "" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Generate/GenerateFunctions.swift: -------------------------------------------------------------------------------- 1 | func generateFunctions(model: Model) -> String { 2 | return code { 3 | "import CWebGPU" 4 | "" 5 | 6 | for type in model.types(of: FunctionType.self) { 7 | generateFunction(type) 8 | "" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Generate/GenerateOptionSets.swift: -------------------------------------------------------------------------------- 1 | func generateOptionSets(model: Model) -> String { 2 | return code { 3 | "import CWebGPU" 4 | "" 5 | 6 | for type in model.types(of: BitmaskType.self) { 7 | availability(of: type) 8 | block("public struct \(type.swiftName): OptionSet, ConvertibleFromC, ConvertibleToC") { 9 | "typealias CType = \(type.cName)" 10 | "" 11 | 12 | "public let rawValue: \(type.cName)" 13 | "" 14 | 15 | block("public init(rawValue: \(type.cName))") { 16 | "self.rawValue = rawValue" 17 | } 18 | "" 19 | 20 | block("init(cValue: \(type.cName))") { 21 | "self.init(rawValue: cValue)" 22 | } 23 | "" 24 | 25 | block("var cValue: \(type.cName)") { 26 | "return self.rawValue" 27 | } 28 | "" 29 | 30 | for value in type.values { 31 | if value.value != 0 { 32 | "public static let \(value.swiftName) = \(type.swiftName)(rawValue: \(value.value))" 33 | } else { 34 | "public static let \(value.swiftName) = \(type.swiftName)([])" 35 | } 36 | } 37 | } 38 | "" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Generate/GenerateStructs.swift: -------------------------------------------------------------------------------- 1 | func generateStructs(model: Model) -> String { 2 | return code { 3 | "import CWebGPU" 4 | "" 5 | 6 | for type in model.types(of: StructureType.self) { 7 | 8 | let adoptions = commaSeparated { 9 | if type.name != "device descriptor" { 10 | "ConvertibleFromC" 11 | } 12 | "ConvertibleToCWithClosure" 13 | if type.extensible == .in { 14 | "Extensible" 15 | } 16 | if type.chained == .in { 17 | "Chained" 18 | } 19 | } 20 | 21 | availability(of: type) 22 | block("public struct \(type.swiftName): \(adoptions)") { 23 | "typealias CType = \(type.cName)" 24 | "" 25 | 26 | for member in type.members.removingHidden { 27 | "public var \(member.swiftName): \(member.swiftType)" 28 | } 29 | 30 | if type.extensible == .in || type.chained == .in { 31 | "public var nextInChain: Chained?" 32 | } 33 | 34 | "" 35 | 36 | let initParams = commaSeparated { 37 | for member in type.members.removingHidden { 38 | line { 39 | "\(member.swiftName): \(member.swiftType)" 40 | if let defaultValue = member.defaultSwiftValue { 41 | " = \(defaultValue)" 42 | } 43 | } 44 | } 45 | if type.extensible == .in || type.chained == .in { 46 | "nextInChain: Chained? = nil" 47 | } 48 | } 49 | 50 | block("public init(\(initParams))") { 51 | for member in type.members.removingHidden { 52 | "self.\(member.swiftName) = \(member.swiftName)" 53 | } 54 | if type.extensible == .in || type.chained == .in { 55 | "self.nextInChain = nextInChain" 56 | } 57 | } 58 | "" 59 | 60 | if type.name != "device descriptor" { 61 | block("init(cValue: \(type.cName))") { 62 | for member in type.members.removingHidden { 63 | "self.\(member.swiftName) = \(convertCToSwift(member: member, prefix: "cValue."))" 64 | } 65 | } 66 | "" 67 | 68 | // TODO: This may be unsafe for certain structs - add some conditional logic 69 | "public static var zero = \(type.swiftName)(cValue: \(type.cName)())" 70 | "" 71 | } 72 | 73 | block("func withCValue(_ body: (\(type.cName)) throws -> R) rethrows -> R") { 74 | block("return try self.nextInChain.withChainedStruct", "chainedStruct in", condition: type.extensible == .in || type.chained == .in) { 75 | convertSwiftToC(members: type.members, prefix: "self.", throws: true) { cValues in 76 | let structArgs = commaSeparated { 77 | switch type.extensible { 78 | case .in: 79 | "nextInChain: UnsafeMutablePointer(mutating: chainedStruct)" 80 | case .out: 81 | "nextInChain: nil" 82 | case .none: 83 | () 84 | } 85 | 86 | switch type.chained { 87 | case .in: 88 | "chain: WGPUChainedStruct(next: UnsafeMutablePointer(mutating: chainedStruct), sType: \(type.sType))" 89 | case .out: 90 | "chain: WGPUChainedStruct(next: nil, sType: \(type.sType))" 91 | case .none: 92 | () 93 | } 94 | 95 | for (member, cValue) in zip(type.members, cValues) { 96 | "\(member.cName): \(cValue)" 97 | } 98 | } 99 | 100 | "let cStruct = \(type.cName)(\(structArgs))" 101 | "return try body(cStruct)" 102 | } 103 | } 104 | } 105 | 106 | if type.chained == .in { 107 | "" 108 | block("public func withChainedStruct(_ body: (UnsafePointer) throws -> R) rethrows -> R") { 109 | block("return try withCPointer", "cStruct in") { 110 | "let chainedStruct = UnsafeRawPointer(cStruct).bindMemory(to: WGPUChainedStruct.self, capacity: 1)" 111 | "return try body(chainedStruct)" 112 | } 113 | } 114 | } 115 | } 116 | "" 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/GenerateWebGPU.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | import Foundation 3 | 4 | @main 5 | struct GenerateWebGPU: ParsableCommand { 6 | static var configuration = CommandConfiguration(commandName: "generate-webgpu") 7 | 8 | @Option(help: "Path to dawn.json", transform: URL.init(fileURLWithPath:)) 9 | var dawnJson: URL 10 | 11 | @Option(help: "Path to output directory", transform: URL.init(fileURLWithPath:)) 12 | var outputDir: URL 13 | 14 | mutating func run() throws { 15 | let jsonData = try Data(contentsOf: dawnJson) 16 | let dawnData = try DawnData(from: jsonData) 17 | let model = Model(data: dawnData) 18 | 19 | try FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) 20 | 21 | try writeSource(generateEnums(model: model), toFileNamed: "Enums.swift") 22 | try writeSource(generateOptionSets(model: model), toFileNamed: "OptionSets.swift") 23 | try writeSource(generateStructs(model: model), toFileNamed: "Structs.swift") 24 | try writeSource(generateClasses(model: model), toFileNamed: "Classes.swift") 25 | try writeSource(generateFunctionTypes(model: model), toFileNamed: "FunctionTypes.swift") 26 | try writeSource(generateFunctions(model: model), toFileNamed: "Functions.swift") 27 | try writeSource(generateCallbacks(model: model), toFileNamed: "Callbacks.swift") 28 | try writeSource(generateCallbackInfo(model: model), toFileNamed: "CallbackInfo.swift") 29 | } 30 | 31 | func writeSource(_ source: String, toFileNamed fileName: String) throws { 32 | try source.write(to: outputDir.appendingPathComponent(fileName), atomically: true, encoding: .utf8) 33 | print("Generated source file \(fileName)") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/BitmaskType.swift: -------------------------------------------------------------------------------- 1 | class BitmaskType: EnumType { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/CallbackFunctionType.swift: -------------------------------------------------------------------------------- 1 | class CallbackFunctionType: Type { 2 | let arguments: Record 3 | 4 | init(name: String, data: CallbackFunctionTypeData) { 5 | arguments = Record(data: data.args.filter { $0.isEnabled }, context: .function) 6 | super.init(name: name, data: data) 7 | } 8 | 9 | override func link(model: Model) { 10 | arguments.link(model: model) 11 | } 12 | 13 | var callbackFunctionName: String { 14 | return name.camelCased() 15 | } 16 | 17 | var isRequestCallback: Bool { 18 | if let statusArg = arguments.first, 19 | statusArg.name == "status", 20 | (statusArg.type as? EnumType)?.isStatus == true { 21 | if arguments.count < 3 { 22 | return true 23 | } else if arguments.count == 3 { 24 | return arguments[2].isString && arguments[2].name == "message" 25 | } 26 | } 27 | return false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/CallbackInfoType.swift: -------------------------------------------------------------------------------- 1 | class CallbackInfoType: Type { 2 | let members: Record 3 | 4 | init(name: String, data: CallbackInfoTypeData) { 5 | var membersData = data.members.filter { !$0.isUpstream } 6 | 7 | if let modeIndex = membersData.firstIndex(where: { $0.name == "mode" }) { 8 | membersData[modeIndex].default = "allow spontaneous" 9 | } 10 | 11 | members = Record(data: membersData, context: .structure) 12 | super.init(name: name, data: data) 13 | } 14 | 15 | override func link(model: Model) { 16 | members.link(model: model) 17 | } 18 | 19 | var modeMember: RecordMember? { 20 | return members.first { $0.name == "mode" } 21 | } 22 | 23 | var callbackMember: RecordMember { 24 | return members.first { $0.name == "callback" }! 25 | } 26 | 27 | var hasDefaultSwiftInitializer: Bool { 28 | return members.removingHidden.allSatisfy { $0.defaultSwiftValue != nil } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/EnumType.swift: -------------------------------------------------------------------------------- 1 | struct EnumValue: Taggable { 2 | let name: String 3 | let swiftName: String 4 | let tags: Set 5 | let value: Int 6 | 7 | init(data: EnumValueData, requiresPrefix: Bool) { 8 | name = data.name 9 | swiftName = (requiresPrefix ? "type " + name : name).camelCased() 10 | 11 | tags = data.tags 12 | 13 | let prefix = tags.contains("native") ? 0x0001_0000 : 0 14 | value = prefix + data.value 15 | } 16 | } 17 | 18 | 19 | class EnumType: Type { 20 | let requiresPrefix: Bool 21 | let values: [EnumValue] 22 | 23 | init(name: String, data: EnumTypeData) { 24 | let values = data.values.filter { $0.isEnabled } 25 | 26 | let requiresPrefix = values.contains { $0.name.first!.isNumber } 27 | self.requiresPrefix = requiresPrefix 28 | 29 | self.values = values.map { EnumValue(data: $0, requiresPrefix: requiresPrefix) } 30 | super.init(name: name, data: data) 31 | } 32 | 33 | override func swiftValue(from value: Any) -> String { 34 | if let value = value as? String { 35 | let name = requiresPrefix ? "type " + value : value 36 | return "." + name.camelCased() 37 | } 38 | return super.swiftValue(from: value) 39 | } 40 | 41 | var isStatus: Bool { 42 | return values.contains { $0.name == "success" } 43 | } 44 | 45 | var hasUndefinedValue: Bool { 46 | return values.contains { $0.name == "undefined" } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/FunctionPointerType.swift: -------------------------------------------------------------------------------- 1 | class FunctionPointerType: Type { 2 | let returnTypeName: String? 3 | let arguments: Record 4 | 5 | weak var returnType: Type? 6 | 7 | init(name: String, data: FunctionTypeData) { 8 | returnTypeName = data.returns 9 | arguments = Record(data: data.args.filter { $0.isEnabled }, context: .function) 10 | super.init(name: name, data: data) 11 | } 12 | 13 | override func link(model: Model) { 14 | if let returnTypeName = returnTypeName, returnTypeName != "void" { 15 | returnType = model.type(named: returnTypeName) 16 | } 17 | arguments.link(model: model) 18 | } 19 | 20 | var isGetter: Bool { 21 | return returnType != nil && arguments.isEmpty && name.hasPrefix("get ") 22 | } 23 | 24 | var isExtensibleGetter: Bool { 25 | // TODO: Return type may be bool 26 | return arguments.count == 1 && arguments[0].annotation == .mutablePointer && (arguments[0].type as? StructureType)?.extensible == .out && name.hasPrefix("get ") 27 | } 28 | 29 | var isEnumerator: Bool { 30 | return returnTypeName == "size_t" && arguments.count == 1 && arguments[0].annotation == .mutablePointer && name.hasPrefix("enumerate ") 31 | } 32 | 33 | var isRequest: Bool { 34 | if returnType?.name == "future", 35 | let callbackInfo = arguments.last?.type as? CallbackInfoType, 36 | (callbackInfo.callbackMember.type as! CallbackFunctionType).isRequestCallback { 37 | return true 38 | } 39 | return false 40 | } 41 | 42 | var swiftReturnType: String? { 43 | if isExtensibleGetter { 44 | return arguments[0].type.swiftName 45 | } 46 | 47 | if isEnumerator { 48 | return "[\(arguments[0].type.swiftName)]" 49 | } 50 | 51 | guard let returnType = returnType else { return nil } 52 | if returnType.category == .functionPointer { 53 | return returnType.swiftName + "?" 54 | } 55 | return returnType.swiftName 56 | } 57 | 58 | var returnConversion: TypeConversion? { 59 | if isExtensibleGetter { 60 | return .value 61 | } 62 | 63 | if isEnumerator { 64 | switch arguments[0].type.category { 65 | case .enum, .bitmask, .structure, .object: 66 | return .array 67 | default: 68 | return .nativeArray 69 | } 70 | } 71 | 72 | guard let returnType = returnType else { return nil } 73 | switch returnType.category { 74 | case .enum, .bitmask, .structure, .object: 75 | return .value 76 | default: 77 | return returnType.name == "bool" ? .value : .native 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/FunctionType.swift: -------------------------------------------------------------------------------- 1 | class FunctionType: FunctionPointerType { 2 | var cFunctionName: String { 3 | return "wgpu" + name.pascalCased(preservingCasing: true) 4 | } 5 | 6 | var swiftFunctionName: String { 7 | if isGetter || isExtensibleGetter || isEnumerator { 8 | return name.split(separator: " ").dropFirst().joined(separator: " ").camelCased() 9 | } else { 10 | return name.camelCased() 11 | } 12 | } 13 | 14 | var hideFirstArgumentLabel: Bool { 15 | guard let firstArgument = arguments.removingHidden.first else { return false } 16 | return name.hasSuffix(" " + firstArgument.name) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/Model.swift: -------------------------------------------------------------------------------- 1 | struct Model { 2 | let types: [String: Type] 3 | let constants: [String: ConstantTypeData] 4 | 5 | init(data: DawnData) { 6 | var types = [String: Type]() 7 | var constants = [String: ConstantTypeData]() 8 | 9 | for (name, data) in data.types { 10 | guard data.isEnabled else { continue } 11 | 12 | if data.category == .native, let data = data as? NativeTypeData { 13 | types[name] = NativeType(name: name, data: data) 14 | } else if data.category == .enum, let data = data as? EnumTypeData { 15 | types[name] = EnumType(name: name, data: data) 16 | } else if data.category == .bitmask, let data = data as? EnumTypeData { 17 | types[name] = BitmaskType(name: name, data: data) 18 | } else if data.category == .structure, let data = data as? StructureTypeData { 19 | types[name] = StructureType(name: name, data: data) 20 | } else if data.category == .object, let data = data as? ObjectTypeData { 21 | types[name] = ObjectType(name: name, data: data) 22 | } else if data.category == .functionPointer, let data = data as? FunctionTypeData { 23 | types[name] = FunctionPointerType(name: name, data: data) 24 | } else if data.category == .function, let data = data as? FunctionTypeData { 25 | types[name] = FunctionType(name: name, data: data) 26 | } else if data.category == .callbackFunction, let data = data as? CallbackFunctionTypeData { 27 | types[name] = CallbackFunctionType(name: name, data: data) 28 | } else if data.category == .callbackInfo, let data = data as? CallbackInfoTypeData { 29 | types[name] = CallbackInfoType(name: name, data: data) 30 | } else if data.category == .constant, let data = data as? ConstantTypeData { 31 | constants[name] = data 32 | } else { 33 | types[name] = NonconvertibleType(name: name, data: data) 34 | } 35 | } 36 | 37 | self.types = types 38 | self.constants = constants 39 | 40 | for type in types.values { 41 | type.link(model: self) 42 | } 43 | } 44 | 45 | func types(of _: T.Type) -> [T] { 46 | return types.values.compactMap { Swift.type(of: $0) == T.self ? ($0 as! T) : nil }.sorted { $0.name < $1.name } 47 | } 48 | 49 | func type(named name: String) -> Type { 50 | guard let type = types[name] else { fatalError("Unknown type '\(name)'") } 51 | return type 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/NativeType.swift: -------------------------------------------------------------------------------- 1 | class NativeType: Type { 2 | init(name: String, data: NativeTypeData) { 3 | super.init(name: name, data: data) 4 | } 5 | 6 | private var constants: [String: ConstantTypeData] = [:] 7 | 8 | override func link(model: Model) { 9 | self.constants = model.constants 10 | } 11 | 12 | override var cName: String { 13 | return [ 14 | "void": "Void", 15 | "void *": "UnsafeMutableRawPointer!", 16 | "void const *": "UnsafeRawPointer!", 17 | "char": "CChar", 18 | "float": "Float", 19 | "double": "Double", 20 | "uint8_t": "UInt8", 21 | "uint16_t": "UInt16", 22 | "uint32_t": "UInt32", 23 | "uint64_t": "UInt64", 24 | "int32_t": "Int32", 25 | "int64_t": "Int64", 26 | "size_t": "Int", 27 | "int": "Int32", 28 | "bool": "WGPUBool" 29 | ][name] ?? name 30 | } 31 | 32 | override var swiftName: String { 33 | if name == "bool" { 34 | // Special case for 'bool' because it has a typedef for compatibility. 35 | return "Bool" 36 | } 37 | return cName 38 | } 39 | 40 | override func swiftValue(from value: Any) -> String { 41 | if let value = value as? String { 42 | if let constant = constants[value] { 43 | let constantName = "WGPU_" + value.snakeCased(uppercased: true) 44 | if name == "size_t" { 45 | return "Int(bitPattern: UInt(\(constantName)))" 46 | } else if constant.value == "NAN" { 47 | return ".nan" 48 | } else { 49 | return "\(swiftName)(\(constantName))" 50 | } 51 | } 52 | 53 | if value == "NAN" { 54 | return ".nan" 55 | } 56 | 57 | if name == "float" && value.hasSuffix("f") { 58 | return String(value.dropLast()) 59 | } 60 | } 61 | return super.swiftValue(from: value) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/ObjectType.swift: -------------------------------------------------------------------------------- 1 | class Method: FunctionType { 2 | let objectName: String 3 | 4 | init(data: MethodData, objectName: String) { 5 | self.objectName = objectName 6 | super.init(name: data.name, data: FunctionTypeData(category: .function, tags: data.tags, returns: data.returns, args: data.args)) 7 | } 8 | 9 | override var cFunctionName: String { 10 | return "wgpu" + objectName.pascalCased(preservingCasing: true) + name.pascalCased(preservingCasing: true) 11 | } 12 | 13 | var isEnabled: Bool { 14 | // TODO: Temporary patch for dawn bug 15 | if objectName == "surface" && name == "set label" { 16 | return false 17 | } 18 | return super.isEnabled 19 | } 20 | } 21 | 22 | class ObjectType: Type { 23 | let methods: [Method] 24 | 25 | init(name: String, data: ObjectTypeData) { 26 | methods = data.methods.map { Method(data: $0, objectName: name) }.filter { $0.isEnabled } 27 | super.init(name: name, data: data) 28 | } 29 | 30 | override func link(model: Model) { 31 | for method in methods { 32 | method.link(model: model) 33 | } 34 | } 35 | 36 | var referenceFunctionName: String { 37 | return "wgpu\(name.pascalCased(preservingCasing: true))Reference" 38 | } 39 | 40 | var releaseFunctionName: String { 41 | return "wgpu\(name.pascalCased(preservingCasing: true))Release" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/Record.swift: -------------------------------------------------------------------------------- 1 | enum TypeConversion { 2 | case native 3 | case value 4 | case valueWithClosure 5 | case pointerWithClosure 6 | case array 7 | case nativeArray 8 | case length 9 | case callback 10 | } 11 | 12 | enum RecordContext { 13 | case structure 14 | case function 15 | } 16 | 17 | class RecordMember { 18 | let name: String 19 | let typeName: String 20 | let annotation: Annotation? 21 | let length: Length 22 | let defaultValue: String? 23 | private let _isOptional: Bool 24 | 25 | let context: RecordContext 26 | 27 | weak var type: Type! 28 | 29 | weak var lengthMember: RecordMember? 30 | weak var parentMember: RecordMember? 31 | 32 | init(data: RecordMemberData, context: RecordContext) { 33 | name = data.name 34 | typeName = data.type 35 | annotation = data.annotation 36 | length = data.length 37 | defaultValue = data.default 38 | _isOptional = data.optional 39 | self.context = context 40 | } 41 | 42 | func link(model: Model) { 43 | type = model.type(named: typeName) 44 | } 45 | 46 | var cName: String { 47 | return name.camelCased(preservingCasing: true) 48 | } 49 | 50 | var swiftName: String { 51 | return name.camelCased() 52 | } 53 | 54 | var isOptional: Bool { 55 | return _isOptional || defaultValue == "nullptr" || typeName == "optional bool" 56 | } 57 | 58 | var isVoidPointer: Bool { 59 | return (typeName == "void" && annotation == .pointer) || typeName == "void const *" 60 | } 61 | 62 | var isMutableVoidPointer: Bool { 63 | return (typeName == "void" && annotation == .mutablePointer) || typeName == "void *" 64 | } 65 | 66 | var isBool: Bool { 67 | return typeName == "bool" || typeName == "optional bool" 68 | } 69 | 70 | var isString: Bool { 71 | return annotation == .none && typeName == "string view" 72 | } 73 | 74 | var isArray: Bool { 75 | return (annotation == .pointer || isVoidPointer) && length != .single 76 | } 77 | 78 | var isHidden: Bool { 79 | return parentMember?.isArray ?? false 80 | } 81 | 82 | var unwrappedCType: String { 83 | if isVoidPointer { 84 | return "UnsafeRawPointer" 85 | } 86 | 87 | if isMutableVoidPointer { 88 | return "UnsafeMutableRawPointer" 89 | } 90 | 91 | if let annotation = annotation { 92 | var innerType = type.cName 93 | if type.category == .object { 94 | innerType += "?" 95 | } 96 | 97 | switch annotation { 98 | case .pointer: 99 | return "UnsafePointer<\(innerType)>" 100 | case .mutablePointer: 101 | return "UnsafeMutablePointer<\(innerType)>" 102 | case .pointerToPointer: 103 | return "UnsafePointer?>" 104 | } 105 | } 106 | 107 | return type.cName 108 | } 109 | 110 | var cType: String { 111 | if annotation != nil || isVoidPointer || isMutableVoidPointer || type.category == .object || type.category == .functionPointer { 112 | return unwrappedCType + "!" 113 | } 114 | return unwrappedCType 115 | } 116 | 117 | var unwrappedSwiftType: String { 118 | if isBool { 119 | return "Bool" 120 | 121 | } else if isString { 122 | return "String" 123 | 124 | } else if isArray { 125 | return isVoidPointer ? "UnsafeRawBufferPointer" : "[\(type.swiftName)]" 126 | 127 | } else if annotation == .pointer && type.category == .structure { 128 | return type.swiftName 129 | 130 | } else if type.category == .functionPointer { 131 | return unwrappedCType 132 | 133 | } else if annotation == nil && !isVoidPointer && !isMutableVoidPointer { 134 | return type.swiftName 135 | 136 | } else { 137 | return unwrappedCType 138 | } 139 | } 140 | 141 | var swiftType: String { 142 | if isOptional && !(isArray && !isVoidPointer) { 143 | return unwrappedSwiftType + "?" 144 | } 145 | return unwrappedSwiftType 146 | } 147 | 148 | var typeConversion: TypeConversion { 149 | if isString { 150 | return .valueWithClosure 151 | } 152 | 153 | if isArray { 154 | return type.category == .native && typeName != "bool" ? .nativeArray : .array 155 | } 156 | 157 | if parentMember?.isArray == true { 158 | return .length 159 | } 160 | 161 | if type.category == .structure && annotation == .pointer { 162 | return .pointerWithClosure 163 | } 164 | 165 | if type.category == .structure && annotation == nil { 166 | return .valueWithClosure 167 | } 168 | 169 | if type.category == .object && annotation == nil { 170 | return .valueWithClosure 171 | } 172 | 173 | if annotation == nil && (type.category == .enum || type.category == .bitmask) { 174 | return .value 175 | } 176 | 177 | if annotation == nil && isBool { 178 | return .value 179 | } 180 | 181 | if type.category == .callbackFunction { 182 | return .callback 183 | } 184 | 185 | if type.category == .callbackInfo { 186 | return .valueWithClosure 187 | } 188 | 189 | return .native 190 | } 191 | 192 | var defaultSwiftValue: String? { 193 | if isBool && defaultValue == "undefined" { 194 | return "nil" 195 | } 196 | 197 | if let defaultValue = defaultValue, defaultValue != "nullptr" { 198 | return type?.swiftValue(from: defaultValue) 199 | } 200 | 201 | if isArray && (isOptional || lengthMember?.defaultValue == "0") { 202 | return isVoidPointer ? "nil" : "[]" 203 | } 204 | 205 | if isOptional { 206 | return "nil" 207 | } 208 | 209 | if annotation == .none, let type = type as? EnumType, type.hasUndefinedValue { 210 | return type.swiftValue(from: "undefined") 211 | } 212 | 213 | if annotation == .none, !isString, let type = type as? StructureType, type.hasDefaultSwiftInitializer { 214 | return "\(type.swiftName)()" 215 | } 216 | 217 | if let type = type as? CallbackInfoType, type.hasDefaultSwiftInitializer { 218 | return "\(type.swiftName)()" 219 | } 220 | 221 | return nil 222 | } 223 | } 224 | 225 | 226 | typealias Record = [RecordMember] 227 | 228 | extension Record { 229 | init(data: RecordData, context: RecordContext) { 230 | self = data.map { RecordMember(data: $0, context: context) } 231 | 232 | for member in self { 233 | if case .member(let length) = member.length { 234 | if let lengthMember = self.first(where: { $0.name == length }) { 235 | member.lengthMember = lengthMember 236 | lengthMember.parentMember = member 237 | } 238 | } 239 | } 240 | } 241 | 242 | func link(model: Model) { 243 | for member in self { 244 | member.link(model: model) 245 | } 246 | } 247 | 248 | var removingHidden: Record { 249 | return filter { !$0.isHidden } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/StringUtils.swift: -------------------------------------------------------------------------------- 1 | extension String { 2 | func camelCased(preservingCasing: Bool = false) -> String { 3 | let string = preservingCasing ? self : self.lowercased() 4 | let words = string.split(separator: " ") 5 | let transformedWords = words.prefix(1) + words.dropFirst().map { $0.first!.uppercased() + $0.dropFirst() } 6 | return transformedWords.joined() 7 | } 8 | 9 | func pascalCased(preservingCasing: Bool = false) -> String { 10 | let string = preservingCasing ? self : self.lowercased() 11 | let words = string.split(separator: " ") 12 | let transformedWords = words.map { $0.first!.uppercased() + $0.dropFirst() } 13 | return transformedWords.joined() 14 | } 15 | 16 | func snakeCased(uppercased: Bool = false) -> String { 17 | let string = uppercased ? self.uppercased() : self.lowercased() 18 | let words = string.split(separator: " ") 19 | return words.joined(separator: "_") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/StructureType.swift: -------------------------------------------------------------------------------- 1 | class StructureType: Type { 2 | let extensible: Extensibility 3 | let chained: Extensibility 4 | let members: Record 5 | 6 | init(name: String, data: StructureTypeData) { 7 | extensible = data.extensible 8 | chained = data.chained 9 | members = Record(data: data.members.filter { !$0.isUpstream }, context: .structure) 10 | super.init(name: name, data: data) 11 | } 12 | 13 | override func link(model: Model) { 14 | members.link(model: model) 15 | } 16 | 17 | var sType: String { 18 | return "WGPUSType_" + name.pascalCased(preservingCasing: true) 19 | } 20 | 21 | var hasDefaultSwiftInitializer: Bool { 22 | return members.removingHidden.allSatisfy { $0.defaultSwiftValue != nil } 23 | } 24 | 25 | override func swiftValue(from value: Any) -> String { 26 | if let value = value as? String { 27 | if value == "zero" { 28 | return ".zero" 29 | } 30 | 31 | let values = value.trimmingCharacters(in: .init(charactersIn: "{}")).split(separator: ",") 32 | let params = zip(members, values).map { (member, value) -> String in 33 | let value = value.trimmingCharacters(in: .whitespaces) 34 | return "\(member.swiftName): \(member.type?.swiftValue(from: value) ?? value)" 35 | } 36 | return ".init(\(params.joined(separator: ", ")))" 37 | } 38 | return super.swiftValue(from: value) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/generate-webgpu/Model/Type.swift: -------------------------------------------------------------------------------- 1 | class Type: Taggable { 2 | let name: String 3 | let category: Category 4 | let tags: Set 5 | 6 | init(name: String, data: TypeData) { 7 | self.name = name 8 | self.category = data.category 9 | self.tags = data.tags 10 | } 11 | 12 | func link(model: Model) {} 13 | 14 | var cName: String { 15 | return "WGPU" + name.pascalCased(preservingCasing: true) 16 | } 17 | 18 | var swiftName: String { 19 | return name.pascalCased() 20 | } 21 | 22 | func swiftValue(from value: Any) -> String { 23 | return String(describing: value) 24 | } 25 | } 26 | 27 | 28 | class NonconvertibleType: Type { 29 | override var swiftName: String { 30 | return cName 31 | } 32 | } 33 | --------------------------------------------------------------------------------