Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2025 Graphics & Games

WWDC25 · 32 min · Graphics & Games

Explore Metal 4 games

Learn to optimize your game engine with the latest advancements in Metal 4. We’ll cover how to unify your command encoding to minimize CPU overhead, scale up your graphics resource management to support massive scenes and maximize your memory budget, and load large libraries of pipeline states quickly. To get the most out of this session, first watch “Discover Metal 4.”

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 17 snippets

Synchronize access to a buffer within an encoder objectivec · at 0:01 ↗
// Synchronize access to a buffer within an encoder

id<MTL4ComputeCommandEncoder> encoder = [commandBuffer computeCommandEncoder];

[encoder copyFromBuffer:src sourceOffset:0 toBuffer:buffer1 destinationOffset:0 size:64];

[encoder barrierAfterEncoderStages:MTLStageBlit 
               beforeEncoderStages:MTLStageDispatch
                 visibilityOptions:MTL4VisibilityOptionDevice];

[encoder setComputePipelineState:pso];

[argTable setAddress:buffer1.gpuAddress atIndex:0];
[encoder setArgumentTable:argTable];
[encoder dispatchThreads:threadsPerGrid threadsPerThreadgroup:threadsPerThreadgroup];

[encoder endEncoding];code snippet.
Configure superset of color attachments objectivec · at 4:29 ↗
// Configure superset of color attachments

MTL4RenderPassDescriptor *desc = [MTLRenderPassDescriptor renderPassDescriptor];

desc.supportColorAttachmentMapping = YES;

desc.colorAttachments[0].texture = colortex0;
desc.colorAttachments[1].texture = colortex1;
desc.colorAttachments[2].texture = colortex2;
desc.colorAttachments[3].texture = colortex3;
desc.colorAttachments[4].texture = colortex4;
Set color attachment map entries objectivec · at 4:38 ↗
// Set color attachment map entries

MTLLogicalToPhysicalColorAttachmentMap* myAttachmentRemap = [MTLLogicalToPhysicalColorAttachmentMap new];

[myAttachmentRemap setPhysicalIndex:0 forLogicalIndex:0];
[myAttachmentRemap setPhysicalIndex:3 forLogicalIndex:1];
[myAttachmentRemap setPhysicalIndex:4 forLogicalIndex:2];
Set a color attachment map per pipeline objectivec · at 4:57 ↗
// Set a color attachment map per pipeline

[renderEncoder setRenderPipelineState:myPipeline];
[renderEncoder setColorAttachmentMap:myAttachmentRemap];
// Draw with myPipeline

[renderEncoder setRenderPipelineState:myPipeline2];
[renderEncoder setColorAttachmentMap:myAttachmentRemap2];
// Draw with myPipeline2
Encode a single render pass with 3 render encoders objectivec · at 8:03 ↗
// Encode a single render pass with 3 render encoders with suspend/resume options


id<MTL4RenderCommandEncoder> enc0 = [cmdbuf0 renderCommandEncoderWithDescriptor:desc options:MTL4RenderEncoderOptionSuspending];

id<MTL4RenderCommandEncoder> enc1 = [cmdbuf1 renderCommandEncoderWithDescriptor:desc options:MTL4RenderEncoderOptionResuming | MTL4RenderEncoderOptionSuspending];

id<MTL4RenderCommandEncoder> enc2 = [cmdbuf2 renderCommandEncoderWithDescriptor:desc options:MTL4RenderEncoderOptionResuming];


id<MTL4CommandBuffer> cmdbufs[] = { cmdbuf0, cmdbuf1, cmdbuf2 };
[commandQueue commit:cmdbufs count:3]
Synchronize drawable contents objectivec · at 11:48 ↗
// Synchronize drawable contents

id<MTLDrawable> drawable = [metalLayer nextDrawable];
[queue waitForDrawable:drawable];

// ... encode render commands to commandBuffer ...
[queue commit:&commandBuffer count:1];

[queue signalDrawable:drawable];

[drawable present];
Encode a queue barrier to synchronize data objectivec · at 13:25 ↗
// Encode a queue barrier to synchronize data

id<MTL4ComputeCommandEncoder> compute = [commandBuffer computeCommandEncoder];

[compute dispatchThreadgroups:threadGrid threadsPerThreadgroup:threadsPerThreadgroup];

[compute endEncoding];


id<MTL4RenderCommandEncoder> render = [commandBuffer renderCommandEncoderWithDescriptor:des];

[render barrierAfterQueueStages:MTLStageDispatch
                   beforeStages:MTLStageFragment
              visibilityOptions:MTL4VisibilityOptionDevice];

[renderCommandEncoder drawPrimitives:MTLPrimitiveTypeTriangle
                         vertexStart:vertexStart
                         vertexCount:vertexCount];

[render endEncoding];
Create a texture view pool objectivec · at 14:57 ↗
// Create a texture view pool

MTLResourceViewPoolDescriptor *desc = [[MTLResourceViewPoolDescriptor alloc] init]; 
desc.resourceCount = 500;
 
id <MTLTextureViewPool> myTextureViewPool =  
    [myDevice newTextureViewPoolWithDescriptor:myTextureViewPoolDescriptor 
                                         error:nullptr];
