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