1
votes

I'm having a problem reusing my metal pixel and vertex shaders across different objects with different uniform parameters. I'm guessing its got something to do with my using my command buffer incorrectly.

When I run my metal application, the geometry draw correctly, but only the uniforms for the final call to

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

Seems to write data to the pixel shader.

I've cut-n-pasted (and edited) my render loop here. I'm looking for an example of how to reuse my pixel shader multiple times during a single render pass with different uniforms passed in.

  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];

I can also post the pixel shader if you want, but when I've coded to only render one object at a time, the shader works correctly.

2
I looked up what "g_" meant after answering; it's actually key to your problem.Jessy

2 Answers

2
votes

The MTLRenderCommandEncoder doesn't encode "memcpy" instructions. You currently believe that each memcpy([metal_info.g_shaderUniformInfo contents], curObj->GetUniformInfo(), curObj->curObj->GetUniformInfoSize()); is being associated with an encoder, but actually, you're just associating the same metal_info.g_shaderUniformInfo with each encoder, and the shaders are all using data from that buffer, after the command buffer is committed – the encoders don't capture a copy of the buffer. Key to what you've noticed, is that this commit happens after you've run through the loop for the last time, at which point g_shaderUniformInfo has the only value that will ever be meaningfully used.

You need a different uniform buffer for each object, or to write to different parts of the same buffer, and read from them appropriately.

0
votes

As @Jessy correctly stated, Metal isn't copying your data, so effectively only the data from the last iteration is going to be used.

I wanted to add that if your buffers are small (<4KB or so), you can use setVertexBytes or setFragmentBytes as an alternative that does copy the bytes, and without having to deal with creating an MTLBuffer, etc. It's a nice convenience method.