blob: 6833eff1e36604d8f24a7336978edd19a490eeac [file] [log] [blame]
// Copyright 2017 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "dawn/native/d3d12/QueueD3D12.h"
#include <limits>
#include <utility>
#include "dawn/common/Math.h"
#include "dawn/native/CommandValidation.h"
#include "dawn/native/Commands.h"
#include "dawn/native/DynamicUploader.h"
#include "dawn/native/d3d/D3DError.h"
#include "dawn/native/d3d12/CommandBufferD3D12.h"
#include "dawn/native/d3d12/DeviceD3D12.h"
#include "dawn/native/d3d12/SharedFenceD3D12.h"
#include "dawn/native/d3d12/UtilsD3D12.h"
#include "dawn/platform/DawnPlatform.h"
#include "dawn/platform/tracing/TraceEvent.h"
namespace dawn::native::d3d12 {
// static
ResultOrError<Ref<Queue>> Queue::Create(Device* device, const QueueDescriptor* descriptor) {
Ref<Queue> queue = AcquireRef(new Queue(device, descriptor));
DAWN_TRY(queue->Initialize());
return queue;
}
Queue::~Queue() {}
MaybeError Queue::Initialize() {
mFreeAllocators.set();
SetLabelImpl();
ID3D12Device* d3d12Device = ToBackend(GetDevice())->GetD3D12Device();
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
DAWN_TRY(CheckHRESULT(d3d12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)),
"D3D12 create command queue"));
// If PIX is not attached, the QueryInterface fails. Hence, no need to check the return
// value.
mCommandQueue.As(&mD3d12SharingContract);
DAWN_TRY(CheckHRESULT(d3d12Device->CreateFence(uint64_t(kBeginningOfGPUTime),
D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(&mFence)),
"D3D12 create fence"));
DAWN_TRY_ASSIGN(mSharedFence, SharedFence::Create(ToBackend(GetDevice()),
"Internal shared DXGI fence", mFence));
return OpenPendingCommands();
}
void Queue::DestroyImpl() {
// Immediately forget about all pending commands for the case where device is lost on its
// own and WaitForIdleForDestruction isn't called.
mPendingCommands.Release();
mCommandQueue.Reset();
// Release the shared fence here to prevent a ref-cycle with the device, but do not destroy the
// underlying native fence so that we can return a SharedFence on EndAccess after destruction.
mSharedFence = nullptr;
}
ResultOrError<Ref<d3d::SharedFence>> Queue::GetOrCreateSharedFence() {
if (mSharedFence == nullptr) {
DAWN_ASSERT(!IsAlive());
return SharedFence::Create(ToBackend(GetDevice()), "Internal shared DXGI fence", mFence);
}
return mSharedFence;
}
ID3D12CommandQueue* Queue::GetCommandQueue() const {
return mCommandQueue.Get();
}
ID3D12SharingContract* Queue::GetSharingContract() const {
return mD3d12SharingContract.Get();
}
MaybeError Queue::SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) {
CommandRecordingContext* commandContext = GetPendingCommandContext();
ExecutionSerial pendingSerial = GetPendingCommandSerial();
TRACE_EVENT_BEGIN1(GetDevice()->GetPlatform(), Recording, "CommandBufferD3D12::RecordCommands",
"serial", uint64_t(pendingSerial));
for (uint32_t i = 0; i < commandCount; ++i) {
DAWN_TRY(ToBackend(commands[i])->RecordCommands(commandContext));
}
TRACE_EVENT_END1(GetDevice()->GetPlatform(), Recording, "CommandBufferD3D12::RecordCommands",
"serial", uint64_t(pendingSerial));
return SubmitPendingCommands();
}
MaybeError Queue::SubmitPendingCommands() {
Device* device = ToBackend(GetDevice());
DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded());
if (!mPendingCommands.NeedsSubmit()) {
return {};
}
DAWN_TRY(mPendingCommands.ExecuteCommandList(device, mCommandQueue.Get()));
RecycleLastCommandListAfter(GetPendingCommandSerial());
// Immediately reopen the command recording context so it is always available.
DAWN_TRY(NextSerial());
DAWN_TRY(RecycleUnusedCommandLists());
return OpenPendingCommands();
}
MaybeError Queue::NextSerial() {
Device* device = ToBackend(GetDevice());
DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded());
// NextSerial should not ever be called with a command list that needs submission since the
// underlying command allocator could be recycled after the serial completes on the GPU.
DAWN_ASSERT(!mPendingCommands.NeedsSubmit());
IncrementLastSubmittedCommandSerial();
TRACE_EVENT1(device->GetPlatform(), General, "D3D12Device::SignalFence", "serial",
uint64_t(GetLastSubmittedCommandSerial()));
return CheckHRESULT(
mCommandQueue->Signal(mFence.Get(), uint64_t(GetLastSubmittedCommandSerial())),
"D3D12 command queue signal fence");
}
MaybeError Queue::WaitForSerial(ExecutionSerial serial) {
if (GetCompletedCommandSerial() >= serial) {
return {};
}
DAWN_TRY_ASSIGN(std::ignore,
WaitForQueueSerial(serial, std::numeric_limits<Nanoseconds>::max()));
return CheckPassedSerials();
}
bool Queue::HasPendingCommands() const {
return mPendingCommands.NeedsSubmit();
}
ResultOrError<ExecutionSerial> Queue::CheckAndUpdateCompletedSerials() {
ExecutionSerial completedSerial = ExecutionSerial(mFence->GetCompletedValue());
if (DAWN_UNLIKELY(completedSerial == ExecutionSerial(UINT64_MAX))) {
// GetCompletedValue returns UINT64_MAX if the device was removed.
// Try to query the failure reason.
ID3D12Device* d3d12Device = ToBackend(GetDevice())->GetD3D12Device();
DAWN_TRY(CheckHRESULT(d3d12Device->GetDeviceRemovedReason(),
"ID3D12Device::GetDeviceRemovedReason"));
// Otherwise, return a generic device lost error.
return DAWN_DEVICE_LOST_ERROR("Device lost");
}
if (completedSerial <= GetCompletedCommandSerial()) {
return ExecutionSerial(0);
}
DAWN_TRY(RecycleSystemEventReceivers(completedSerial));
return completedSerial;
}
void Queue::ForceEventualFlushOfCommands() {
mPendingCommands.SetNeedsSubmit();
}
MaybeError Queue::WaitForIdleForDestruction() {
// Immediately forget about all pending commands
mPendingCommands.Release();
// Wait for all in-flight commands to finish executing and clean
DAWN_TRY(NextSerial());
DAWN_TRY(WaitForSerial(GetLastSubmittedCommandSerial()));
// Clean up after waiting for the commands.
return RecycleUnusedCommandLists();
}
CommandRecordingContext* Queue::GetPendingCommandContext(SubmitMode submitMode) {
if (submitMode == SubmitMode::Normal) {
mPendingCommands.SetNeedsSubmit();
}
return &mPendingCommands;
}
void Queue::SetLabelImpl() {
Device* device = ToBackend(GetDevice());
// TODO(crbug.com/dawn/1344): When we start using multiple queues this needs to be adjusted
// so it doesn't always change the default queue's label.
SetDebugName(device, mCommandQueue.Get(), "Dawn_Queue", GetLabel());
}
void Queue::SetEventOnCompletion(ExecutionSerial serial, HANDLE event) {
mFence->SetEventOnCompletion(static_cast<uint64_t>(serial), event);
}
MaybeError Queue::OpenPendingCommands() {
DAWN_ASSERT(mLastAllocatorUsed == kNoCommandAllocator);
// If there are no free allocators, get the oldest serial in flight and wait on it
if (mFreeAllocators.none()) {
const ExecutionSerial firstSerial = mInFlightCommandAllocators.FirstSerial();
DAWN_TRY(WaitForSerial(firstSerial));
DAWN_TRY(RecycleUnusedCommandLists());
}
DAWN_ASSERT(mFreeAllocators.any());
ID3D12Device* d3d12Device = ToBackend(GetDevice())->GetD3D12Device();
// Get the index of the first free allocator from the bitset
uint32_t freeIndex = *(IterateBitSet(mFreeAllocators).begin());
mFreeAllocators.reset(freeIndex);
auto& allocator = mCommandAllocators[freeIndex];
// Lazily create an allocator or reset an existing one to start a new command list on it.
if (freeIndex >= mAllocatorCount) {
DAWN_ASSERT(freeIndex == mAllocatorCount);
mAllocatorCount++;
DAWN_TRY(
CheckHRESULT(d3d12Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(&allocator.allocator)),
"D3D12 create command allocator"));
DAWN_TRY(CheckHRESULT(d3d12Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,
allocator.allocator.Get(), nullptr,
IID_PPV_ARGS(&allocator.list)),
"D3D12 creating direct command list"));
} else {
DAWN_TRY(CheckHRESULT(allocator.list->Reset(allocator.allocator.Get(), nullptr),
"D3D12 resetting command list"));
}
mLastAllocatorUsed = freeIndex;
mPendingCommands.Open(mCommandAllocators[mLastAllocatorUsed].list);
return {};
}
void Queue::RecycleLastCommandListAfter(ExecutionSerial serial) {
mInFlightCommandAllocators.Enqueue(mLastAllocatorUsed, serial);
mLastAllocatorUsed = kNoCommandAllocator;
}
MaybeError Queue::RecycleUnusedCommandLists() {
ExecutionSerial completedSerial = GetCompletedCommandSerial();
for (auto index : mInFlightCommandAllocators.IterateUpTo(completedSerial)) {
DAWN_TRY(CheckHRESULT(mCommandAllocators[index].allocator->Reset(),
"D3D12 reset command allocator"));
mFreeAllocators.set(index);
}
mInFlightCommandAllocators.ClearUpTo(completedSerial);
return {};
}
} // namespace dawn::native::d3d12