/* * Copyright 2019 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/gpu/ganesh/mtl/GrMtlCommandBuffer.h" #include "src/core/SkTraceEvent.h" #include "src/gpu/ganesh/mtl/GrMtlGpu.h" #include "src/gpu/ganesh/mtl/GrMtlOpsRenderPass.h" #include "src/gpu/ganesh/mtl/GrMtlPipelineState.h" #include "src/gpu/ganesh/mtl/GrMtlRenderCommandEncoder.h" #include "src/gpu/ganesh/mtl/GrMtlSemaphore.h" #if !__has_feature(objc_arc) #error This file must be compiled with Arc. Use -fobjc-arc flag #endif GR_NORETAIN_BEGIN sk_sp GrMtlCommandBuffer::Make(id queue) { #ifdef SK_BUILD_FOR_IOS if (GrMtlIsAppInBackground()) { NSLog(@"GrMtlCommandBuffer: WARNING: Creating MTLCommandBuffer while in background."); } #endif id mtlCommandBuffer; #if GR_METAL_SDK_VERSION >= 230 if (@available(macOS 11.0, iOS 14.0, *)) { MTLCommandBufferDescriptor* desc = [[MTLCommandBufferDescriptor alloc] init]; desc.errorOptions = MTLCommandBufferErrorOptionEncoderExecutionStatus; mtlCommandBuffer = [queue commandBufferWithDescriptor:desc]; } else { mtlCommandBuffer = [queue commandBuffer]; } #else mtlCommandBuffer = [queue commandBuffer]; #endif if (nil == mtlCommandBuffer) { return nullptr; } #ifdef SK_ENABLE_MTL_DEBUG_INFO mtlCommandBuffer.label = @"GrMtlCommandBuffer::Make"; #endif return sk_sp(new GrMtlCommandBuffer(mtlCommandBuffer)); } GrMtlCommandBuffer::~GrMtlCommandBuffer() { this->endAllEncoding(); this->releaseResources(); this->callFinishedCallbacks(); fCmdBuffer = nil; } void GrMtlCommandBuffer::releaseResources() { TRACE_EVENT0("skia.gpu", TRACE_FUNC); fTrackedResources.clear(); fTrackedGrBuffers.clear(); fTrackedGrSurfaces.clear(); } id GrMtlCommandBuffer::getBlitCommandEncoder() { if (fActiveBlitCommandEncoder) { return fActiveBlitCommandEncoder; } this->endAllEncoding(); if (fCmdBuffer.status != MTLCommandBufferStatusNotEnqueued) { NSLog(@"GrMtlCommandBuffer: tried to create MTLBlitCommandEncoder while in invalid state."); return nullptr; } #ifdef SK_BUILD_FOR_IOS if (GrMtlIsAppInBackground()) { fActiveBlitCommandEncoder = nil; NSLog(@"GrMtlCommandBuffer: tried to create MTLBlitCommandEncoder while in background."); return nil; } #endif fActiveBlitCommandEncoder = [fCmdBuffer blitCommandEncoder]; fHasWork = true; return fActiveBlitCommandEncoder; } static bool compatible(const MTLRenderPassAttachmentDescriptor* first, const MTLRenderPassAttachmentDescriptor* second, const GrMtlPipelineState* pipelineState) { // From the Metal Best Practices Guide: // Check to see if the previous descriptor is compatible with the new one. // They are compatible if: // * they share the same rendertargets // * the first's store actions are either Store or DontCare // * the second's load actions are either Load or DontCare // * the second doesn't sample from any rendertargets in the first bool renderTargetsMatch = (first.texture == second.texture); bool storeActionsValid = first.storeAction == MTLStoreActionStore || first.storeAction == MTLStoreActionDontCare; bool loadActionsValid = second.loadAction == MTLLoadActionLoad || second.loadAction == MTLLoadActionDontCare; bool secondDoesntSampleFirst = (!pipelineState || pipelineState->doesntSampleAttachment(first)); // Since we are trying to use the same encoder rather than merging two, // we have to check to see if both store actions are mutually compatible. bool secondStoreValid = true; if (second.storeAction == MTLStoreActionDontCare) { secondStoreValid = (first.storeAction == MTLStoreActionDontCare); // TODO: if first.storeAction is Store and second.loadAction is Load, // we could reset the active RenderCommandEncoder's store action to DontCare } else if (second.storeAction == MTLStoreActionStore) { if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) { secondStoreValid = (first.storeAction == MTLStoreActionStore || first.storeAction == MTLStoreActionStoreAndMultisampleResolve); } else { secondStoreValid = (first.storeAction == MTLStoreActionStore); } // TODO: if the first store action is DontCare we could reset the active // RenderCommandEncoder's store action to Store, but it's not clear if it's worth it. } else if (second.storeAction == MTLStoreActionMultisampleResolve) { if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) { secondStoreValid = (first.resolveTexture == second.resolveTexture) && (first.storeAction == MTLStoreActionMultisampleResolve || first.storeAction == MTLStoreActionStoreAndMultisampleResolve); } else { secondStoreValid = (first.resolveTexture == second.resolveTexture) && (first.storeAction == MTLStoreActionMultisampleResolve); } // When we first check whether store actions are valid we don't consider resolves, // so we need to reset that here. storeActionsValid = secondStoreValid; } else { if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) { if (second.storeAction == MTLStoreActionStoreAndMultisampleResolve) { secondStoreValid = (first.resolveTexture == second.resolveTexture) && (first.storeAction == MTLStoreActionStoreAndMultisampleResolve); // TODO: if the first store action is simply MultisampleResolve we could reset // the active RenderCommandEncoder's store action to StoreAndMultisampleResolve, // but it's not clear if it's worth it. // When we first check whether store actions are valid we don't consider resolves, // so we need to reset that here. storeActionsValid = secondStoreValid; } } } return renderTargetsMatch && (nil == first.texture || (storeActionsValid && loadActionsValid && secondDoesntSampleFirst && secondStoreValid)); } GrMtlRenderCommandEncoder* GrMtlCommandBuffer::getRenderCommandEncoder( MTLRenderPassDescriptor* descriptor, const GrMtlPipelineState* pipelineState, GrMtlOpsRenderPass* opsRenderPass) { if (nil != fPreviousRenderPassDescriptor) { if (compatible(fPreviousRenderPassDescriptor.colorAttachments[0], descriptor.colorAttachments[0], pipelineState) && compatible(fPreviousRenderPassDescriptor.stencilAttachment, descriptor.stencilAttachment, pipelineState)) { return fActiveRenderCommandEncoder.get(); } } return this->getRenderCommandEncoder(descriptor, opsRenderPass); } GrMtlRenderCommandEncoder* GrMtlCommandBuffer::getRenderCommandEncoder( MTLRenderPassDescriptor* descriptor, GrMtlOpsRenderPass* opsRenderPass) { this->endAllEncoding(); if (fCmdBuffer.status != MTLCommandBufferStatusNotEnqueued) { NSLog(@"GrMtlCommandBuffer: tried to create MTLRenderCommandEncoder while in bad state."); return nullptr; } #ifdef SK_BUILD_FOR_IOS if (GrMtlIsAppInBackground()) { fActiveRenderCommandEncoder = nullptr; NSLog(@"GrMtlCommandBuffer: tried to create MTLRenderCommandEncoder while in background."); return nullptr; } #endif fActiveRenderCommandEncoder = GrMtlRenderCommandEncoder::Make( [fCmdBuffer renderCommandEncoderWithDescriptor:descriptor]); if (opsRenderPass) { opsRenderPass->initRenderState(fActiveRenderCommandEncoder.get()); } fPreviousRenderPassDescriptor = descriptor; fHasWork = true; return fActiveRenderCommandEncoder.get(); } bool GrMtlCommandBuffer::commit(bool waitUntilCompleted) { this->endAllEncoding(); if ([fCmdBuffer status] != MTLCommandBufferStatusNotEnqueued) { NSLog(@"GrMtlCommandBuffer: Tried to commit command buffer while in invalid state.\n"); return false; } #ifdef SK_BUILD_FOR_IOS if (GrMtlIsAppInBackground()) { NSLog(@"GrMtlCommandBuffer: Tried to commit command buffer while in background.\n"); return false; } #endif [fCmdBuffer commit]; if (waitUntilCompleted) { this->waitUntilCompleted(); #if defined(SK_BUILD_FOR_IOS) && defined(SK_METAL_WAIT_UNTIL_SCHEDULED) // If iOS goes into the background we need to make sure all command buffers are scheduled first. // We don't have a way of detecting background transition so this guarantees it. } else { [fCmdBuffer waitUntilScheduled]; #endif } if ([fCmdBuffer status] == MTLCommandBufferStatusError) { SkDebugf("Error submitting command buffer.\n"); if (NSError* error = [fCmdBuffer error]) { NSLog(@"%@", error); } } return ([fCmdBuffer status] != MTLCommandBufferStatusError); } void GrMtlCommandBuffer::endAllEncoding() { if (fActiveRenderCommandEncoder) { fActiveRenderCommandEncoder->endEncoding(); fActiveRenderCommandEncoder.reset(); fPreviousRenderPassDescriptor = nil; } if (fActiveBlitCommandEncoder) { [fActiveBlitCommandEncoder endEncoding]; fActiveBlitCommandEncoder = nil; } } void GrMtlCommandBuffer::encodeSignalEvent(sk_sp event, uint64_t eventValue) { SkASSERT(fCmdBuffer); this->endAllEncoding(); // ensure we don't have any active command encoders if (@available(macOS 10.14, iOS 12.0, *)) { [fCmdBuffer encodeSignalEvent:event->mtlEvent() value:eventValue]; this->addResource(std::move(event)); } fHasWork = true; } void GrMtlCommandBuffer::encodeWaitForEvent(sk_sp event, uint64_t eventValue) { SkASSERT(fCmdBuffer); this->endAllEncoding(); // ensure we don't have any active command encoders // TODO: not sure if needed but probably if (@available(macOS 10.14, iOS 12.0, *)) { [fCmdBuffer encodeWaitForEvent:event->mtlEvent() value:eventValue]; this->addResource(std::move(event)); } fHasWork = true; } GR_NORETAIN_END