blob: 94741aa37c82c3de47f884179a9b29dfa66312d3 [file] [log] [blame]
/*
* 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, *)) {
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, *)) {
[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, *)) {
[fCmdBuffer encodeWaitForEvent:event->mtlEvent() value:eventValue];
this->addResource(std::move(event));
}
fHasWork = true;
}
GR_NORETAIN_END