1/* 2 * Copyright 2019 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "src/gpu/ganesh/mtl/GrMtlCommandBuffer.h" 9 10#include "src/core/SkTraceEvent.h" 11#include "src/gpu/ganesh/mtl/GrMtlGpu.h" 12#include "src/gpu/ganesh/mtl/GrMtlOpsRenderPass.h" 13#include "src/gpu/ganesh/mtl/GrMtlPipelineState.h" 14#include "src/gpu/ganesh/mtl/GrMtlRenderCommandEncoder.h" 15#include "src/gpu/ganesh/mtl/GrMtlSemaphore.h" 16#include "src/gpu/mtl/MtlUtilsPriv.h" 17 18#if !__has_feature(objc_arc) 19#error This file must be compiled with Arc. Use -fobjc-arc flag 20#endif 21 22GR_NORETAIN_BEGIN 23 24sk_sp<GrMtlCommandBuffer> GrMtlCommandBuffer::Make(id<MTLCommandQueue> queue) { 25 id<MTLCommandBuffer> mtlCommandBuffer; 26#if GR_METAL_SDK_VERSION >= 230 27 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 28 MTLCommandBufferDescriptor* desc = [[MTLCommandBufferDescriptor alloc] init]; 29 desc.errorOptions = MTLCommandBufferErrorOptionEncoderExecutionStatus; 30 mtlCommandBuffer = [queue commandBufferWithDescriptor:desc]; 31 } else { 32 mtlCommandBuffer = [queue commandBuffer]; 33 } 34#else 35 mtlCommandBuffer = [queue commandBuffer]; 36#endif 37 if (nil == mtlCommandBuffer) { 38 return nullptr; 39 } 40 41#ifdef SK_ENABLE_MTL_DEBUG_INFO 42 mtlCommandBuffer.label = @"GrMtlCommandBuffer::Make"; 43#endif 44 45 return sk_sp<GrMtlCommandBuffer>(new GrMtlCommandBuffer(mtlCommandBuffer)); 46} 47 48GrMtlCommandBuffer::~GrMtlCommandBuffer() { 49 this->endAllEncoding(); 50 this->releaseResources(); 51 this->callFinishedCallbacks(); 52 53 fCmdBuffer = nil; 54} 55 56void GrMtlCommandBuffer::releaseResources() { 57 TRACE_EVENT0("skia.gpu", TRACE_FUNC); 58 59 fTrackedResources.clear(); 60 fTrackedGrBuffers.clear(); 61 fTrackedGrSurfaces.clear(); 62} 63 64id<MTLBlitCommandEncoder> GrMtlCommandBuffer::getBlitCommandEncoder() { 65 if (fActiveBlitCommandEncoder) { 66 return fActiveBlitCommandEncoder; 67 } 68 69 this->endAllEncoding(); 70 if (fCmdBuffer.status != MTLCommandBufferStatusNotEnqueued) { 71 NSLog(@"GrMtlCommandBuffer: tried to create MTLBlitCommandEncoder while in invalid state."); 72 return nullptr; 73 } 74 fActiveBlitCommandEncoder = [fCmdBuffer blitCommandEncoder]; 75 fHasWork = true; 76 77 return fActiveBlitCommandEncoder; 78} 79 80static bool compatible(const MTLRenderPassAttachmentDescriptor* first, 81 const MTLRenderPassAttachmentDescriptor* second, 82 const GrMtlPipelineState* pipelineState) { 83 // From the Metal Best Practices Guide: 84 // Check to see if the previous descriptor is compatible with the new one. 85 // They are compatible if: 86 // * they share the same rendertargets 87 // * the first's store actions are either Store or DontCare 88 // * the second's load actions are either Load or DontCare 89 // * the second doesn't sample from any rendertargets in the first 90 bool renderTargetsMatch = (first.texture == second.texture); 91 bool storeActionsValid = first.storeAction == MTLStoreActionStore || 92 first.storeAction == MTLStoreActionDontCare; 93 bool loadActionsValid = second.loadAction == MTLLoadActionLoad || 94 second.loadAction == MTLLoadActionDontCare; 95 bool secondDoesntSampleFirst = (!pipelineState || 96 pipelineState->doesntSampleAttachment(first)); 97 98 // Since we are trying to use the same encoder rather than merging two, 99 // we have to check to see if both store actions are mutually compatible. 100 bool secondStoreValid = true; 101 if (second.storeAction == MTLStoreActionDontCare) { 102 secondStoreValid = (first.storeAction == MTLStoreActionDontCare); 103 // TODO: if first.storeAction is Store and second.loadAction is Load, 104 // we could reset the active RenderCommandEncoder's store action to DontCare 105 } else if (second.storeAction == MTLStoreActionStore) { 106 if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) { 107 secondStoreValid = (first.storeAction == MTLStoreActionStore || 108 first.storeAction == MTLStoreActionStoreAndMultisampleResolve); 109 } else { 110 secondStoreValid = (first.storeAction == MTLStoreActionStore); 111 } 112 // TODO: if the first store action is DontCare we could reset the active 113 // RenderCommandEncoder's store action to Store, but it's not clear if it's worth it. 114 } else if (second.storeAction == MTLStoreActionMultisampleResolve) { 115 if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) { 116 secondStoreValid = (first.resolveTexture == second.resolveTexture) && 117 (first.storeAction == MTLStoreActionMultisampleResolve || 118 first.storeAction == MTLStoreActionStoreAndMultisampleResolve); 119 } else { 120 secondStoreValid = (first.resolveTexture == second.resolveTexture) && 121 (first.storeAction == MTLStoreActionMultisampleResolve); 122 } 123 // When we first check whether store actions are valid we don't consider resolves, 124 // so we need to reset that here. 125 storeActionsValid = secondStoreValid; 126 } else { 127 if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) { 128 if (second.storeAction == MTLStoreActionStoreAndMultisampleResolve) { 129 secondStoreValid = (first.resolveTexture == second.resolveTexture) && 130 (first.storeAction == MTLStoreActionStoreAndMultisampleResolve); 131 // TODO: if the first store action is simply MultisampleResolve we could reset 132 // the active RenderCommandEncoder's store action to StoreAndMultisampleResolve, 133 // but it's not clear if it's worth it. 134 135 // When we first check whether store actions are valid we don't consider resolves, 136 // so we need to reset that here. 137 storeActionsValid = secondStoreValid; 138 } 139 } 140 } 141 142 return renderTargetsMatch && 143 (nil == first.texture || 144 (storeActionsValid && loadActionsValid && secondDoesntSampleFirst && secondStoreValid)); 145} 146 147GrMtlRenderCommandEncoder* GrMtlCommandBuffer::getRenderCommandEncoder( 148 MTLRenderPassDescriptor* descriptor, const GrMtlPipelineState* pipelineState, 149 GrMtlOpsRenderPass* opsRenderPass) { 150 if (nil != fPreviousRenderPassDescriptor) { 151 if (compatible(fPreviousRenderPassDescriptor.colorAttachments[0], 152 descriptor.colorAttachments[0], pipelineState) && 153 compatible(fPreviousRenderPassDescriptor.stencilAttachment, 154 descriptor.stencilAttachment, pipelineState)) { 155 return fActiveRenderCommandEncoder.get(); 156 } 157 } 158 159 return this->getRenderCommandEncoder(descriptor, opsRenderPass); 160} 161 162GrMtlRenderCommandEncoder* GrMtlCommandBuffer::getRenderCommandEncoder( 163 MTLRenderPassDescriptor* descriptor, 164 GrMtlOpsRenderPass* opsRenderPass) { 165 this->endAllEncoding(); 166 if (fCmdBuffer.status != MTLCommandBufferStatusNotEnqueued) { 167 NSLog(@"GrMtlCommandBuffer: tried to create MTLRenderCommandEncoder while in bad state."); 168 return nullptr; 169 } 170 fActiveRenderCommandEncoder = GrMtlRenderCommandEncoder::Make( 171 [fCmdBuffer renderCommandEncoderWithDescriptor:descriptor]); 172 if (opsRenderPass) { 173 opsRenderPass->initRenderState(fActiveRenderCommandEncoder.get()); 174 } 175 fPreviousRenderPassDescriptor = descriptor; 176 fHasWork = true; 177 178 return fActiveRenderCommandEncoder.get(); 179} 180 181bool GrMtlCommandBuffer::commit(bool waitUntilCompleted) { 182 this->endAllEncoding(); 183 if ([fCmdBuffer status] != MTLCommandBufferStatusNotEnqueued) { 184 NSLog(@"GrMtlCommandBuffer: Tried to commit command buffer while in invalid state.\n"); 185 return false; 186 } 187 [fCmdBuffer commit]; 188 if (waitUntilCompleted) { 189 this->waitUntilCompleted(); 190#if defined(SK_BUILD_FOR_IOS) && defined(SK_METAL_WAIT_UNTIL_SCHEDULED) 191 // If iOS goes into the background we need to make sure all command buffers are scheduled first. 192 // We don't have a way of detecting background transition so this guarantees it. 193 } else { 194 [fCmdBuffer waitUntilScheduled]; 195#endif 196 } 197 198 if ([fCmdBuffer status] == MTLCommandBufferStatusError) { 199 SkDebugf("Error submitting command buffer.\n"); 200 if (NSError* error = [fCmdBuffer error]) { 201 NSLog(@"%@", error); 202 } 203 } 204 205 return ([fCmdBuffer status] != MTLCommandBufferStatusError); 206} 207 208void GrMtlCommandBuffer::endAllEncoding() { 209 if (fActiveRenderCommandEncoder) { 210 fActiveRenderCommandEncoder->endEncoding(); 211 fActiveRenderCommandEncoder.reset(); 212 fPreviousRenderPassDescriptor = nil; 213 } 214 if (fActiveBlitCommandEncoder) { 215 [fActiveBlitCommandEncoder endEncoding]; 216 fActiveBlitCommandEncoder = nil; 217 } 218} 219 220void GrMtlCommandBuffer::encodeSignalEvent(sk_sp<GrMtlEvent> event, uint64_t eventValue) { 221 SkASSERT(fCmdBuffer); 222 this->endAllEncoding(); // ensure we don't have any active command encoders 223 if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, *)) { 224 [fCmdBuffer encodeSignalEvent:event->mtlEvent() value:eventValue]; 225 this->addResource(std::move(event)); 226 } 227 fHasWork = true; 228} 229 230void GrMtlCommandBuffer::encodeWaitForEvent(sk_sp<GrMtlEvent> event, uint64_t eventValue) { 231 SkASSERT(fCmdBuffer); 232 this->endAllEncoding(); // ensure we don't have any active command encoders 233 // TODO: not sure if needed but probably 234 if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, *)) { 235 [fCmdBuffer encodeWaitForEvent:event->mtlEvent() value:eventValue]; 236 this->addResource(std::move(event)); 237 } 238 fHasWork = true; 239} 240 241GR_NORETAIN_END 242