This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
650 lines
20 KiB
Markdown
650 lines
20 KiB
Markdown
---
|
|
name: axiom-metal-migration-ref
|
|
description: Use when converting shaders or looking up API equivalents - GLSL to MSL, HLSL to MSL, GL/DirectX to Metal mappings, MTKView setup code
|
|
license: MIT
|
|
compatibility: [iOS 12+, macOS 10.14+, tvOS 12+]
|
|
metadata:
|
|
version: "1.0.0"
|
|
---
|
|
|
|
# Metal Migration Reference
|
|
|
|
Complete reference for converting OpenGL/DirectX code to Metal.
|
|
|
|
## When to Use This Reference
|
|
|
|
Use this reference when:
|
|
- Converting GLSL shaders to Metal Shading Language (MSL)
|
|
- Converting HLSL shaders to MSL
|
|
- Looking up GL/D3D API equivalents in Metal
|
|
- Setting up MTKView or CAMetalLayer
|
|
- Building render pipelines
|
|
- Using Metal Shader Converter for DirectX
|
|
|
|
## Part 1: GLSL to MSL Conversion
|
|
|
|
### Type Mappings
|
|
|
|
| GLSL | MSL | Notes |
|
|
|------|-----|-------|
|
|
| `void` | `void` | |
|
|
| `bool` | `bool` | |
|
|
| `int` | `int` | 32-bit signed |
|
|
| `uint` | `uint` | 32-bit unsigned |
|
|
| `float` | `float` | 32-bit |
|
|
| `double` | N/A | Use `float` (no 64-bit float in MSL) |
|
|
| `vec2` | `float2` | |
|
|
| `vec3` | `float3` | |
|
|
| `vec4` | `float4` | |
|
|
| `ivec2` | `int2` | |
|
|
| `ivec3` | `int3` | |
|
|
| `ivec4` | `int4` | |
|
|
| `uvec2` | `uint2` | |
|
|
| `uvec3` | `uint3` | |
|
|
| `uvec4` | `uint4` | |
|
|
| `bvec2` | `bool2` | |
|
|
| `bvec3` | `bool3` | |
|
|
| `bvec4` | `bool4` | |
|
|
| `mat2` | `float2x2` | |
|
|
| `mat3` | `float3x3` | |
|
|
| `mat4` | `float4x4` | |
|
|
| `mat2x3` | `float2x3` | Columns x Rows |
|
|
| `mat3x4` | `float3x4` | |
|
|
| `sampler2D` | `texture2d<float>` + `sampler` | Separate in MSL |
|
|
| `sampler3D` | `texture3d<float>` + `sampler` | |
|
|
| `samplerCube` | `texturecube<float>` + `sampler` | |
|
|
| `sampler2DArray` | `texture2d_array<float>` + `sampler` | |
|
|
| `sampler2DShadow` | `depth2d<float>` + `sampler` | |
|
|
|
|
### Built-in Variable Mappings
|
|
|
|
| GLSL | MSL | Stage |
|
|
|------|-----|-------|
|
|
| `gl_Position` | Return `[[position]]` | Vertex |
|
|
| `gl_PointSize` | Return `[[point_size]]` | Vertex |
|
|
| `gl_VertexID` | `[[vertex_id]]` parameter | Vertex |
|
|
| `gl_InstanceID` | `[[instance_id]]` parameter | Vertex |
|
|
| `gl_FragCoord` | `[[position]]` parameter | Fragment |
|
|
| `gl_FrontFacing` | `[[front_facing]]` parameter | Fragment |
|
|
| `gl_PointCoord` | `[[point_coord]]` parameter | Fragment |
|
|
| `gl_FragDepth` | Return `[[depth(any)]]` | Fragment |
|
|
| `gl_SampleID` | `[[sample_id]]` parameter | Fragment |
|
|
| `gl_SamplePosition` | `[[sample_position]]` parameter | Fragment |
|
|
|
|
### Function Mappings
|
|
|
|
| GLSL | MSL | Notes |
|
|
|------|-----|-------|
|
|
| `texture(sampler, uv)` | `tex.sample(sampler, uv)` | Method on texture |
|
|
| `textureLod(sampler, uv, lod)` | `tex.sample(sampler, uv, level(lod))` | |
|
|
| `textureGrad(sampler, uv, ddx, ddy)` | `tex.sample(sampler, uv, gradient2d(ddx, ddy))` | |
|
|
| `texelFetch(sampler, coord, lod)` | `tex.read(coord, lod)` | Integer coords |
|
|
| `textureSize(sampler, lod)` | `tex.get_width(lod)`, `tex.get_height(lod)` | Separate calls |
|
|
| `dFdx(v)` | `dfdx(v)` | |
|
|
| `dFdy(v)` | `dfdy(v)` | |
|
|
| `fwidth(v)` | `fwidth(v)` | Same |
|
|
| `mix(a, b, t)` | `mix(a, b, t)` | Same |
|
|
| `clamp(v, lo, hi)` | `clamp(v, lo, hi)` | Same |
|
|
| `smoothstep(e0, e1, x)` | `smoothstep(e0, e1, x)` | Same |
|
|
| `step(edge, x)` | `step(edge, x)` | Same |
|
|
| `mod(x, y)` | `fmod(x, y)` | Different name |
|
|
| `fract(x)` | `fract(x)` | Same |
|
|
| `inversesqrt(x)` | `rsqrt(x)` | Different name |
|
|
| `atan(y, x)` | `atan2(y, x)` | Different name |
|
|
|
|
### Shader Structure Conversion
|
|
|
|
**GLSL Vertex Shader**:
|
|
```glsl
|
|
#version 300 es
|
|
precision highp float;
|
|
|
|
layout(location = 0) in vec3 aPosition;
|
|
layout(location = 1) in vec2 aTexCoord;
|
|
|
|
uniform mat4 uModelViewProjection;
|
|
|
|
out vec2 vTexCoord;
|
|
|
|
void main() {
|
|
gl_Position = uModelViewProjection * vec4(aPosition, 1.0);
|
|
vTexCoord = aTexCoord;
|
|
}
|
|
```
|
|
|
|
**MSL Vertex Shader**:
|
|
```metal
|
|
#include <metal_stdlib>
|
|
using namespace metal;
|
|
|
|
struct VertexIn {
|
|
float3 position [[attribute(0)]];
|
|
float2 texCoord [[attribute(1)]];
|
|
};
|
|
|
|
struct VertexOut {
|
|
float4 position [[position]];
|
|
float2 texCoord;
|
|
};
|
|
|
|
struct Uniforms {
|
|
float4x4 modelViewProjection;
|
|
};
|
|
|
|
vertex VertexOut vertexShader(
|
|
VertexIn in [[stage_in]],
|
|
constant Uniforms& uniforms [[buffer(1)]]
|
|
) {
|
|
VertexOut out;
|
|
out.position = uniforms.modelViewProjection * float4(in.position, 1.0);
|
|
out.texCoord = in.texCoord;
|
|
return out;
|
|
}
|
|
```
|
|
|
|
**GLSL Fragment Shader**:
|
|
```glsl
|
|
#version 300 es
|
|
precision highp float;
|
|
|
|
in vec2 vTexCoord;
|
|
uniform sampler2D uTexture;
|
|
|
|
out vec4 fragColor;
|
|
|
|
void main() {
|
|
fragColor = texture(uTexture, vTexCoord);
|
|
}
|
|
```
|
|
|
|
**MSL Fragment Shader**:
|
|
```metal
|
|
fragment float4 fragmentShader(
|
|
VertexOut in [[stage_in]],
|
|
texture2d<float> tex [[texture(0)]],
|
|
sampler samp [[sampler(0)]]
|
|
) {
|
|
return tex.sample(samp, in.texCoord);
|
|
}
|
|
```
|
|
|
|
### Precision Qualifiers
|
|
|
|
GLSL precision qualifiers have no direct MSL equivalent — MSL uses explicit types:
|
|
|
|
| GLSL | MSL Equivalent |
|
|
|------|----------------|
|
|
| `lowp float` | `half` (16-bit) |
|
|
| `mediump float` | `half` (16-bit) |
|
|
| `highp float` | `float` (32-bit) |
|
|
| `lowp int` | `short` (16-bit) |
|
|
| `mediump int` | `short` (16-bit) |
|
|
| `highp int` | `int` (32-bit) |
|
|
|
|
### Buffer Alignment (Critical)
|
|
|
|
**GLSL/C assumes**:
|
|
- `vec3`: 12 bytes, any alignment
|
|
- `vec4`: 16 bytes
|
|
|
|
**MSL requires**:
|
|
- `float3`: 12 bytes storage, **16-byte aligned**
|
|
- `float4`: 16 bytes storage, 16-byte aligned
|
|
|
|
**Solution**: Use `simd` types in Swift for CPU-GPU shared structs:
|
|
|
|
```swift
|
|
import simd
|
|
|
|
struct Uniforms {
|
|
var modelViewProjection: simd_float4x4 // Correct alignment
|
|
var cameraPosition: simd_float3 // 16-byte aligned
|
|
var padding: Float = 0 // Explicit padding if needed
|
|
}
|
|
```
|
|
|
|
Or use packed types in MSL (slower):
|
|
```metal
|
|
struct VertexPacked {
|
|
packed_float3 position; // 12 bytes, no padding
|
|
packed_float2 texCoord; // 8 bytes
|
|
};
|
|
```
|
|
|
|
## Part 2: HLSL to MSL Conversion
|
|
|
|
### Type Mappings
|
|
|
|
| HLSL | MSL | Notes |
|
|
|------|-----|-------|
|
|
| `float` | `float` | |
|
|
| `float2` | `float2` | |
|
|
| `float3` | `float3` | |
|
|
| `float4` | `float4` | |
|
|
| `half` | `half` | |
|
|
| `int` | `int` | |
|
|
| `uint` | `uint` | |
|
|
| `bool` | `bool` | |
|
|
| `float2x2` | `float2x2` | |
|
|
| `float3x3` | `float3x3` | |
|
|
| `float4x4` | `float4x4` | |
|
|
| `Texture2D` | `texture2d<float>` | |
|
|
| `Texture3D` | `texture3d<float>` | |
|
|
| `TextureCube` | `texturecube<float>` | |
|
|
| `SamplerState` | `sampler` | |
|
|
| `RWTexture2D` | `texture2d<float, access::read_write>` | |
|
|
| `RWBuffer` | `device float* [[buffer(n)]]` | |
|
|
| `StructuredBuffer` | `constant T* [[buffer(n)]]` | |
|
|
| `RWStructuredBuffer` | `device T* [[buffer(n)]]` | |
|
|
|
|
### Semantic Mappings
|
|
|
|
| HLSL Semantic | MSL Attribute |
|
|
|---------------|---------------|
|
|
| `SV_Position` | `[[position]]` |
|
|
| `SV_Target0` | Return value / `[[color(0)]]` |
|
|
| `SV_Target1` | `[[color(1)]]` |
|
|
| `SV_Depth` | `[[depth(any)]]` |
|
|
| `SV_VertexID` | `[[vertex_id]]` |
|
|
| `SV_InstanceID` | `[[instance_id]]` |
|
|
| `SV_IsFrontFace` | `[[front_facing]]` |
|
|
| `SV_SampleIndex` | `[[sample_id]]` |
|
|
| `SV_PrimitiveID` | `[[primitive_id]]` |
|
|
| `SV_DispatchThreadID` | `[[thread_position_in_grid]]` |
|
|
| `SV_GroupThreadID` | `[[thread_position_in_threadgroup]]` |
|
|
| `SV_GroupID` | `[[threadgroup_position_in_grid]]` |
|
|
| `SV_GroupIndex` | `[[thread_index_in_threadgroup]]` |
|
|
|
|
### Function Mappings
|
|
|
|
| HLSL | MSL | Notes |
|
|
|------|-----|-------|
|
|
| `tex.Sample(samp, uv)` | `tex.sample(samp, uv)` | Lowercase |
|
|
| `tex.SampleLevel(samp, uv, lod)` | `tex.sample(samp, uv, level(lod))` | |
|
|
| `tex.SampleGrad(samp, uv, ddx, ddy)` | `tex.sample(samp, uv, gradient2d(ddx, ddy))` | |
|
|
| `tex.Load(coord)` | `tex.read(coord.xy, coord.z)` | Split coord |
|
|
| `mul(a, b)` | `a * b` | Operator |
|
|
| `saturate(x)` | `saturate(x)` | Same |
|
|
| `lerp(a, b, t)` | `mix(a, b, t)` | Different name |
|
|
| `frac(x)` | `fract(x)` | Different name |
|
|
| `ddx(v)` | `dfdx(v)` | Different name |
|
|
| `ddy(v)` | `dfdy(v)` | Different name |
|
|
| `clip(x)` | `if (x < 0) discard_fragment()` | Manual |
|
|
| `discard` | `discard_fragment()` | Function call |
|
|
|
|
### Metal Shader Converter (DirectX → Metal)
|
|
|
|
Apple's official tool for converting DXIL (compiled HLSL) to Metal libraries.
|
|
|
|
**Requirements**:
|
|
- macOS 13+ with Xcode 15+
|
|
- OR Windows 10+ with VS 2019+
|
|
- Target devices: Argument Buffers Tier 2 (macOS 14+, iOS 17+)
|
|
|
|
**Workflow**:
|
|
|
|
```bash
|
|
# Step 1: Compile HLSL to DXIL using DXC
|
|
dxc -T vs_6_0 -E MainVS -Fo vertex.dxil shader.hlsl
|
|
dxc -T ps_6_0 -E MainPS -Fo fragment.dxil shader.hlsl
|
|
|
|
# Step 2: Convert DXIL to Metal library
|
|
metal-shaderconverter vertex.dxil -o vertex.metallib
|
|
metal-shaderconverter fragment.dxil -o fragment.metallib
|
|
|
|
# Step 3: Load in Swift
|
|
let vertexLib = try device.makeLibrary(URL: vertexURL)
|
|
let fragmentLib = try device.makeLibrary(URL: fragmentURL)
|
|
```
|
|
|
|
**Key Options**:
|
|
|
|
| Option | Purpose |
|
|
|--------|---------|
|
|
| `-o <file>` | Output metallib path |
|
|
| `--minimum-gpu-family` | Target GPU family |
|
|
| `--minimum-os-build-version` | Minimum OS version |
|
|
| `--vertex-stage-in` | Separate vertex fetch function |
|
|
| `-dualSourceBlending` | Enable dual-source blending |
|
|
|
|
**Supported Shader Models**: SM 6.0 - 6.6 (with limitations on 6.6 features)
|
|
|
|
## Part 3: OpenGL API to Metal API
|
|
|
|
### View/Context Setup
|
|
|
|
| OpenGL | Metal |
|
|
|--------|-------|
|
|
| `NSOpenGLView` | `MTKView` |
|
|
| `GLKView` | `MTKView` |
|
|
| `EAGLContext` | `MTLDevice` + `MTLCommandQueue` |
|
|
| `CGLContextObj` | `MTLDevice` |
|
|
|
|
### Resource Creation
|
|
|
|
| OpenGL | Metal |
|
|
|--------|-------|
|
|
| `glGenBuffers` + `glBufferData` | `device.makeBuffer(bytes:length:options:)` |
|
|
| `glGenTextures` + `glTexImage2D` | `device.makeTexture(descriptor:)` + `texture.replace(region:...)` |
|
|
| `glGenFramebuffers` | `MTLRenderPassDescriptor` |
|
|
| `glGenVertexArrays` | `MTLVertexDescriptor` |
|
|
| `glCreateShader` + `glCompileShader` | Build-time compilation → `MTLLibrary` |
|
|
| `glCreateProgram` + `glLinkProgram` | `MTLRenderPipelineDescriptor` → `MTLRenderPipelineState` |
|
|
|
|
### State Management
|
|
|
|
| OpenGL | Metal |
|
|
|--------|-------|
|
|
| `glEnable(GL_DEPTH_TEST)` | `MTLDepthStencilDescriptor` → `MTLDepthStencilState` |
|
|
| `glDepthFunc(GL_LESS)` | `descriptor.depthCompareFunction = .less` |
|
|
| `glEnable(GL_BLEND)` | `pipelineDescriptor.colorAttachments[0].isBlendingEnabled = true` |
|
|
| `glBlendFunc` | `sourceRGBBlendFactor`, `destinationRGBBlendFactor` |
|
|
| `glCullFace` | `encoder.setCullMode(.back)` |
|
|
| `glFrontFace` | `encoder.setFrontFacing(.counterClockwise)` |
|
|
| `glViewport` | `encoder.setViewport(MTLViewport(...))` |
|
|
| `glScissor` | `encoder.setScissorRect(MTLScissorRect(...))` |
|
|
|
|
### Draw Commands
|
|
|
|
| OpenGL | Metal |
|
|
|--------|-------|
|
|
| `glDrawArrays(mode, first, count)` | `encoder.drawPrimitives(type:vertexStart:vertexCount:)` |
|
|
| `glDrawElements(mode, count, type, indices)` | `encoder.drawIndexedPrimitives(type:indexCount:indexType:indexBuffer:indexBufferOffset:)` |
|
|
| `glDrawArraysInstanced` | `encoder.drawPrimitives(type:vertexStart:vertexCount:instanceCount:)` |
|
|
| `glDrawElementsInstanced` | `encoder.drawIndexedPrimitives(...instanceCount:)` |
|
|
|
|
### Primitive Types
|
|
|
|
| OpenGL | Metal |
|
|
|--------|-------|
|
|
| `GL_POINTS` | `.point` |
|
|
| `GL_LINES` | `.line` |
|
|
| `GL_LINE_STRIP` | `.lineStrip` |
|
|
| `GL_TRIANGLES` | `.triangle` |
|
|
| `GL_TRIANGLE_STRIP` | `.triangleStrip` |
|
|
| `GL_TRIANGLE_FAN` | N/A (decompose to triangles) |
|
|
|
|
## Part 4: Complete Setup Examples
|
|
|
|
### MTKView Setup (Recommended)
|
|
|
|
```swift
|
|
import MetalKit
|
|
|
|
class GameViewController: UIViewController {
|
|
var metalView: MTKView!
|
|
var renderer: Renderer!
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
// Create Metal view
|
|
guard let device = MTLCreateSystemDefaultDevice() else {
|
|
fatalError("Metal not supported")
|
|
}
|
|
|
|
metalView = MTKView(frame: view.bounds, device: device)
|
|
metalView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
metalView.colorPixelFormat = .bgra8Unorm
|
|
metalView.depthStencilPixelFormat = .depth32Float
|
|
metalView.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 1)
|
|
metalView.preferredFramesPerSecond = 60
|
|
view.addSubview(metalView)
|
|
|
|
// Create renderer
|
|
renderer = Renderer(metalView: metalView)
|
|
metalView.delegate = renderer
|
|
}
|
|
}
|
|
|
|
class Renderer: NSObject, MTKViewDelegate {
|
|
let device: MTLDevice
|
|
let commandQueue: MTLCommandQueue
|
|
var pipelineState: MTLRenderPipelineState!
|
|
var depthState: MTLDepthStencilState!
|
|
var vertexBuffer: MTLBuffer!
|
|
|
|
init(metalView: MTKView) {
|
|
device = metalView.device!
|
|
commandQueue = device.makeCommandQueue()!
|
|
super.init()
|
|
|
|
buildPipeline(metalView: metalView)
|
|
buildDepthStencil()
|
|
buildBuffers()
|
|
}
|
|
|
|
private func buildPipeline(metalView: MTKView) {
|
|
let library = device.makeDefaultLibrary()!
|
|
|
|
let descriptor = MTLRenderPipelineDescriptor()
|
|
descriptor.vertexFunction = library.makeFunction(name: "vertexShader")
|
|
descriptor.fragmentFunction = library.makeFunction(name: "fragmentShader")
|
|
descriptor.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
|
|
descriptor.depthAttachmentPixelFormat = metalView.depthStencilPixelFormat
|
|
|
|
// Vertex descriptor (matches shader's VertexIn struct)
|
|
let vertexDescriptor = MTLVertexDescriptor()
|
|
vertexDescriptor.attributes[0].format = .float3
|
|
vertexDescriptor.attributes[0].offset = 0
|
|
vertexDescriptor.attributes[0].bufferIndex = 0
|
|
vertexDescriptor.attributes[1].format = .float2
|
|
vertexDescriptor.attributes[1].offset = MemoryLayout<SIMD3<Float>>.stride
|
|
vertexDescriptor.attributes[1].bufferIndex = 0
|
|
vertexDescriptor.layouts[0].stride = MemoryLayout<Vertex>.stride
|
|
descriptor.vertexDescriptor = vertexDescriptor
|
|
|
|
pipelineState = try! device.makeRenderPipelineState(descriptor: descriptor)
|
|
}
|
|
|
|
private func buildDepthStencil() {
|
|
let descriptor = MTLDepthStencilDescriptor()
|
|
descriptor.depthCompareFunction = .less
|
|
descriptor.isDepthWriteEnabled = true
|
|
depthState = device.makeDepthStencilState(descriptor: descriptor)
|
|
}
|
|
|
|
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
|
|
// Handle resize
|
|
}
|
|
|
|
func draw(in view: MTKView) {
|
|
guard let drawable = view.currentDrawable,
|
|
let descriptor = view.currentRenderPassDescriptor,
|
|
let commandBuffer = commandQueue.makeCommandBuffer(),
|
|
let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
|
|
return
|
|
}
|
|
|
|
encoder.setRenderPipelineState(pipelineState)
|
|
encoder.setDepthStencilState(depthState)
|
|
encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
|
encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertexCount)
|
|
encoder.endEncoding()
|
|
|
|
commandBuffer.present(drawable)
|
|
commandBuffer.commit()
|
|
}
|
|
}
|
|
```
|
|
|
|
### CAMetalLayer Setup (Custom Control)
|
|
|
|
```swift
|
|
import Metal
|
|
import QuartzCore
|
|
|
|
class MetalLayerView: UIView {
|
|
var metalLayer: CAMetalLayer!
|
|
var device: MTLDevice!
|
|
var commandQueue: MTLCommandQueue!
|
|
var displayLink: CADisplayLink?
|
|
|
|
override class var layerClass: AnyClass { CAMetalLayer.self }
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
setup()
|
|
}
|
|
|
|
private func setup() {
|
|
device = MTLCreateSystemDefaultDevice()!
|
|
commandQueue = device.makeCommandQueue()!
|
|
|
|
metalLayer = layer as? CAMetalLayer
|
|
metalLayer.device = device
|
|
metalLayer.pixelFormat = .bgra8Unorm
|
|
metalLayer.framebufferOnly = true
|
|
|
|
displayLink = CADisplayLink(target: self, selector: #selector(render))
|
|
displayLink?.add(to: .main, forMode: .common)
|
|
}
|
|
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
metalLayer.drawableSize = CGSize(
|
|
width: bounds.width * contentScaleFactor,
|
|
height: bounds.height * contentScaleFactor
|
|
)
|
|
}
|
|
|
|
@objc func render() {
|
|
guard let drawable = metalLayer.nextDrawable(),
|
|
let commandBuffer = commandQueue.makeCommandBuffer() else {
|
|
return
|
|
}
|
|
|
|
let descriptor = MTLRenderPassDescriptor()
|
|
descriptor.colorAttachments[0].texture = drawable.texture
|
|
descriptor.colorAttachments[0].loadAction = .clear
|
|
descriptor.colorAttachments[0].storeAction = .store
|
|
descriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 1)
|
|
|
|
guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else {
|
|
return
|
|
}
|
|
|
|
// Draw commands here
|
|
encoder.endEncoding()
|
|
|
|
commandBuffer.present(drawable)
|
|
commandBuffer.commit()
|
|
}
|
|
}
|
|
```
|
|
|
|
### Compute Shader Setup
|
|
|
|
```swift
|
|
class ComputeProcessor {
|
|
let device: MTLDevice
|
|
let commandQueue: MTLCommandQueue
|
|
var computePipeline: MTLComputePipelineState!
|
|
|
|
init() {
|
|
device = MTLCreateSystemDefaultDevice()!
|
|
commandQueue = device.makeCommandQueue()!
|
|
|
|
let library = device.makeDefaultLibrary()!
|
|
let function = library.makeFunction(name: "computeKernel")!
|
|
computePipeline = try! device.makeComputePipelineState(function: function)
|
|
}
|
|
|
|
func process(input: MTLBuffer, output: MTLBuffer, count: Int) {
|
|
let commandBuffer = commandQueue.makeCommandBuffer()!
|
|
let encoder = commandBuffer.makeComputeCommandEncoder()!
|
|
|
|
encoder.setComputePipelineState(computePipeline)
|
|
encoder.setBuffer(input, offset: 0, index: 0)
|
|
encoder.setBuffer(output, offset: 0, index: 1)
|
|
|
|
let threadGroupSize = MTLSize(width: 256, height: 1, depth: 1)
|
|
let threadGroups = MTLSize(
|
|
width: (count + 255) / 256,
|
|
height: 1,
|
|
depth: 1
|
|
)
|
|
|
|
encoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupSize)
|
|
encoder.endEncoding()
|
|
|
|
commandBuffer.commit()
|
|
commandBuffer.waitUntilCompleted()
|
|
}
|
|
}
|
|
```
|
|
|
|
```metal
|
|
// Compute shader
|
|
kernel void computeKernel(
|
|
device float* input [[buffer(0)]],
|
|
device float* output [[buffer(1)]],
|
|
uint id [[thread_position_in_grid]]
|
|
) {
|
|
output[id] = input[id] * 2.0;
|
|
}
|
|
```
|
|
|
|
## Part 5: Storage Modes & Synchronization
|
|
|
|
### Buffer Storage Modes
|
|
|
|
| Mode | CPU Access | GPU Access | Use Case |
|
|
|------|------------|------------|----------|
|
|
| `.shared` | Read/Write | Read/Write | Small dynamic data, uniforms |
|
|
| `.private` | None | Read/Write | Static assets, render targets |
|
|
| `.managed` (macOS) | Read/Write | Read/Write | Large buffers with partial updates |
|
|
|
|
```swift
|
|
// Shared: CPU and GPU both access (iOS typical)
|
|
let uniformBuffer = device.makeBuffer(length: size, options: .storageModeShared)
|
|
|
|
// Private: GPU only (best for static geometry)
|
|
let vertexBuffer = device.makeBuffer(bytes: vertices, length: size, options: .storageModePrivate)
|
|
|
|
// Managed: Explicit sync (macOS)
|
|
#if os(macOS)
|
|
let buffer = device.makeBuffer(length: size, options: .storageModeManaged)
|
|
// After CPU write:
|
|
buffer.didModifyRange(0..<size)
|
|
#endif
|
|
```
|
|
|
|
### Texture Storage Modes
|
|
|
|
```swift
|
|
let descriptor = MTLTextureDescriptor.texture2DDescriptor(
|
|
pixelFormat: .rgba8Unorm,
|
|
width: 1024,
|
|
height: 1024,
|
|
mipmapped: true
|
|
)
|
|
|
|
// For static textures (loaded once)
|
|
descriptor.storageMode = .private
|
|
descriptor.usage = [.shaderRead]
|
|
|
|
// For render targets
|
|
descriptor.storageMode = .private
|
|
descriptor.usage = [.renderTarget, .shaderRead]
|
|
|
|
// For CPU-readable (screenshots, readback)
|
|
descriptor.storageMode = .shared // iOS
|
|
descriptor.storageMode = .managed // macOS
|
|
descriptor.usage = [.shaderRead, .shaderWrite]
|
|
```
|
|
|
|
## Resources
|
|
|
|
**WWDC**: 2016-00602, 2018-00604, 2019-00611
|
|
|
|
**Docs**: /metal/migrating-opengl-code-to-metal, /metal/shader-converter, /metalkit/mtkview
|
|
|
|
**Skills**: axiom-metal-migration, axiom-metal-migration-diag
|
|
|
|
---
|
|
|
|
**Last Updated**: 2025-12-29
|
|
**Platforms**: iOS 12+, macOS 10.14+, tvOS 12+
|
|
**Status**: Complete shader conversion and API mapping reference
|