-
Bring your game to Mac, Part 3: Render with Metal
Discover how you can support Metal in your rendering code as we close out our three-part series on bringing your game to Mac. Once you've evaluated your existing Windows binary with the game porting toolkit and brought your HLSL shaders over to Metal, learn how you can optimally implement the features that high-end, modern games require. We'll show you how to manage GPU resource bindings, residency, and synchronization. Find out how to optimize GPU commands submission, render rich visuals with MetalFX Upscaling, and more.
To get the most out of this session, we recommend first watching “Bring your game to Mac, Part 1: Make a game plan” and “Bring your game to Mac, Part 2: Compile your shaders" from WWDC23.Chapitres
- 0:00 - Intro
- 1:58 - Manage GPU resources
- 9:08 - Optimize rendering commands
- 18:00 - Handle indirect rendering
- 22:41 - Upscale with MetalFX
- 25:31 - Wrap-Up
Ressources
- Applying temporal antialiasing and upscaling using MetalFX
- MetalFX
- Modern rendering with Metal
- Metal
Vidéos connexes
WWDC23
- Bring your game to Mac, Part 1: Make a game plan
- Bring your game to Mac, Part 2: Compile your shaders
Tech Talks
WWDC22
WWDC21
WWDC20
-
Rechercher dans cette vidéo…
-
-
3:55 - Encode the texture tables.
// Encode the texture tables outside of the rendering loop. id<MTLBuffer> textureTable = [device newBufferWithLength:sizeof(MTLResourceID) * texturesCount options:MTLResourceStorageModeShared]; MTLResourceID* textureTableCPUPtr = (MTLResourceID*)textureTable.contents; for (uint32_t i = 0; i < texturesCount; ++i) { // create the textures. id<MTLTexture> texture = [device newTextureWithDescriptor:textureDesc[i]]; // encode texture in argument buffer textureTableCPUPtr[i] = texture.gpuResourceID; } -
4:33 - Encode the sampler tables.
// Encode the sampler tables outside of the rendering loop. id<MTLBuffer> samplerTable = [device newBufferWithLength:sizeof(MTLResourceID) * samplersCount options:MTLResourceStorageModeShared]; MTLResourceID* samplerTableCPUPtr = (MTLResourceID*)samplerTable.contents; for (uint32_t i = 0; i < samplersCount; ++i) { // create sampler descriptor MTLSamplerDescriptor* desc = [MTLSamplerDescriptor new]; desc.supportArgumentBuffers = YES; . . . // create a sampler id<MTLSamplerState> sampler = [device newSamplerStateWithDescriptor:desc]; // encode the sampler in argument buffer samplerTableCPUPtr[i] = sampler.gpuResourceID; } -
5:05 - Encode the top level argument buffer.
// Encode the top level argument buffer. struct TopLevelAB { MTLResourceID* textureTable; float* myBuffer; uint32_t myConstant; MTLResourceID* samplerTable; }; id<MTLBuffer> topAB = [device newBufferWithLength:sizeof(TopLevelAB) options:MTLResourceStorageModeShared]; TopLevelAB* topABCPUPtr = (TopLevelAB*)topAB.contents; topABCPUPtr->textureTable = (MTLResourceID*)textureTable.gpuAddress; topABCPUPtr->myBuffer = (float*)myBuffer.gpuAddress; topABCPUPtr->myConstant = 128; topABCPUPtr->samplerTable = (MTLResourceID*)samplerTable.gpuAddress; -
6:49 - Allocate the read-only resources.
// Allocate the read-only resources from a heap. MTLHeapDescriptor* heapDesc = [MTLHeapDescriptor new]; heapDesc.size = requiredSize; heapDesc.type = MTLHeapTypeAutomatic; id<MTLHeap> heap = [device newHeapWithDescriptor:heapDesc]; // Allocate the textures and the buffers from the heap. id<MTLTexture> texture = [heap newTextureWithDescriptor:desc]; id<MTLBuffer> buffer = [heap newBufferWithLength:length options:options]; . . . // Make the heap resident once for each encoder that uses it. [encoder useHeap:heap]; -
7:34 - Allocate the writable resources.
// Allocate the writable resources individually. id<MTLTexture> textureRW = [device newTextureWithDescriptor:desc]; id<MTLBuffer> bufferRW = [device newBufferWithLength:length options:options]; // Mark these resources resident when they're needed in the current encoder. // Specify the resource usage in the encoder using MTLResourceUsage. [encoder useResource:textureRW usage:MTLResourceUsageWrite stages:stage]; [encoder useResource:bufferRW usage:MTLResourceUsageRead stages:stage]; -
19:31 - Encode the execute indirect
// Encode the execute indirect command as a series of indirect draw calls. for (uint32_t i = 0; i < maxDrawCount; ++i) { // Encode the current indirect draw call. [renderEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle indexType:MTLIndexTypeUInt16 indexBuffer:indexBuffer indexBufferOffset:indexBufferOffset indirectBuffer:drawArgumentsBuffer indirectBufferOffset:drawArgumentsBufferOffset]; // Advance the draw arguments buffer offset to the next indirect arguments. drawArgumentsBufferOffset += sizeof(MTLDrawIndexedPrimitivesIndirectArguments); } -
21:48 - Translate the indirect draw arguments to ICB.
// Kernel written in Metal Shading Language to translate the indirect draw arguments to an ICB. kernel void translateToICB(device const Command* indirectCommands [[ buffer(0) ]], device const ICBContainerAB* icb [[ buffer(1) ]], . . .) { . . . device const Command* indirectCommand = &indirectCommands[commandIndex]; device const MTLDrawIndexedPrimitivesIndirectArguments* args = &command->mdiBuffer[mdiIndex]; render_command drawCall(icb->buffer, indirectCommand->mdiCmdStart + mdiIndex); if(args->indexCount > 0 && args->instanceCount > 0) { encodeCommand(indirectCommand, args, drawCall); } else { cmd.reset(); } } // Encode a render command on the GPU. void encodeCommand(device const Command* indirectCommand, device const MTLDrawIndexedPrimitivesIndirectArguments* args, thread render_command& drawCall) { drawCall.set_render_pipeline_state(indirectCommand->pso); for(ushort i = 0; i < indirectCommand->vertexBuffersCount; ++i) { drawCall.set_vertex_buffer(indirectCommand->vertexBuffer[i].buffer, indirectCommand->vertexBuffer[i].slot); } for(ushort i = 0; i < indirectCommand->fragmentBuffersCount; ++i) { drawCall.set_fragment_buffer(indirectCommand->fragmentBuffer[i].buffer, indirectCommand->fragmentBuffer[i].slot); } drawCall.draw_indexed_primitives(primitive_type::triangle, args->indexCount, indirectCommand->indexBuffer + args->indexStart, args->instanceCount, args->baseVertex, args->baseInstance); }
-