• Mac에 게임 가져오기, 3부: Metal로 렌더링하기

    3부로 구성된 ‘Mac에 게임 가져오기' 마지막 세션에서는 렌더링 코드에서 Metal을 지원하는 방법을 알아봅니다. Game Porting Toolkit으로 기존의 Windows 바이너리를 실행하고 HLSL 셰이더를 Metal로 가져왔다면, 최신 게임에서 요구되는 고성능 기능 구현을 최적화할 방법을 살펴볼 차례입니다. GPU 리소스 바인딩과 리소스 상주, 그리고 동기화를 관리하는 방법을 소개해 드립니다. 그 밖에 GPU 커맨드 전송을 최적화하는 방법과 MetalFX 업스케일링을 통해 선명한 화면을 렌더링하는 방법 등을 살펴보세요. 이번 세션을 최대한 활용하려면 WWDC23의 'Mac에 게임 가져오기 1부: 게임 플랜 만들기'와 'Mac에 게임 가져오기 2부: 셰이더 컴파일하기'를 먼저 시청해 주시기 바랍니다.

    챕터

    리소스

    관련 비디오

    WWDC23

    Tech Talks

    WWDC22

    WWDC21

    WWDC20

  • 다운로드
    Array
    • 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);
      }