Set a texture view objectivec · at 15:07 ↗
// Set a texture view

MTLResourceID myTextureView = [myTextureViewPool setTextureView:myTexture  
                                                     descriptor:myTextureViewDescriptor  
                                                        atIndex:5];

[myArgumentTable setTexture:myTextureView 
                    atIndex:0];
Choose appropriate sparse page size objectivec · at 16:01 ↗
MTLHeapDescriptor *desc = [MTLHeapDescriptor new];    
desc.type = MTLHeapTypePlacement;
desc.storageMode = MTLStorageModePrivate;
desc.maxCompatiblePlacementSparsePageSize = MTLSparsePageSize64;
desc.size = alignedHeapSize;

id<MTLHeap> heap = [device newHeapWithDescriptor:desc];
Update buffer mappings objectivec · at 17:05 ↗
// Update buffer mappings

MTL4UpdateSparseBufferMappingOperation bufferOperation;

bufferOperation.mode = MTLSparseTextureMappingModeMap;  
bufferOperation.bufferRange.location = bufferOffsetInTiles;
bufferOperation.bufferRange.length = length;
bufferOperation.heapOffset = heapOffsetInTiles;

[cmdQueue updateBufferMappings:myBuf heap:myHeap operations:&bufferOperation count:1];
Set unspecialized configuration objectivec · at 20:41 ↗
// In MTL4RenderPipelineColorAttachmentDescriptor
// Set unspecialized configuration

pipelineDescriptor.colorAttachments[i].pixelFormat   = MTLPixelFormatUnspecialized;
pipelineDescriptor.colorAttachments[i].writeMask     = MTLColorWriteMaskUnspecialized;
pipelineDescriptor.colorAttachments[i].blendingState = MTL4BlendStateUnspecialized;
Create a specialized transparent pipeline objectivec · at 21:40 ↗
// Create a specialized transparent pipeline

// Set the previously unspecialized properties
pipelineDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
pipelineDescriptor.colorAttachments[0].writeMask =
    MTLColorWriteMaskRed | MTLColorWriteMaskGreen | MTLColorWriteMaskBlue;
pipelineDescriptor.colorAttachments[0].blendingState = MTL4BlendStateEnabled;

pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne;
pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = 
    MTLBlendFactorOneMinusSourceAlpha;
pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;

id<MTLRenderPipelineState> transparentPipeline = 
    [compiler newRenderPipelineStateBySpecializationWithDescriptor:pipelineDescriptor
                                                          pipeline:unspecializedPipeline
                                                             error:&error];

// Similarly, create the specialized opaque and hologram pipelines
Determine thread count objectivec · at 26:22 ↗
// Determine thread count
NSInteger numThreads = 2;
if (@available(macOS 13.3, iOS 19, visionOS 3, tvOS 19, *))
{
    numThreads = [device maximumConcurrentCompilationTaskCount];
}
Set a proper QoS class for your compilation threads objectivec · at 26:30 ↗
// Create thread pool
for (NSInteger i = 0; i < numThreads; ++i)
{
    // Creating a thread with a QoS class DEFAULT
    pthread_attr_set_qos_class_np(&attr, QOS_CLASS_DEFAULT, 0) ;
    pthread_create(&threadIds[i], &attr, entryPoint, NULL);
    pthread_attr_destroy(&attr);
}
Harvest pipeline configuration scripts objectivec · at 28:24 ↗
// Harvest pipeline configuration scripts with the pipeline data set serializer

// Create a pipeline data set serializer that only captures descriptors
MTL4PipelineDataSetSerializerDescriptor *desc = [MTL4PipelineDataSetSerializerDescriptor new];
desc.configuration = MTL4PipelineDataSetSerializerConfigurationCaptureDescriptors;
id<MTL4PipelineDataSetSerializer> serializer =
    [device newPipelineDataSetSerializerWithDescriptor:desc];

// Set the pipeline data set serializer when creating the compiler
MTL4CompilerDescriptor *compilerDesc = [MTL4CompilerDescriptor new];
[compilerDesc setPipelineDataSetSerializer:serializer];
id<MTL4Compiler> compiler = [device newCompilerWithDescriptor:compilerDesc error:nil];

// Create pipelines using the compiler as usual

// Serialize the descriptors as a pipeline script
NSData *data = [serializer serializeAsPipelinesScriptWithError:&err];

// Write the pipeline script data to disk
NSString *path = [NSString pathWithComponents:@[folder, @"pipelines.mtl4-json"]];
BOOL success = [data writeToFile:path options:NSDataWritingAtomic error:&err];
Query pipeline state from MTLArchive objectivec · at 30:28 ↗
// Query pipeline state from MTLArchive

id<MTL4Archive> archive = [device newArchiveWithURL:archiveURL error:&error];

id<MTLRenderPipelineState> pipeline = 
    [archive newRenderPipelineStateWithDescriptor:descriptor error:&error];

if (pipeline == nil)
{
    // handle lookup miss
		pipeline = [compiler newRenderPipelineStateWithDescriptor:descriptor 
                                          compilerTaskOptions:nil 
}

Resources