• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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