xref: /aosp_15_r20/external/skia/src/gpu/ganesh/mtl/GrMtlCommandBuffer.mm (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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