1
votes

In metal, when I already have a RenderCommandEncoder and when I already did some job with it, how can I clear the depth buffer or the stencil buffer (but not both I need to keep one)? For example, in OpenGl we have glClearDepthf / GL_DEPTH_BUFFER_BIT and glClearStencil / GL_STENCIL_BUFFER_BIT but I didn't find any equivalent in metal.

1
There is no API in Metal for clearing the contents of any attachment in the middle of a render pass. To clear an attachment at the beginning of a pass, set its load action to .clear. To preserve the contents of an attachment, set its load action to .load. - warrenm
@warrenm : it's seam doing load on the depth attachment do not work :( I do call endEncoding of the RenderCommandEncoder then I set loadAction to MTLLoadActionLoad and finally i create a new renderCommandEncoder but it's do not help - zeus
Did you set the store action of the depth attachment of the preceding pass to .store (instead of .dontCare)? - warrenm
@warrenm : i think i Found the problem, in fact i didn't create any texture for the DepthAttachment nor for the StencilAttachment! the framework create those texture (but when I don't know but I can see them). sean when i endencoding the framework also release those texture and create new one ... it's seam possible for you ? - zeus
If you're using MTKView, the depth and stencil attachments are not automatically released and/or recreated at the end of the pass. However, if you're manually creating MTLRenderPassDescriptor objects (as opposed to using a view's currentRenderPassDescriptor), you're responsible for setting the same textures on those respective attachments at the beginning of each pass. - warrenm

1 Answers

3
votes

While it's true that Metal doesn't provide a mechanism to clear the depth or stencil buffers in the middle of a rendering pass, it's possible to create a near-trivial pipeline state that allows you to do so as selectively as you like.

In the course of porting some OpenGL code to Metal, I found myself with a need to clear a section of the depth buffer that corresponds to the bounds of the currently set viewport. Here was my solution:

In my setup code, I create a specialized MTLRenderPipelineState and MTLDepthStencilState that are used only for the purpose of clearing the depth buffer, and stash them in my MTKView subclass with my other long-lived resources:

@property (nonatomic, retain) id<MTLRenderPipelineState> pipelineDepthClear;
@property (nonatomic, retain) id<MTLDepthStencilState> depthStencilClear;

[...]

// Special depth stencil state for clearing the depth buffer
MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];

// Don't actually perform a depth test, just always write the buffer
depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways;
depthStencilDescriptor.depthWriteEnabled = YES;
depthStencilDescriptor.label = @"depthStencilClear";
depthStencilClear = [self.device newDepthStencilStateWithDescriptor:depthStencilDescriptor];

// Special pipeline state just for clearing the depth buffer.
MTLRenderPipelineDescriptor *renderPipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];

// Omit the color attachment, since we don't want to write the color buffer for this case.

renderPipelineDescriptor.depthAttachmentPixelFormat = self.depthStencilPixelFormat;
renderPipelineDescriptor.rasterSampleCount = self.sampleCount;

renderPipelineDescriptor.vertexFunction = [self.library newFunctionWithName:@"vertex_depth_clear"];
renderPipelineDescriptor.vertexFunction.label = @"vertexDepthClear";

renderPipelineDescriptor.fragmentFunction = [self.library newFunctionWithName:@"fragment_depth_clear"];
renderPipelineDescriptor.fragmentFunction.label = @"fragmentDepthClear";

MTLVertexDescriptor *vertexDescriptor = [[MTLVertexDescriptor alloc] init];
vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2;
vertexDescriptor.attributes[0].offset = 0;
vertexDescriptor.attributes[0].bufferIndex = 0;
vertexDescriptor.layouts[0].stepRate = 1;
vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
vertexDescriptor.layouts[0].stride = 8;

renderPipelineDescriptor.vertexDescriptor = vertexDescriptor;

NSError* error = NULL;
renderPipelineDescriptor.label = @"pipelineDepthClear";
self.pipelineDepthClear = [self.device newRenderPipelineStateWithDescriptor:renderPipelineDescriptor error:&error];

and set up the matching vertex and fragment functions in my .metal file:

struct DepthClearVertexIn
{
    float2 position [[ attribute(0) ]];
};

struct DepthClearVertexOut
{
    float4 position [[ position ]];
};

struct DepthClearFragmentOut
{
    float depth [[depth(any)]];
};

vertex DepthClearVertexOut 
vertex_depth_clear(     DepthClearVertexIn in [[ stage_in ]])
{
    DepthClearVertexOut out;
    // Just pass the position through. We're clearing in NDC space.
    out.position = float4(in.position, 0.5, 1.0);
    return out;
}

fragment DepthClearFragmentOut fragment_depth_clear()
{
    DepthClearFragmentOut out;
    out.depth = 1.0;
    return out;
}

Finally, the body of my clearDepthBuffer() method looks like this:

// Set up the pipeline and depth/stencil state to write a clear value to only the depth buffer.
[view.commandEncoder setDepthStencilState:view.depthStencilClear];
[view.commandEncoder setRenderPipelineState:view.pipelineDepthClear];

// Normalized Device Coordinates of a tristrip we'll draw to clear the buffer
// (the vertex shader set in pipelineDepthClear ignores all transforms and just passes these through)
float clearCoords[8] = {
    -1, -1,
    1, -1,
    -1, 1,
    1, 1
};

[view.commandEncoder setVertexBytes:clearCoords length:sizeof(float) * 8 atIndex:0];
[view.commandEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];

// Be sure to reset the setDepthStencilState and setRenderPipelineState for further drawing

Since the vertex shader doesn't transform the coordinates at all, I specify the input geometry in NDC space, so a rectangle from (-1, -1) to (1, 1) covers the entire viewport. The same technique could be used to clear any portion of your depth buffer if you set up the geometry and/or transforms appropiately.

A similar technique should work for clearing stencil buffers, but I'll leave that as an exercise to the reader. ;)