Search code examples
metal

How to clear color according to viewport on Metal?


Suppose that there is a 200 x 200 color drawable. I want to clear the region(0,0, 100, 100) of the screen to color red. The code is as follows:

- (void)drawInMTKView:(nonnull MTKView *)view
{
    // Create a new command buffer for each render pass to the current drawable.
    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    commandBuffer.label = @"MyCommand";

    // Obtain a renderPassDescriptor generated from the view's drawable textures.
    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
    renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
    renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1.0, 0.0, 0.0, 1.0);
    
    if(renderPassDescriptor != nil)
    {
        // Create a render command encoder.
        id<MTLRenderCommandEncoder> renderEncoder =
        [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        renderEncoder.label = @"MyRenderEncoder";

        // Set the region of the drawable to half.
        NSUInteger renderAreaWidth = _drawableSize.x/2;
        NSUInteger renderAreaHeight = _drawableSize.y/2;
        [renderEncoder setViewport:(MTLViewport){0, 0, renderAreaWidth, renderAreaHeight}];
        [renderEncoder setScissorRect:(MTLScissorRect){0, 0, renderAreaWidth, renderAreaHeight}];
        
        [renderEncoder endEncoding];
 
        // Schedule a present once the framebuffer is complete using the current drawable.
        [commandBuffer presentDrawable:view.currentDrawable];
    }

    // Finalize rendering here & push the command buffer to the GPU.
    [commandBuffer commit];
}

The expected result should be that the top-left part of the drawable was clear to red and the rest is black.
However I found that the entire drawable was clear to red.
How can that be? How do I fix it?


Solution

  • As you've determined, Metal doesn't apply the viewports and scissors to renderpass load-action clearing.

    You'll have to clear the sub-region manually:

    • If you're already in a renderpass, you can render an appropriate clip-space quad.
    • If the renderpass hasn't started, you can optionally use a compute pass, which might be faster.
    • If the renderpass hasn't started, and the region to be cleared has a consistent size (ie- you're always clearing the same area frame-by-frame), you could fill a separate texture or buffer, and BLIT it to the destination region. This will likely be the most efficient option.