在 iOS metal 中重用具有不同统一参数的像素着色器

Reusing pixels shaders with different uniform parameters in iOS metal

我在跨具有不同统一参数的不同对象重用我的金属像素和顶点着色器时遇到问题。我猜这与我错误地使用命令缓冲区有关。

当我 运行 我的 metal 应用程序时,几何图形绘制正确,但只有最终调用

的制服

renderCommand setFragmentBuffer:uniform_info offset:0 atIndex:UNIFORM_INFO_BUFFER_INDEX];

似乎正在将数据写入像素着色器。

我在这里剪切粘贴(并编辑)了我的渲染循环。我正在寻找一个示例,说明如何在传入不同制服的单个渲染过程中多次重复使用我的像素着色器。

  CAMetalLayer *metalLayer = [metal_info.g_iosMetalView getMetalLayer];

  id <CAMetalDrawable> frameDrawable;
  while (!frameDrawable){
     frameDrawable = [metalLayer nextDrawable];
  }
  MTLRenderPassDescriptor *mtlRenderPassDescriptor;
  mtlRenderPassDescriptor = [MTLRenderPassDescriptor new];
  mtlRenderPassDescriptor.colorAttachments[0].texture = frameDrawable.texture;
  mtlRenderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionLoad;
  mtlRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 1.0, 1.0);

  mtlRenderPassDescriptor.depthAttachment.texture = metal_info.g_depthTexture;
  mtlRenderPassDescriptor.depthAttachment.loadAction = MTLLoadActionClear;
  mtlRenderPassDescriptor.depthAttachment.storeAction = MTLStoreActionStore;
  mtlRenderPassDescriptor.depthAttachment.clearDepth = 1.0;

  // Create My one and only command buffer
  id <MTLCommandBuffer> mtlCommandBuffer = [metal_info.g_commandQueue commandBuffer];

  int n = RendererInfo.fact.GetActiveCount();

  // loop through all the objects I have (I'm having trouble with 2)
  Object* curObj = NULL;
  for (int obj_i = 0 ; obj_i < n ; ++obj_i)
  {
     // create one render encoder for each object
     id <MTLRenderCommandEncoder> renderCommand = [mtlCommandBuffer renderCommandEncoderWithDescriptor: mtlRenderPassDescriptor];

     memcpy([metal_info.g_projectionMatrix contents], &metal_info.g_Projection, sizeof(Matrix4f));         
     Matrix4f *view = RendererInfo.cam->GetViewMatrix();
     memcpy([metal_info.g_viewMatrix contents], view, sizeof(Matrix4f));

     [renderCommand setFrontFacingWinding:MTLWindingCounterClockwise];
     [renderCommand setCullMode:MTLCullModeBack]; 

     [renderCommand setViewport: (MTLViewport){ 0.0, 0.0,
        (double)metal_info.g_metal_width,
        (double)metal_info.g_metal_height,
        0.0, 1.0 }];

     [renderCommand setRenderPipelineState:metal_info.g_pipelineState];
     [renderCommand setDepthStencilState:metal_info.g_depthState];


     curObj = RendererInfo.fact.GetObjectAtIndex(obj_i);

     id<MTLBuffer> vertices = curObj->GetVertexBuffer();
     id<MTLBuffer> indicies = curObj->GetIndexBuffer();         
     int indexCount = curObj->GetIndexCount();

     id <MTLTexture> texture = objTexture->GetAsset();
     [renderCommand setFragmentTexture:texture atIndex:0];         

     memcpy([metal_info.g_shaderUniformInfo contents], curObj->GetUniformInfo(), curObj->curObj->GetUniformInfoSize());

     [renderCommand setVertexBuffer:vertices                       offset:0 atIndex:VERTICES__INDEX];
     [renderCommand setVertexBuffer:metal_info.g_viewMatrix        offset:0 atIndex:VIEW_MATRIX_BUFFER_INDEX];
     [renderCommand setVertexBuffer:metal_info.g_projectionMatrix  offset:0 atIndex:PROJECTION_MATRIX_BUFFER_INDEX];

     [renderCommand setFragmentBuffer:metal_info.g_shaderUniformInfo  offset:0 atIndex:UNIFORM_INFO_BUFFER_INDEX];

     [renderCommand drawIndexedPrimitives:MTLPrimitiveTypeTriangle
                               indexCount:indexCount
                               indexType:MTLIndexTypeUInt32
                             indexBuffer:indicies
                       indexBufferOffset:0];

     [renderCommand endEncoding];
  }
  [mtlCommandBuffer presentDrawable:frameDrawable];
  [mtlCommandBuffer commit];

如果需要,我也可以 post 像素着色器,但是当我编码为一次只渲染一个对象时,着色器可以正常工作。

MTLRenderCommandEncoder 不对 "memcpy" 指令进行编码。您目前认为每个 memcpy([metal_info.g_shaderUniformInfo contents], curObj->GetUniformInfo(), curObj->curObj->GetUniformInfoSize()); 都与一个编码器相关联,但实际上,您只是将相同的 metal_info.g_shaderUniformInfo 与每个编码器相关联,并且着色器都在使用来自该缓冲区的数据,在命令之后缓冲区已提交——编码器不会捕获缓冲区的副本。您注意到的关键是,此提交发生在您最后一次 运行 通过循环之后,此时 g_shaderUniformInfo 具有唯一有意义的值。

您需要为每个对象使用不同的统一缓冲区,或者写入同一缓冲区的不同部分,然后适当地从中读取。

正如@Jessy 正确指出的那样,Metal 不会复制您的数据,因此实际上只会使用上一次迭代的数据。

我想补充一点,如果您的缓冲区很小(<4KB 左右),您可以使用 setVertexBytes or setFragmentBytes 作为 确实 复制字节的替代方法,并且无需处理创建 MTLBuffer 等问题。这是一个非常方便的方法。