Metal: Add CommandRecordingContext

Introduces the idea of a CommandRecordingContext to the Metal backend,
similar to other backends. This is a class to track which Metal encoder
is open on the device-global pending MTLCommandBuffer.
It will be needed to open/close encoders for lazy clearing.

Bug: dawn:145
Change-Id: Ief6b71a079d73943677d2b61382d1c36b88a4f87
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/14780
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
This commit is contained in:
Austin Eng 2020-01-08 17:01:07 +00:00 committed by Commit Bot service account
parent d1cba106c8
commit 2b3975f808
8 changed files with 280 additions and 146 deletions

View File

@ -355,6 +355,8 @@ source_set("libdawn_native_sources") {
"src/dawn_native/metal/BufferMTL.mm", "src/dawn_native/metal/BufferMTL.mm",
"src/dawn_native/metal/CommandBufferMTL.h", "src/dawn_native/metal/CommandBufferMTL.h",
"src/dawn_native/metal/CommandBufferMTL.mm", "src/dawn_native/metal/CommandBufferMTL.mm",
"src/dawn_native/metal/CommandRecordingContext.h",
"src/dawn_native/metal/CommandRecordingContext.mm",
"src/dawn_native/metal/ComputePipelineMTL.h", "src/dawn_native/metal/ComputePipelineMTL.h",
"src/dawn_native/metal/ComputePipelineMTL.mm", "src/dawn_native/metal/ComputePipelineMTL.mm",
"src/dawn_native/metal/DeviceMTL.h", "src/dawn_native/metal/DeviceMTL.h",

View File

@ -26,25 +26,24 @@ namespace dawn_native {
namespace dawn_native { namespace metal { namespace dawn_native { namespace metal {
class CommandRecordingContext;
class Device; class Device;
struct GlobalEncoders;
class CommandBuffer : public CommandBufferBase { class CommandBuffer : public CommandBufferBase {
public: public:
CommandBuffer(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor); CommandBuffer(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor);
~CommandBuffer(); ~CommandBuffer();
void FillCommands(id<MTLCommandBuffer> commandBuffer); void FillCommands(CommandRecordingContext* commandContext);
private: private:
void EncodeComputePass(id<MTLCommandBuffer> commandBuffer); void EncodeComputePass(CommandRecordingContext* commandContext);
void EncodeRenderPass(id<MTLCommandBuffer> commandBuffer, void EncodeRenderPass(CommandRecordingContext* commandContext,
MTLRenderPassDescriptor* mtlRenderPass, MTLRenderPassDescriptor* mtlRenderPass,
GlobalEncoders* globalEncoders,
uint32_t width, uint32_t width,
uint32_t height); uint32_t height);
void EncodeRenderPassInternal(id<MTLCommandBuffer> commandBuffer, void EncodeRenderPassInternal(CommandRecordingContext* commandContext,
MTLRenderPassDescriptor* mtlRenderPass, MTLRenderPassDescriptor* mtlRenderPass,
uint32_t width, uint32_t width,
uint32_t height); uint32_t height);

View File

@ -29,23 +29,6 @@
namespace dawn_native { namespace metal { namespace dawn_native { namespace metal {
struct GlobalEncoders {
id<MTLBlitCommandEncoder> blit = nil;
void Finish() {
if (blit != nil) {
[blit endEncoding];
blit = nil; // This will be autoreleased.
}
}
void EnsureBlit(id<MTLCommandBuffer> commandBuffer) {
if (blit == nil) {
blit = [commandBuffer blitCommandEncoder];
}
}
};
namespace { namespace {
// Allows this file to use MTLStoreActionStoreAndMultismapleResolve because the logic is // Allows this file to use MTLStoreActionStoreAndMultismapleResolve because the logic is
@ -133,7 +116,7 @@ namespace dawn_native { namespace metal {
// Helper function for Toggle EmulateStoreAndMSAAResolve // Helper function for Toggle EmulateStoreAndMSAAResolve
void ResolveInAnotherRenderPass( void ResolveInAnotherRenderPass(
id<MTLCommandBuffer> commandBuffer, CommandRecordingContext* commandContext,
const MTLRenderPassDescriptor* mtlRenderPass, const MTLRenderPassDescriptor* mtlRenderPass,
const std::array<id<MTLTexture>, kMaxColorAttachments>& resolveTextures) { const std::array<id<MTLTexture>, kMaxColorAttachments>& resolveTextures) {
MTLRenderPassDescriptor* mtlRenderPassForResolve = MTLRenderPassDescriptor* mtlRenderPassForResolve =
@ -155,9 +138,8 @@ namespace dawn_native { namespace metal {
mtlRenderPass.colorAttachments[i].resolveSlice; mtlRenderPass.colorAttachments[i].resolveSlice;
} }
id<MTLRenderCommandEncoder> encoder = commandContext->BeginRender(mtlRenderPassForResolve);
[commandBuffer renderCommandEncoderWithDescriptor:mtlRenderPassForResolve]; commandContext->EndRender();
[encoder endEncoding];
} }
// Helper functions for Toggle AlwaysResolveIntoZeroLevelAndLayer // Helper functions for Toggle AlwaysResolveIntoZeroLevelAndLayer
@ -182,16 +164,14 @@ namespace dawn_native { namespace metal {
return resolveTexture; return resolveTexture;
} }
void CopyIntoTrueResolveTarget(id<MTLCommandBuffer> commandBuffer, void CopyIntoTrueResolveTarget(CommandRecordingContext* commandContext,
id<MTLTexture> mtlTrueResolveTexture, id<MTLTexture> mtlTrueResolveTexture,
uint32_t trueResolveLevel, uint32_t trueResolveLevel,
uint32_t trueResolveSlice, uint32_t trueResolveSlice,
id<MTLTexture> temporaryResolveTexture, id<MTLTexture> temporaryResolveTexture,
uint32_t width, uint32_t width,
uint32_t height, uint32_t height) {
GlobalEncoders* encoders) { [commandContext->EnsureBlit() copyFromTexture:temporaryResolveTexture
encoders->EnsureBlit(commandBuffer);
[encoders->blit copyFromTexture:temporaryResolveTexture
sourceSlice:0 sourceSlice:0
sourceLevel:0 sourceLevel:0
sourceOrigin:MTLOriginMake(0, 0, 0) sourceOrigin:MTLOriginMake(0, 0, 0)
@ -608,30 +588,29 @@ namespace dawn_native { namespace metal {
FreeCommands(&mCommands); FreeCommands(&mCommands);
} }
void CommandBuffer::FillCommands(id<MTLCommandBuffer> commandBuffer) { void CommandBuffer::FillCommands(CommandRecordingContext* commandContext) {
GlobalEncoders encoders;
Command type; Command type;
while (mCommands.NextCommandId(&type)) { while (mCommands.NextCommandId(&type)) {
switch (type) { switch (type) {
case Command::BeginComputePass: { case Command::BeginComputePass: {
mCommands.NextCommand<BeginComputePassCmd>(); mCommands.NextCommand<BeginComputePassCmd>();
encoders.Finish();
EncodeComputePass(commandBuffer); commandContext->EndBlit();
EncodeComputePass(commandContext);
} break; } break;
case Command::BeginRenderPass: { case Command::BeginRenderPass: {
BeginRenderPassCmd* cmd = mCommands.NextCommand<BeginRenderPassCmd>(); BeginRenderPassCmd* cmd = mCommands.NextCommand<BeginRenderPassCmd>();
encoders.Finish(); commandContext->EndBlit();
MTLRenderPassDescriptor* descriptor = CreateMTLRenderPassDescriptor(cmd); MTLRenderPassDescriptor* descriptor = CreateMTLRenderPassDescriptor(cmd);
EncodeRenderPass(commandBuffer, descriptor, &encoders, cmd->width, cmd->height); EncodeRenderPass(commandContext, descriptor, cmd->width, cmd->height);
} break; } break;
case Command::CopyBufferToBuffer: { case Command::CopyBufferToBuffer: {
CopyBufferToBufferCmd* copy = mCommands.NextCommand<CopyBufferToBufferCmd>(); CopyBufferToBufferCmd* copy = mCommands.NextCommand<CopyBufferToBufferCmd>();
encoders.EnsureBlit(commandBuffer); [commandContext->EnsureBlit()
[encoders.blit copyFromBuffer:ToBackend(copy->source)->GetMTLBuffer() copyFromBuffer:ToBackend(copy->source)->GetMTLBuffer()
sourceOffset:copy->sourceOffset sourceOffset:copy->sourceOffset
toBuffer:ToBackend(copy->destination)->GetMTLBuffer() toBuffer:ToBackend(copy->destination)->GetMTLBuffer()
destinationOffset:copy->destinationOffset destinationOffset:copy->destinationOffset
@ -651,10 +630,9 @@ namespace dawn_native { namespace metal {
dst.origin, copySize, texture->GetFormat(), virtualSizeAtLevel, dst.origin, copySize, texture->GetFormat(), virtualSizeAtLevel,
buffer->GetSize(), src.offset, src.rowPitch, src.imageHeight); buffer->GetSize(), src.offset, src.rowPitch, src.imageHeight);
encoders.EnsureBlit(commandBuffer);
for (uint32_t i = 0; i < splittedCopies.count; ++i) { for (uint32_t i = 0; i < splittedCopies.count; ++i) {
const TextureBufferCopySplit::CopyInfo& copyInfo = splittedCopies.copies[i]; const TextureBufferCopySplit::CopyInfo& copyInfo = splittedCopies.copies[i];
[encoders.blit copyFromBuffer:buffer->GetMTLBuffer() [commandContext->EnsureBlit() copyFromBuffer:buffer->GetMTLBuffer()
sourceOffset:copyInfo.bufferOffset sourceOffset:copyInfo.bufferOffset
sourceBytesPerRow:copyInfo.bytesPerRow sourceBytesPerRow:copyInfo.bytesPerRow
sourceBytesPerImage:copyInfo.bytesPerImage sourceBytesPerImage:copyInfo.bytesPerImage
@ -679,10 +657,9 @@ namespace dawn_native { namespace metal {
src.origin, copySize, texture->GetFormat(), virtualSizeAtLevel, src.origin, copySize, texture->GetFormat(), virtualSizeAtLevel,
buffer->GetSize(), dst.offset, dst.rowPitch, dst.imageHeight); buffer->GetSize(), dst.offset, dst.rowPitch, dst.imageHeight);
encoders.EnsureBlit(commandBuffer);
for (uint32_t i = 0; i < splittedCopies.count; ++i) { for (uint32_t i = 0; i < splittedCopies.count; ++i) {
const TextureBufferCopySplit::CopyInfo& copyInfo = splittedCopies.copies[i]; const TextureBufferCopySplit::CopyInfo& copyInfo = splittedCopies.copies[i];
[encoders.blit copyFromTexture:texture->GetMTLTexture() [commandContext->EnsureBlit() copyFromTexture:texture->GetMTLTexture()
sourceSlice:src.arrayLayer sourceSlice:src.arrayLayer
sourceLevel:src.mipLevel sourceLevel:src.mipLevel
sourceOrigin:copyInfo.textureOrigin sourceOrigin:copyInfo.textureOrigin
@ -700,9 +677,8 @@ namespace dawn_native { namespace metal {
Texture* srcTexture = ToBackend(copy->source.texture.Get()); Texture* srcTexture = ToBackend(copy->source.texture.Get());
Texture* dstTexture = ToBackend(copy->destination.texture.Get()); Texture* dstTexture = ToBackend(copy->destination.texture.Get());
encoders.EnsureBlit(commandBuffer); [commandContext->EnsureBlit()
copyFromTexture:srcTexture->GetMTLTexture()
[encoders.blit copyFromTexture:srcTexture->GetMTLTexture()
sourceSlice:copy->source.arrayLayer sourceSlice:copy->source.arrayLayer
sourceLevel:copy->source.mipLevel sourceLevel:copy->source.mipLevel
sourceOrigin:MakeMTLOrigin(copy->source.origin) sourceOrigin:MakeMTLOrigin(copy->source.origin)
@ -717,23 +693,22 @@ namespace dawn_native { namespace metal {
} }
} }
encoders.Finish(); commandContext->EndBlit();
} }
void CommandBuffer::EncodeComputePass(id<MTLCommandBuffer> commandBuffer) { void CommandBuffer::EncodeComputePass(CommandRecordingContext* commandContext) {
ComputePipeline* lastPipeline = nullptr; ComputePipeline* lastPipeline = nullptr;
StorageBufferLengthTracker storageBufferLengths = {}; StorageBufferLengthTracker storageBufferLengths = {};
BindGroupTracker bindGroups(&storageBufferLengths); BindGroupTracker bindGroups(&storageBufferLengths);
// Will be autoreleased id<MTLComputeCommandEncoder> encoder = commandContext->BeginCompute();
id<MTLComputeCommandEncoder> encoder = [commandBuffer computeCommandEncoder];
Command type; Command type;
while (mCommands.NextCommandId(&type)) { while (mCommands.NextCommandId(&type)) {
switch (type) { switch (type) {
case Command::EndComputePass: { case Command::EndComputePass: {
mCommands.NextCommand<EndComputePassCmd>(); mCommands.NextCommand<EndComputePassCmd>();
[encoder endEncoding]; commandContext->EndCompute();
return; return;
} break; } break;
@ -813,12 +788,11 @@ namespace dawn_native { namespace metal {
UNREACHABLE(); UNREACHABLE();
} }
void CommandBuffer::EncodeRenderPass(id<MTLCommandBuffer> commandBuffer, void CommandBuffer::EncodeRenderPass(CommandRecordingContext* commandContext,
MTLRenderPassDescriptor* mtlRenderPass, MTLRenderPassDescriptor* mtlRenderPass,
GlobalEncoders* globalEncoders,
uint32_t width, uint32_t width,
uint32_t height) { uint32_t height) {
ASSERT(mtlRenderPass && globalEncoders); ASSERT(mtlRenderPass);
Device* device = ToBackend(GetDevice()); Device* device = ToBackend(GetDevice());
@ -861,17 +835,16 @@ namespace dawn_native { namespace metal {
// If we need to use a temporary resolve texture we need to copy the result of MSAA // If we need to use a temporary resolve texture we need to copy the result of MSAA
// resolve back to the true resolve targets. // resolve back to the true resolve targets.
if (useTemporaryResolveTexture) { if (useTemporaryResolveTexture) {
EncodeRenderPass(commandBuffer, mtlRenderPass, globalEncoders, width, height); EncodeRenderPass(commandContext, mtlRenderPass, width, height);
for (uint32_t i = 0; i < kMaxColorAttachments; ++i) { for (uint32_t i = 0; i < kMaxColorAttachments; ++i) {
if (trueResolveTextures[i] == nil) { if (trueResolveTextures[i] == nil) {
continue; continue;
} }
ASSERT(temporaryResolveTextures[i] != nil); ASSERT(temporaryResolveTextures[i] != nil);
CopyIntoTrueResolveTarget(commandBuffer, trueResolveTextures[i], CopyIntoTrueResolveTarget(commandContext, trueResolveTextures[i],
trueResolveLevels[i], trueResolveSlices[i], trueResolveLevels[i], trueResolveSlices[i],
temporaryResolveTextures[i], width, height, temporaryResolveTextures[i], width, height);
globalEncoders);
} }
return; return;
} }
@ -896,16 +869,16 @@ namespace dawn_native { namespace metal {
// If we found a store + MSAA resolve we need to resolve in a different render pass. // If we found a store + MSAA resolve we need to resolve in a different render pass.
if (hasStoreAndMSAAResolve) { if (hasStoreAndMSAAResolve) {
EncodeRenderPass(commandBuffer, mtlRenderPass, globalEncoders, width, height); EncodeRenderPass(commandContext, mtlRenderPass, width, height);
ResolveInAnotherRenderPass(commandBuffer, mtlRenderPass, resolveTextures); ResolveInAnotherRenderPass(commandContext, mtlRenderPass, resolveTextures);
return; return;
} }
} }
EncodeRenderPassInternal(commandBuffer, mtlRenderPass, width, height); EncodeRenderPassInternal(commandContext, mtlRenderPass, width, height);
} }
void CommandBuffer::EncodeRenderPassInternal(id<MTLCommandBuffer> commandBuffer, void CommandBuffer::EncodeRenderPassInternal(CommandRecordingContext* commandContext,
MTLRenderPassDescriptor* mtlRenderPass, MTLRenderPassDescriptor* mtlRenderPass,
uint32_t width, uint32_t width,
uint32_t height) { uint32_t height) {
@ -916,9 +889,7 @@ namespace dawn_native { namespace metal {
StorageBufferLengthTracker storageBufferLengths = {}; StorageBufferLengthTracker storageBufferLengths = {};
BindGroupTracker bindGroups(&storageBufferLengths); BindGroupTracker bindGroups(&storageBufferLengths);
// This will be autoreleased id<MTLRenderCommandEncoder> encoder = commandContext->BeginRender(mtlRenderPass);
id<MTLRenderCommandEncoder> encoder =
[commandBuffer renderCommandEncoderWithDescriptor:mtlRenderPass];
auto EncodeRenderBundleCommand = [&](CommandIterator* iter, Command type) { auto EncodeRenderBundleCommand = [&](CommandIterator* iter, Command type) {
switch (type) { switch (type) {
@ -1068,7 +1039,7 @@ namespace dawn_native { namespace metal {
switch (type) { switch (type) {
case Command::EndRenderPass: { case Command::EndRenderPass: {
mCommands.NextCommand<EndRenderPassCmd>(); mCommands.NextCommand<EndRenderPassCmd>();
[encoder endEncoding]; commandContext->EndRender();
return; return;
} break; } break;

View File

@ -0,0 +1,59 @@
// Copyright 2020 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef DAWNNATIVE_METAL_COMMANDRECORDINGCONTEXT_H_
#define DAWNNATIVE_METAL_COMMANDRECORDINGCONTEXT_H_
#import <Metal/Metal.h>
namespace dawn_native { namespace metal {
// This class wraps a MTLCommandBuffer and tracks which Metal encoder is open.
// Only one encoder may be open at a time.
class CommandRecordingContext {
public:
CommandRecordingContext();
CommandRecordingContext(id<MTLCommandBuffer> commands);
CommandRecordingContext(const CommandRecordingContext& rhs) = delete;
CommandRecordingContext& operator=(const CommandRecordingContext& rhs) = delete;
CommandRecordingContext(CommandRecordingContext&& rhs);
CommandRecordingContext& operator=(CommandRecordingContext&& rhs);
~CommandRecordingContext();
id<MTLCommandBuffer> GetCommands();
id<MTLCommandBuffer> AcquireCommands();
id<MTLBlitCommandEncoder> EnsureBlit();
void EndBlit();
id<MTLComputeCommandEncoder> BeginCompute();
void EndCompute();
id<MTLRenderCommandEncoder> BeginRender(MTLRenderPassDescriptor* descriptor);
void EndRender();
private:
id<MTLCommandBuffer> mCommands = nil;
id<MTLBlitCommandEncoder> mBlit = nil;
id<MTLComputeCommandEncoder> mCompute = nil;
id<MTLRenderCommandEncoder> mRender = nil;
bool mInEncoder = false;
};
}} // namespace dawn_native::metal
#endif // DAWNNATIVE_METAL_COMMANDRECORDINGCONTEXT_H_

View File

@ -0,0 +1,113 @@
// Copyright 2020 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "dawn_native/metal/CommandRecordingContext.h"
#include "common/Assert.h"
namespace dawn_native { namespace metal {
CommandRecordingContext::CommandRecordingContext() = default;
CommandRecordingContext::CommandRecordingContext(id<MTLCommandBuffer> commands)
: mCommands(commands) {
}
CommandRecordingContext::CommandRecordingContext(CommandRecordingContext&& rhs)
: mCommands(rhs.AcquireCommands()) {
}
CommandRecordingContext& CommandRecordingContext::operator=(CommandRecordingContext&& rhs) {
mCommands = rhs.AcquireCommands();
return *this;
}
CommandRecordingContext::~CommandRecordingContext() {
// Commands must be acquired.
ASSERT(mCommands == nil);
}
id<MTLCommandBuffer> CommandRecordingContext::GetCommands() {
return mCommands;
}
id<MTLCommandBuffer> CommandRecordingContext::AcquireCommands() {
ASSERT(!mInEncoder);
id<MTLCommandBuffer> commands = mCommands;
mCommands = nil;
return commands;
}
id<MTLBlitCommandEncoder> CommandRecordingContext::EnsureBlit() {
ASSERT(mCommands != nil);
if (mBlit == nil) {
ASSERT(!mInEncoder);
mInEncoder = true;
mBlit = [mCommands blitCommandEncoder];
}
return mBlit;
}
void CommandRecordingContext::EndBlit() {
ASSERT(mCommands != nil);
if (mBlit != nil) {
[mBlit endEncoding];
mBlit = nil; // This will be autoreleased.
mInEncoder = false;
}
}
id<MTLComputeCommandEncoder> CommandRecordingContext::BeginCompute() {
ASSERT(mCommands != nil);
ASSERT(mCompute == nil);
ASSERT(!mInEncoder);
mInEncoder = true;
mCompute = [mCommands computeCommandEncoder];
return mCompute;
}
void CommandRecordingContext::EndCompute() {
ASSERT(mCommands != nil);
ASSERT(mCompute != nil);
[mCompute endEncoding];
mCompute = nil; // This will be autoreleased.
mInEncoder = false;
}
id<MTLRenderCommandEncoder> CommandRecordingContext::BeginRender(
MTLRenderPassDescriptor* descriptor) {
ASSERT(mCommands != nil);
ASSERT(mRender == nil);
ASSERT(!mInEncoder);
mInEncoder = true;
mRender = [mCommands renderCommandEncoderWithDescriptor:descriptor];
return mRender;
}
void CommandRecordingContext::EndRender() {
ASSERT(mCommands != nil);
ASSERT(mRender != nil);
[mRender endEncoding];
mRender = nil; // This will be autoreleased.
mInEncoder = false;
}
}} // namespace dawn_native::metal

View File

@ -19,6 +19,7 @@
#include "common/Serial.h" #include "common/Serial.h"
#include "dawn_native/Device.h" #include "dawn_native/Device.h"
#include "dawn_native/metal/CommandRecordingContext.h"
#include "dawn_native/metal/Forward.h" #include "dawn_native/metal/Forward.h"
#import <IOSurface/IOSurfaceRef.h> #import <IOSurface/IOSurfaceRef.h>
@ -48,7 +49,7 @@ namespace dawn_native { namespace metal {
id<MTLDevice> GetMTLDevice(); id<MTLDevice> GetMTLDevice();
id<MTLCommandQueue> GetMTLQueue(); id<MTLCommandQueue> GetMTLQueue();
id<MTLCommandBuffer> GetPendingCommandBuffer(); CommandRecordingContext* GetPendingCommandContext();
Serial GetPendingCommandSerial() const override; Serial GetPendingCommandSerial() const override;
void SubmitPendingCommandBuffer(); void SubmitPendingCommandBuffer();
@ -98,7 +99,7 @@ namespace dawn_native { namespace metal {
std::unique_ptr<MapRequestTracker> mMapTracker; std::unique_ptr<MapRequestTracker> mMapTracker;
Serial mLastSubmittedSerial = 0; Serial mLastSubmittedSerial = 0;
id<MTLCommandBuffer> mPendingCommands = nil; CommandRecordingContext mCommandContext;
// The completed serial is updated in a Metal completion handler that can be fired on a // The completed serial is updated in a Metal completion handler that can be fired on a
// different thread, so it needs to be atomic. // different thread, so it needs to be atomic.

View File

@ -144,7 +144,7 @@ namespace dawn_native { namespace metal {
mDynamicUploader->Deallocate(completedSerial); mDynamicUploader->Deallocate(completedSerial);
mMapTracker->Tick(completedSerial); mMapTracker->Tick(completedSerial);
if (mPendingCommands != nil) { if (mCommandContext.GetCommands() != nil) {
SubmitPendingCommandBuffer(); SubmitPendingCommandBuffer();
} else if (completedSerial == mLastSubmittedSerial) { } else if (completedSerial == mLastSubmittedSerial) {
// If there's no GPU work in flight we still need to artificially increment the serial // If there's no GPU work in flight we still need to artificially increment the serial
@ -164,45 +164,43 @@ namespace dawn_native { namespace metal {
return mCommandQueue; return mCommandQueue;
} }
id<MTLCommandBuffer> Device::GetPendingCommandBuffer() { CommandRecordingContext* Device::GetPendingCommandContext() {
TRACE_EVENT0(GetPlatform(), General, "DeviceMTL::GetPendingCommandBuffer"); if (mCommandContext.GetCommands() == nil) {
if (mPendingCommands == nil) { TRACE_EVENT0(GetPlatform(), General, "[MTLCommandQueue commandBuffer]");
mPendingCommands = [mCommandQueue commandBuffer]; mCommandContext = CommandRecordingContext([mCommandQueue commandBuffer]);
[mPendingCommands retain];
} }
return mPendingCommands; return &mCommandContext;
} }
void Device::SubmitPendingCommandBuffer() { void Device::SubmitPendingCommandBuffer() {
if (mPendingCommands == nil) { if (mCommandContext.GetCommands() == nil) {
return; return;
} }
mLastSubmittedSerial++; mLastSubmittedSerial++;
// Ensure the blit encoder is ended. It may have been opened to perform a lazy clear or
// buffer upload.
mCommandContext.EndBlit();
// Acquire and retain the pending commands. We must keep them alive until scheduled.
id<MTLCommandBuffer> pendingCommands = [mCommandContext.AcquireCommands() retain];
// Replace mLastSubmittedCommands with the mutex held so we avoid races between the // Replace mLastSubmittedCommands with the mutex held so we avoid races between the
// schedule handler and this code. // schedule handler and this code.
{ {
std::lock_guard<std::mutex> lock(mLastSubmittedCommandsMutex); std::lock_guard<std::mutex> lock(mLastSubmittedCommandsMutex);
[mLastSubmittedCommands release]; [mLastSubmittedCommands release];
mLastSubmittedCommands = mPendingCommands; mLastSubmittedCommands = pendingCommands;
} }
// Ok, ObjC blocks are weird. My understanding is that local variables are captured by [pendingCommands addScheduledHandler:^(id<MTLCommandBuffer>) {
// value so this-> works as expected. However it is unclear how members are captured, (are
// they captured using this-> or by value?). To be safe we copy members to local variables
// to ensure they are captured "by value".
// Free mLastSubmittedCommands as soon as it is scheduled so that it doesn't hold
// references to its resources. Make a local copy of pendingCommands first so it is
// captured "by-value" by the block.
id<MTLCommandBuffer> pendingCommands = mPendingCommands;
[mPendingCommands addScheduledHandler:^(id<MTLCommandBuffer>) {
// This is DRF because we hold the mutex for mLastSubmittedCommands and pendingCommands // This is DRF because we hold the mutex for mLastSubmittedCommands and pendingCommands
// is a local value (and not the member itself). // is a local value (and not the member itself).
std::lock_guard<std::mutex> lock(mLastSubmittedCommandsMutex); std::lock_guard<std::mutex> lock(mLastSubmittedCommandsMutex);
if (this->mLastSubmittedCommands == pendingCommands) { if (this->mLastSubmittedCommands == pendingCommands) {
// Free mLastSubmittedCommands as soon as it is scheduled so that it doesn't hold
// references to its resources.
[this->mLastSubmittedCommands release]; [this->mLastSubmittedCommands release];
this->mLastSubmittedCommands = nil; this->mLastSubmittedCommands = nil;
} }
@ -211,7 +209,7 @@ namespace dawn_native { namespace metal {
// Update the completed serial once the completed handler is fired. Make a local copy of // Update the completed serial once the completed handler is fired. Make a local copy of
// mLastSubmittedSerial so it is captured by value. // mLastSubmittedSerial so it is captured by value.
Serial pendingSerial = mLastSubmittedSerial; Serial pendingSerial = mLastSubmittedSerial;
[mPendingCommands addCompletedHandler:^(id<MTLCommandBuffer>) { [pendingCommands addCompletedHandler:^(id<MTLCommandBuffer>) {
TRACE_EVENT_ASYNC_END0(GetPlatform(), GPUWork, "DeviceMTL::SubmitPendingCommandBuffer", TRACE_EVENT_ASYNC_END0(GetPlatform(), GPUWork, "DeviceMTL::SubmitPendingCommandBuffer",
pendingSerial); pendingSerial);
ASSERT(pendingSerial > mCompletedSerial.load()); ASSERT(pendingSerial > mCompletedSerial.load());
@ -220,8 +218,7 @@ namespace dawn_native { namespace metal {
TRACE_EVENT_ASYNC_BEGIN0(GetPlatform(), GPUWork, "DeviceMTL::SubmitPendingCommandBuffer", TRACE_EVENT_ASYNC_BEGIN0(GetPlatform(), GPUWork, "DeviceMTL::SubmitPendingCommandBuffer",
pendingSerial); pendingSerial);
[mPendingCommands commit]; [pendingCommands commit];
mPendingCommands = nil;
} }
MapRequestTracker* Device::GetMapTracker() const { MapRequestTracker* Device::GetMapTracker() const {
@ -242,15 +239,11 @@ namespace dawn_native { namespace metal {
uint64_t size) { uint64_t size) {
id<MTLBuffer> uploadBuffer = ToBackend(source)->GetBufferHandle(); id<MTLBuffer> uploadBuffer = ToBackend(source)->GetBufferHandle();
id<MTLBuffer> buffer = ToBackend(destination)->GetMTLBuffer(); id<MTLBuffer> buffer = ToBackend(destination)->GetMTLBuffer();
id<MTLCommandBuffer> commandBuffer = GetPendingCommandBuffer(); [GetPendingCommandContext()->EnsureBlit() copyFromBuffer:uploadBuffer
id<MTLBlitCommandEncoder> encoder = [commandBuffer blitCommandEncoder];
[encoder copyFromBuffer:uploadBuffer
sourceOffset:sourceOffset sourceOffset:sourceOffset
toBuffer:buffer toBuffer:buffer
destinationOffset:destinationOffset destinationOffset:destinationOffset
size:size]; size:size];
[encoder endEncoding];
return {}; return {};
} }
@ -273,8 +266,7 @@ namespace dawn_native { namespace metal {
} }
MaybeError Device::WaitForIdleForDestruction() { MaybeError Device::WaitForIdleForDestruction() {
[mPendingCommands release]; [mCommandContext.AcquireCommands() release];
mPendingCommands = nil;
// Wait for all commands to be finished so we can free resources // Wait for all commands to be finished so we can free resources
while (GetCompletedCommandSerial() != mLastSubmittedSerial) { while (GetCompletedCommandSerial() != mLastSubmittedSerial) {
@ -285,10 +277,7 @@ namespace dawn_native { namespace metal {
} }
void Device::Destroy() { void Device::Destroy() {
if (mPendingCommands != nil) { [mCommandContext.AcquireCommands() release];
[mPendingCommands release];
mPendingCommands = nil;
}
mMapTracker = nullptr; mMapTracker = nullptr;
mDynamicUploader = nullptr; mDynamicUploader = nullptr;

View File

@ -27,11 +27,11 @@ namespace dawn_native { namespace metal {
MaybeError Queue::SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) { MaybeError Queue::SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) {
Device* device = ToBackend(GetDevice()); Device* device = ToBackend(GetDevice());
device->Tick(); device->Tick();
id<MTLCommandBuffer> commandBuffer = device->GetPendingCommandBuffer(); CommandRecordingContext* commandContext = device->GetPendingCommandContext();
TRACE_EVENT_BEGIN0(GetDevice()->GetPlatform(), Recording, "CommandBufferMTL::FillCommands"); TRACE_EVENT_BEGIN0(GetDevice()->GetPlatform(), Recording, "CommandBufferMTL::FillCommands");
for (uint32_t i = 0; i < commandCount; ++i) { for (uint32_t i = 0; i < commandCount; ++i) {
ToBackend(commands[i])->FillCommands(commandBuffer); ToBackend(commands[i])->FillCommands(commandContext);
} }
TRACE_EVENT_END0(GetDevice()->GetPlatform(), Recording, "CommandBufferMTL::FillCommands"); TRACE_EVENT_END0(GetDevice()->GetPlatform(), Recording, "CommandBufferMTL::FillCommands");