| /* | |
| * 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" | |
| #include "src/gpu/mtl/MtlUtilsPriv.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> GrMtlCommandBuffer::Make(id<MTLCommandQueue> queue) { | |
| #ifdef SK_BUILD_FOR_IOS | |
| if (skgpu::MtlIsAppInBackground()) { | |
| NSLog(@"GrMtlCommandBuffer: WARNING: Creating MTLCommandBuffer while in background."); | |
| } | |
| #endif | |
| id<MTLCommandBuffer> mtlCommandBuffer; | |
| #if GR_METAL_SDK_VERSION >= 230 | |
| if (@available(macOS 11.0, iOS 14.0, tvOS 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<GrMtlCommandBuffer>(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<MTLBlitCommandEncoder> 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 (skgpu::MtlIsAppInBackground()) { | |
| 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 (skgpu::MtlIsAppInBackground()) { | |
| 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 (skgpu::MtlIsAppInBackground()) { | |
| 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<GrMtlEvent> 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, tvOS 12.0, *)) { | |
| [fCmdBuffer encodeSignalEvent:event->mtlEvent() value:eventValue]; | |
| this->addResource(std::move(event)); | |
| } | |
| fHasWork = true; | |
| } | |
| void GrMtlCommandBuffer::encodeWaitForEvent(sk_sp<GrMtlEvent> 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, tvOS 12.0, *)) { | |
| [fCmdBuffer encodeWaitForEvent:event->mtlEvent() value:eventValue]; | |
| this->addResource(std::move(event)); | |
| } | |
| fHasWork = true; | |
| } | |
| GR_NORETAIN_END |