diff --git a/BUILD.gn b/BUILD.gn index 99d1ae4af9..19bc99820d 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -451,6 +451,8 @@ source_set("libdawn_native_sources") { "src/dawn_native/ComputePipeline.h", "src/dawn_native/Device.cpp", "src/dawn_native/Device.h", + "src/dawn_native/DynamicUploader.cpp", + "src/dawn_native/DynamicUploader.h", "src/dawn_native/Error.cpp", "src/dawn_native/Error.h", "src/dawn_native/ErrorData.cpp", @@ -485,10 +487,14 @@ source_set("libdawn_native_sources") { "src/dawn_native/RenderPassEncoder.h", "src/dawn_native/RenderPipeline.cpp", "src/dawn_native/RenderPipeline.h", + "src/dawn_native/RingBuffer.cpp", + "src/dawn_native/RingBuffer.h", "src/dawn_native/Sampler.cpp", "src/dawn_native/Sampler.h", "src/dawn_native/ShaderModule.cpp", "src/dawn_native/ShaderModule.h", + "src/dawn_native/StagingBuffer.cpp", + "src/dawn_native/StagingBuffer.h", "src/dawn_native/SwapChain.cpp", "src/dawn_native/SwapChain.h", "src/dawn_native/Texture.cpp", @@ -533,12 +539,12 @@ source_set("libdawn_native_sources") { "src/dawn_native/d3d12/RenderPipelineD3D12.h", "src/dawn_native/d3d12/ResourceAllocator.cpp", "src/dawn_native/d3d12/ResourceAllocator.h", - "src/dawn_native/d3d12/ResourceUploader.cpp", - "src/dawn_native/d3d12/ResourceUploader.h", "src/dawn_native/d3d12/SamplerD3D12.cpp", "src/dawn_native/d3d12/SamplerD3D12.h", "src/dawn_native/d3d12/ShaderModuleD3D12.cpp", "src/dawn_native/d3d12/ShaderModuleD3D12.h", + "src/dawn_native/d3d12/StagingBufferD3D12.cpp", + "src/dawn_native/d3d12/StagingBufferD3D12.h", "src/dawn_native/d3d12/SwapChainD3D12.cpp", "src/dawn_native/d3d12/SwapChainD3D12.h", "src/dawn_native/d3d12/TextureCopySplitter.cpp", @@ -899,6 +905,7 @@ test("dawn_unittests") { "src/tests/unittests/PerStageTests.cpp", "src/tests/unittests/RefCountedTests.cpp", "src/tests/unittests/ResultTests.cpp", + "src/tests/unittests/RingBufferTests.cpp", "src/tests/unittests/SerialMapTests.cpp", "src/tests/unittests/SerialQueueTests.cpp", "src/tests/unittests/ToBackendTests.cpp", diff --git a/src/common/SerialStorage.h b/src/common/SerialStorage.h index 60fae47b4f..6f382134a7 100644 --- a/src/common/SerialStorage.h +++ b/src/common/SerialStorage.h @@ -110,6 +110,7 @@ class SerialStorage { void ClearUpTo(Serial serial); Serial FirstSerial() const; + Serial LastSerial() const; protected: // Returns the first StorageIterator that a serial bigger than serial. @@ -162,6 +163,12 @@ Serial SerialStorage::FirstSerial() const { return mStorage.begin()->first; } +template +Serial SerialStorage::LastSerial() const { + DAWN_ASSERT(!Empty()); + return mStorage.back().first; +} + template typename SerialStorage::ConstStorageIterator SerialStorage::FindUpTo( Serial serial) const { diff --git a/src/dawn_native/Buffer.cpp b/src/dawn_native/Buffer.cpp index 887356775a..543d62e95a 100644 --- a/src/dawn_native/Buffer.cpp +++ b/src/dawn_native/Buffer.cpp @@ -106,7 +106,9 @@ namespace dawn_native { return; } - SetSubDataImpl(start, count, data); + if (GetDevice()->ConsumedError(SetSubDataImpl(start, count, data))) { + return; + } } void BufferBase::MapReadAsync(uint32_t start, diff --git a/src/dawn_native/Buffer.h b/src/dawn_native/Buffer.h index 9a3fa2cc46..4472cecbbc 100644 --- a/src/dawn_native/Buffer.h +++ b/src/dawn_native/Buffer.h @@ -63,7 +63,7 @@ namespace dawn_native { void CallMapWriteCallback(uint32_t serial, dawnBufferMapAsyncStatus status, void* pointer); private: - virtual void SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) = 0; + virtual MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) = 0; virtual void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t size) = 0; virtual void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t size) = 0; virtual void UnmapImpl() = 0; diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h index 82e2cf8629..fc79075880 100644 --- a/src/dawn_native/Device.h +++ b/src/dawn_native/Device.h @@ -31,6 +31,8 @@ namespace dawn_native { class AdapterBase; class FenceSignalTracker; + class DynamicUploader; + class StagingBufferBase; class DeviceBase { public: @@ -60,6 +62,7 @@ namespace dawn_native { virtual Serial GetCompletedCommandSerial() const = 0; virtual Serial GetLastSubmittedCommandSerial() const = 0; + virtual Serial GetPendingCommandSerial() const = 0; virtual void TickImpl() = 0; // Many Dawn objects are completely immutable once created which means that if two @@ -111,6 +114,14 @@ namespace dawn_native { virtual const PCIInfo& GetPCIInfo() const; + virtual ResultOrError> CreateStagingBuffer( + size_t size) = 0; + virtual MaybeError CopyFromStagingToBuffer(StagingBufferBase* source, + uint32_t sourceOffset, + BufferBase* destination, + uint32_t destinationOffset, + uint32_t size) = 0; + private: virtual ResultOrError CreateBindGroupImpl( const BindGroupDescriptor* descriptor) = 0; diff --git a/src/dawn_native/DynamicUploader.cpp b/src/dawn_native/DynamicUploader.cpp new file mode 100644 index 0000000000..e127db453d --- /dev/null +++ b/src/dawn_native/DynamicUploader.cpp @@ -0,0 +1,82 @@ +// Copyright 2018 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/DynamicUploader.h" +#include "common/Math.h" +#include "dawn_native/Device.h" + +namespace dawn_native { + + DynamicUploader::DynamicUploader(DeviceBase* device) : mDevice(device) { + } + + MaybeError DynamicUploader::CreateAndAppendBuffer(size_t size) { + std::unique_ptr ringBuffer = std::make_unique(mDevice, size); + DAWN_TRY(ringBuffer->Initialize()); + mRingBuffers.emplace_back(std::move(ringBuffer)); + return {}; + } + + ResultOrError DynamicUploader::Allocate(uint32_t size, uint32_t alignment) { + ASSERT(IsPowerOfTwo(alignment)); + + // Align the requested allocation size + const size_t alignedSize = Align(size, alignment); + + RingBuffer* largestRingBuffer = GetLargestBuffer(); + UploadHandle uploadHandle = largestRingBuffer->SubAllocate(alignedSize); + + // Upon failure, append a newly created (and much larger) ring buffer to fulfill the + // request. + if (uploadHandle.mappedBuffer == nullptr) { + // Compute the new max size (in powers of two to preserve alignment). + size_t newMaxSize = largestRingBuffer->GetSize() * 2; + while (newMaxSize < size) { + newMaxSize *= 2; + } + + // TODO(b-brber): Fall-back to no sub-allocations should this fail. + DAWN_TRY(CreateAndAppendBuffer(newMaxSize)); + largestRingBuffer = GetLargestBuffer(); + uploadHandle = largestRingBuffer->SubAllocate(alignedSize); + } + + uploadHandle.stagingBuffer = largestRingBuffer->GetStagingBuffer(); + + return uploadHandle; + } + + void DynamicUploader::Tick(Serial lastCompletedSerial) { + // Reclaim memory within the ring buffers by ticking (or removing requests no longer + // in-flight). + for (size_t i = 0; i < mRingBuffers.size(); ++i) { + mRingBuffers[i]->Tick(lastCompletedSerial); + + // Never erase the last buffer as to prevent re-creating smaller buffers + // again. The last buffer is the largest. + if (mRingBuffers[i]->Empty() && i < mRingBuffers.size() - 1) { + mRingBuffers.erase(mRingBuffers.begin() + i); + } + } + } + + RingBuffer* DynamicUploader::GetLargestBuffer() { + ASSERT(!mRingBuffers.empty()); + return mRingBuffers.back().get(); + } + + bool DynamicUploader::IsEmpty() const { + return mRingBuffers.empty(); + } +} // namespace dawn_native \ No newline at end of file diff --git a/src/dawn_native/DynamicUploader.h b/src/dawn_native/DynamicUploader.h new file mode 100644 index 0000000000..848c07a2df --- /dev/null +++ b/src/dawn_native/DynamicUploader.h @@ -0,0 +1,47 @@ +// Copyright 2018 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_DYNAMICUPLOADER_H_ +#define DAWNNATIVE_DYNAMICUPLOADER_H_ + +#include "dawn_native/Forward.h" +#include "dawn_native/RingBuffer.h" + +#include + +// DynamicUploader is the front-end implementation used to manage multiple ring buffers for upload +// usage. +namespace dawn_native { + + class DynamicUploader { + public: + DynamicUploader(DeviceBase* device); + ~DynamicUploader() = default; + + ResultOrError Allocate(uint32_t requiredSize, uint32_t alignment); + void Tick(Serial lastCompletedSerial); + + RingBuffer* GetLargestBuffer(); + + MaybeError CreateAndAppendBuffer(size_t size); + + bool IsEmpty() const; + + private: + std::vector> mRingBuffers; + DeviceBase* mDevice; + }; +} // namespace dawn_native + +#endif // DAWNNATIVE_DYNAMICUPLOADER_H_ \ No newline at end of file diff --git a/src/dawn_native/Forward.h b/src/dawn_native/Forward.h index 5373f66232..73a2555de5 100644 --- a/src/dawn_native/Forward.h +++ b/src/dawn_native/Forward.h @@ -44,6 +44,7 @@ namespace dawn_native { class SamplerBase; class ShaderModuleBase; class ShaderModuleBuilder; + class StagingBufferBase; class SwapChainBase; class SwapChainBuilder; class TextureBase; diff --git a/src/dawn_native/RingBuffer.cpp b/src/dawn_native/RingBuffer.cpp new file mode 100644 index 0000000000..51e97c2841 --- /dev/null +++ b/src/dawn_native/RingBuffer.cpp @@ -0,0 +1,148 @@ +// Copyright 2018 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/RingBuffer.h" +#include "dawn_native/Device.h" + +// Note: Current RingBuffer implementation uses two indices (start and end) to implement a circular +// queue. However, this approach defines a full queue when one element is still unused. +// +// For example, [E,E,E,E] would be equivelent to [U,U,U,U]. +// ^ ^ +// S=E=1 S=E=1 +// +// The latter case is eliminated by counting used bytes >= capacity. This definition prevents +// (the last) byte and requires an extra variable to count used bytes. Alternatively, we could use +// only two indices that keep increasing (unbounded) but can be still indexed using bit masks. +// However, this 1) requires the size to always be a power-of-two and 2) remove tests that check +// used bytes. +// TODO(b-brber): Follow-up with ringbuffer optimization. +namespace dawn_native { + + static constexpr size_t INVALID_OFFSET = std::numeric_limits::max(); + + RingBuffer::RingBuffer(DeviceBase* device, size_t size) : mBufferSize(size), mDevice(device) { + } + + MaybeError RingBuffer::Initialize() { + DAWN_TRY_ASSIGN(mStagingBuffer, mDevice->CreateStagingBuffer(mBufferSize)); + DAWN_TRY(mStagingBuffer->Initialize()); + return {}; + } + + // Record allocations in a request when serial advances. + // This method has been split from Tick() for testing. + void RingBuffer::Track() { + if (mCurrentRequestSize == 0) + return; + const Serial currentSerial = mDevice->GetPendingCommandSerial(); + if (mInflightRequests.Empty() || currentSerial > mInflightRequests.LastSerial()) { + Request request; + request.endOffset = mUsedEndOffset; + request.size = mCurrentRequestSize; + + mInflightRequests.Enqueue(std::move(request), currentSerial); + mCurrentRequestSize = 0; // reset + } + } + + void RingBuffer::Tick(Serial lastCompletedSerial) { + Track(); + + // Reclaim memory from previously recorded blocks. + for (Request& request : mInflightRequests.IterateUpTo(lastCompletedSerial)) { + mUsedStartOffset = request.endOffset; + mUsedSize -= request.size; + } + + // Dequeue previously recorded requests. + mInflightRequests.ClearUpTo(lastCompletedSerial); + } + + size_t RingBuffer::GetSize() const { + return mBufferSize; + } + + size_t RingBuffer::GetUsedSize() const { + return mUsedSize; + } + + bool RingBuffer::Empty() const { + return mInflightRequests.Empty(); + } + + StagingBufferBase* RingBuffer::GetStagingBuffer() const { + ASSERT(mStagingBuffer != nullptr); + return mStagingBuffer.get(); + } + + // Sub-allocate the ring-buffer by requesting a chunk of the specified size. + // This is a serial-based resource scheme, the life-span of resources (and the allocations) get + // tracked by GPU progress via serials. Memory can be reused by determining if the GPU has + // completed up to a given serial. Each sub-allocation request is tracked in the serial offset + // queue, which identifies an existing (or new) frames-worth of resources. Internally, the + // ring-buffer maintains offsets of 3 "memory" states: Free, Reclaimed, and Used. This is done + // in FIFO order as older frames would free resources before newer ones. + UploadHandle RingBuffer::SubAllocate(size_t allocSize) { + ASSERT(mStagingBuffer != nullptr); + + // Check if the buffer is full by comparing the used size. + // If the buffer is not split where waste occurs (e.g. cannot fit new sub-alloc in front), a + // subsequent sub-alloc could fail where the used size was previously adjusted to include + // the wasted. + if (mUsedSize >= mBufferSize) + return UploadHandle{}; + + size_t startOffset = INVALID_OFFSET; + + // Check if the buffer is NOT split (i.e sub-alloc on ends) + if (mUsedStartOffset <= mUsedEndOffset) { + // Order is important (try to sub-alloc at end first). + // This is due to FIFO order where sub-allocs are inserted from left-to-right (when not + // wrapped). + if (mUsedEndOffset + allocSize <= mBufferSize) { + startOffset = mUsedEndOffset; + mUsedEndOffset += allocSize; + mUsedSize += allocSize; + mCurrentRequestSize += allocSize; + } else if (allocSize <= mUsedStartOffset) { // Try to sub-alloc at front. + // Count the space at front in the request size so that a subsequent + // sub-alloc cannot not succeed when the buffer is full. + const size_t requestSize = (mBufferSize - mUsedEndOffset) + allocSize; + + startOffset = 0; + mUsedEndOffset = allocSize; + mUsedSize += requestSize; + mCurrentRequestSize += requestSize; + } + } else if (mUsedEndOffset + allocSize <= + mUsedStartOffset) { // Otherwise, buffer is split where sub-alloc must be + // in-between. + startOffset = mUsedEndOffset; + mUsedEndOffset += allocSize; + mUsedSize += allocSize; + mCurrentRequestSize += allocSize; + } + + if (startOffset == INVALID_OFFSET) + return UploadHandle{}; + + UploadHandle uploadHandle; + uploadHandle.mappedBuffer = + static_cast(mStagingBuffer->GetMappedPointer()) + startOffset; + uploadHandle.startOffset = startOffset; + + return uploadHandle; + } +} // namespace dawn_native \ No newline at end of file diff --git a/src/dawn_native/RingBuffer.h b/src/dawn_native/RingBuffer.h new file mode 100644 index 0000000000..dfa44f535e --- /dev/null +++ b/src/dawn_native/RingBuffer.h @@ -0,0 +1,72 @@ +// Copyright 2018 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_RINGBUFFER_H_ +#define DAWNNATIVE_RINGBUFFER_H_ + +#include "common/SerialQueue.h" +#include "dawn_native/StagingBuffer.h" + +// RingBuffer is the front-end implementation used to manage a ring buffer in GPU memory. +namespace dawn_native { + + struct UploadHandle { + uint8_t* mappedBuffer = nullptr; + size_t startOffset = 0; + StagingBufferBase* stagingBuffer = nullptr; + }; + + class DeviceBase; + + class RingBuffer { + public: + RingBuffer(DeviceBase* device, size_t size); + ~RingBuffer() = default; + + MaybeError Initialize(); + + UploadHandle SubAllocate(size_t requestedSize); + + void Tick(Serial lastCompletedSerial); + size_t GetSize() const; + bool Empty() const; + size_t GetUsedSize() const; + StagingBufferBase* GetStagingBuffer() const; + + // Seperated for testing. + void Track(); + + private: + std::unique_ptr mStagingBuffer; + + struct Request { + size_t endOffset; + size_t size; + }; + + SerialQueue mInflightRequests; // Queue of the recorded sub-alloc requests (e.g. + // frame of resources). + + size_t mUsedEndOffset = 0; // Tail of used sub-alloc requests (in bytes). + size_t mUsedStartOffset = 0; // Head of used sub-alloc requests (in bytes). + size_t mBufferSize = 0; // Max size of the ring buffer (in bytes). + size_t mUsedSize = 0; // Size of the sub-alloc requests (in bytes) of the ring buffer. + size_t mCurrentRequestSize = + 0; // Size of the sub-alloc requests (in bytes) of the current serial. + + DeviceBase* mDevice; + }; +} // namespace dawn_native + +#endif // DAWNNATIVE_RINGBUFFER_H_ \ No newline at end of file diff --git a/src/dawn_native/StagingBuffer.cpp b/src/dawn_native/StagingBuffer.cpp new file mode 100644 index 0000000000..51f5fa8c5e --- /dev/null +++ b/src/dawn_native/StagingBuffer.cpp @@ -0,0 +1,29 @@ +// Copyright 2018 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/StagingBuffer.h" + +namespace dawn_native { + + StagingBufferBase::StagingBufferBase(size_t size) : mBufferSize(size) { + } + + size_t StagingBufferBase::GetSize() const { + return mBufferSize; + } + + void* StagingBufferBase::GetMappedPointer() const { + return mMappedPointer; + } +} // namespace dawn_native \ No newline at end of file diff --git a/src/dawn_native/StagingBuffer.h b/src/dawn_native/StagingBuffer.h new file mode 100644 index 0000000000..1da8900a12 --- /dev/null +++ b/src/dawn_native/StagingBuffer.h @@ -0,0 +1,41 @@ +// Copyright 2018 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_STAGINGBUFFER_H_ +#define DAWNNATIVE_STAGINGBUFFER_H_ + +#include "dawn_native/Error.h" + +namespace dawn_native { + + class StagingBufferBase { + public: + StagingBufferBase(size_t size); + virtual ~StagingBufferBase() = default; + + virtual MaybeError Initialize() = 0; + + void* GetMappedPointer() const; + size_t GetSize() const; + + protected: + void* mMappedPointer = nullptr; + + private: + const size_t mBufferSize; + }; + +} // namespace dawn_native + +#endif // DAWNNATIVE_STAGINGBUFFER_H_ \ No newline at end of file diff --git a/src/dawn_native/ToBackend.h b/src/dawn_native/ToBackend.h index 585a40dc93..1ad86a8403 100644 --- a/src/dawn_native/ToBackend.h +++ b/src/dawn_native/ToBackend.h @@ -93,6 +93,11 @@ namespace dawn_native { using BackendType = typename BackendTraits::ShaderModuleType; }; + template + struct ToBackendTraits { + using BackendType = typename BackendTraits::StagingBufferType; + }; + template struct ToBackendTraits { using BackendType = typename BackendTraits::TextureType; diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp index 4b75d1f48f..69c25aae2d 100644 --- a/src/dawn_native/d3d12/BufferD3D12.cpp +++ b/src/dawn_native/d3d12/BufferD3D12.cpp @@ -17,9 +17,9 @@ #include "common/Assert.h" #include "common/Constants.h" #include "common/Math.h" +#include "dawn_native/DynamicUploader.h" #include "dawn_native/d3d12/DeviceD3D12.h" #include "dawn_native/d3d12/ResourceAllocator.h" -#include "dawn_native/d3d12/ResourceUploader.h" namespace dawn_native { namespace d3d12 { @@ -161,11 +161,22 @@ namespace dawn_native { namespace d3d12 { } } - void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) { + MaybeError Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) { Device* device = ToBackend(GetDevice()); - TransitionUsageNow(device->GetPendingCommandList(), dawn::BufferUsageBit::TransferDst); - device->GetResourceUploader()->BufferSubData(mResource, start, count, data); + DynamicUploader* uploader = nullptr; + DAWN_TRY_ASSIGN(uploader, device->GetDynamicUploader()); + + UploadHandle uploadHandle; + DAWN_TRY_ASSIGN(uploadHandle, uploader->Allocate(count, kDefaultAlignment)); + ASSERT(uploadHandle.mappedBuffer != nullptr); + + memcpy(uploadHandle.mappedBuffer, data, count); + + DAWN_TRY(device->CopyFromStagingToBuffer(uploadHandle.stagingBuffer, + uploadHandle.startOffset, this, start, count)); + + return {}; } void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) { diff --git a/src/dawn_native/d3d12/BufferD3D12.h b/src/dawn_native/d3d12/BufferD3D12.h index 633d2602ea..753aac456b 100644 --- a/src/dawn_native/d3d12/BufferD3D12.h +++ b/src/dawn_native/d3d12/BufferD3D12.h @@ -39,11 +39,15 @@ namespace dawn_native { namespace d3d12 { private: // Dawn API - void SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override; + MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override; void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override; void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override; void UnmapImpl() override; + // TODO(b-brber): Remove once alignment constraint is added to validation (dawn:73). + static constexpr size_t kDefaultAlignment = + 4; // D3D does not specify so we assume 4-byte alignment to be safe. + ComPtr mResource; bool mFixedResourceState = false; dawn::BufferUsageBit mLastUsage = dawn::BufferUsageBit::None; diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp index a72ec89020..0ad3f4d1af 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.cpp +++ b/src/dawn_native/d3d12/DeviceD3D12.cpp @@ -16,6 +16,7 @@ #include "common/Assert.h" #include "dawn_native/BackendConnection.h" +#include "dawn_native/DynamicUploader.h" #include "dawn_native/d3d12/BindGroupD3D12.h" #include "dawn_native/d3d12/BindGroupLayoutD3D12.h" #include "dawn_native/d3d12/BufferD3D12.h" @@ -30,9 +31,9 @@ #include "dawn_native/d3d12/RenderPassDescriptorD3D12.h" #include "dawn_native/d3d12/RenderPipelineD3D12.h" #include "dawn_native/d3d12/ResourceAllocator.h" -#include "dawn_native/d3d12/ResourceUploader.h" #include "dawn_native/d3d12/SamplerD3D12.h" #include "dawn_native/d3d12/ShaderModuleD3D12.h" +#include "dawn_native/d3d12/StagingBufferD3D12.h" #include "dawn_native/d3d12/SwapChainD3D12.h" #include "dawn_native/d3d12/TextureD3D12.h" @@ -134,7 +135,7 @@ namespace dawn_native { namespace d3d12 { mDescriptorHeapAllocator = std::make_unique(this); mMapRequestTracker = std::make_unique(this); mResourceAllocator = std::make_unique(this); - mResourceUploader = std::make_unique(this); + mDynamicUploader = std::make_unique(this); NextSerial(); } @@ -176,10 +177,6 @@ namespace dawn_native { namespace d3d12 { return mResourceAllocator.get(); } - ResourceUploader* Device::GetResourceUploader() { - return mResourceUploader.get(); - } - void Device::OpenCommandList(ComPtr* commandList) { ComPtr& cmdList = *commandList; if (!cmdList) { @@ -223,6 +220,7 @@ namespace dawn_native { namespace d3d12 { mDescriptorHeapAllocator->Tick(mCompletedSerial); mMapRequestTracker->Tick(mCompletedSerial); mUsedComObjectRefs.ClearUpTo(mCompletedSerial); + mDynamicUploader->Tick(mCompletedSerial); ExecuteCommandLists({}); NextSerial(); } @@ -335,4 +333,33 @@ namespace dawn_native { namespace d3d12 { mPCIInfo.name = converter.to_bytes(adapterDesc.Description); } + ResultOrError> Device::CreateStagingBuffer(size_t size) { + std::unique_ptr stagingBuffer = + std::make_unique(size, this); + return std::move(stagingBuffer); + } + + MaybeError Device::CopyFromStagingToBuffer(StagingBufferBase* source, + uint32_t sourceOffset, + BufferBase* destination, + uint32_t destinationOffset, + uint32_t size) { + ToBackend(destination) + ->TransitionUsageNow(GetPendingCommandList(), dawn::BufferUsageBit::TransferDst); + + GetPendingCommandList()->CopyBufferRegion( + ToBackend(destination)->GetD3D12Resource().Get(), destinationOffset, + ToBackend(source)->GetResource(), sourceOffset, size); + + return {}; + } + + ResultOrError Device::GetDynamicUploader() const { + // TODO(b-brber): Refactor this into device init once moved into DeviceBase. + if (mDynamicUploader->IsEmpty()) { + DAWN_TRY(mDynamicUploader->CreateAndAppendBuffer(kDefaultUploadBufferSize)); + } + return mDynamicUploader.get(); + } + }} // namespace dawn_native::d3d12 diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h index 8230227779..be489d9e3b 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.h +++ b/src/dawn_native/d3d12/DeviceD3D12.h @@ -31,7 +31,6 @@ namespace dawn_native { namespace d3d12 { class MapRequestTracker; class PlatformFunctions; class ResourceAllocator; - class ResourceUploader; void ASSERT_SUCCESS(HRESULT hr); @@ -61,11 +60,10 @@ namespace dawn_native { namespace d3d12 { MapRequestTracker* GetMapRequestTracker() const; const PlatformFunctions* GetFunctions(); ResourceAllocator* GetResourceAllocator(); - ResourceUploader* GetResourceUploader(); void OpenCommandList(ComPtr* commandList); ComPtr GetPendingCommandList(); - Serial GetPendingCommandSerial() const; + Serial GetPendingCommandSerial() const override; void NextSerial(); void WaitForSerial(Serial serial); @@ -74,6 +72,15 @@ namespace dawn_native { namespace d3d12 { void ExecuteCommandLists(std::initializer_list commandLists); + ResultOrError> CreateStagingBuffer(size_t size) override; + MaybeError CopyFromStagingToBuffer(StagingBufferBase* source, + uint32_t sourceOffset, + BufferBase* destination, + uint32_t destinationOffset, + uint32_t size) override; + + ResultOrError GetDynamicUploader() const; + private: ResultOrError CreateBindGroupImpl( const BindGroupDescriptor* descriptor) override; @@ -121,9 +128,11 @@ namespace dawn_native { namespace d3d12 { std::unique_ptr mDescriptorHeapAllocator; std::unique_ptr mMapRequestTracker; std::unique_ptr mResourceAllocator; - std::unique_ptr mResourceUploader; + std::unique_ptr mDynamicUploader; dawn_native::PCIInfo mPCIInfo; + + static constexpr size_t kDefaultUploadBufferSize = 64000; // DXGI min heap size is 64kB. }; }} // namespace dawn_native::d3d12 diff --git a/src/dawn_native/d3d12/Forward.h b/src/dawn_native/d3d12/Forward.h index d4fa3335b4..d803900c07 100644 --- a/src/dawn_native/d3d12/Forward.h +++ b/src/dawn_native/d3d12/Forward.h @@ -33,6 +33,7 @@ namespace dawn_native { namespace d3d12 { class RenderPipeline; class Sampler; class ShaderModule; + class StagingBuffer; class SwapChain; class Texture; class TextureView; @@ -52,6 +53,7 @@ namespace dawn_native { namespace d3d12 { using RenderPipelineType = RenderPipeline; using SamplerType = Sampler; using ShaderModuleType = ShaderModule; + using StagingBufferType = StagingBuffer; using SwapChainType = SwapChain; using TextureType = Texture; using TextureViewType = TextureView; diff --git a/src/dawn_native/d3d12/ResourceUploader.cpp b/src/dawn_native/d3d12/ResourceUploader.cpp deleted file mode 100644 index 6aa9a6b950..0000000000 --- a/src/dawn_native/d3d12/ResourceUploader.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2017 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/d3d12/ResourceUploader.h" - -#include "dawn_native/d3d12/DeviceD3D12.h" -#include "dawn_native/d3d12/ResourceAllocator.h" - -namespace dawn_native { namespace d3d12 { - - ResourceUploader::ResourceUploader(Device* device) : mDevice(device) { - } - - void ResourceUploader::BufferSubData(ComPtr resource, - uint32_t start, - uint32_t count, - const void* data) { - // TODO(enga@google.com): Use a handle to a subset of a large ring buffer. On Release, - // decrease reference count on the ring buffer and free when 0. Alternatively, the - // SerialQueue could be used to track which last point of the ringbuffer is in use, and - // start reusing chunks of it that aren't in flight. - UploadHandle uploadHandle = GetUploadBuffer(count); - memcpy(uploadHandle.mappedBuffer, data, count); - mDevice->GetPendingCommandList()->CopyBufferRegion(resource.Get(), start, - uploadHandle.resource.Get(), 0, count); - Release(uploadHandle); - } - - ResourceUploader::UploadHandle ResourceUploader::GetUploadBuffer(uint32_t requiredSize) { - // TODO(enga@google.com): This will find or create a mapped buffer of sufficient size and - // return a handle to a mapped range - D3D12_RESOURCE_DESC resourceDescriptor; - resourceDescriptor.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - resourceDescriptor.Alignment = 0; - resourceDescriptor.Width = requiredSize; - resourceDescriptor.Height = 1; - resourceDescriptor.DepthOrArraySize = 1; - resourceDescriptor.MipLevels = 1; - resourceDescriptor.Format = DXGI_FORMAT_UNKNOWN; - resourceDescriptor.SampleDesc.Count = 1; - resourceDescriptor.SampleDesc.Quality = 0; - resourceDescriptor.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - resourceDescriptor.Flags = D3D12_RESOURCE_FLAG_NONE; - - UploadHandle uploadHandle; - uploadHandle.resource = mDevice->GetResourceAllocator()->Allocate( - D3D12_HEAP_TYPE_UPLOAD, resourceDescriptor, D3D12_RESOURCE_STATE_GENERIC_READ); - D3D12_RANGE readRange; - readRange.Begin = 0; - readRange.End = 0; - - uploadHandle.resource->Map(0, &readRange, - reinterpret_cast(&uploadHandle.mappedBuffer)); - return uploadHandle; - } - - void ResourceUploader::Release(UploadHandle uploadHandle) { - uploadHandle.resource->Unmap(0, nullptr); - mDevice->GetResourceAllocator()->Release(uploadHandle.resource); - } - -}} // namespace dawn_native::d3d12 diff --git a/src/dawn_native/d3d12/StagingBufferD3D12.cpp b/src/dawn_native/d3d12/StagingBufferD3D12.cpp new file mode 100644 index 0000000000..bb711f41ca --- /dev/null +++ b/src/dawn_native/d3d12/StagingBufferD3D12.cpp @@ -0,0 +1,63 @@ +// Copyright 2018 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/d3d12/StagingBufferD3D12.h" +#include "dawn_native/d3d12/DeviceD3D12.h" +#include "dawn_native/d3d12/ResourceAllocator.h" + +namespace dawn_native { namespace d3d12 { + + StagingBuffer::StagingBuffer(size_t size, Device* device) + : StagingBufferBase(size), mDevice(device) { + } + + MaybeError StagingBuffer::Initialize() { + D3D12_RESOURCE_DESC resourceDescriptor; + resourceDescriptor.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + resourceDescriptor.Alignment = 0; + resourceDescriptor.Width = GetSize(); + resourceDescriptor.Height = 1; + resourceDescriptor.DepthOrArraySize = 1; + resourceDescriptor.MipLevels = 1; + resourceDescriptor.Format = DXGI_FORMAT_UNKNOWN; + resourceDescriptor.SampleDesc.Count = 1; + resourceDescriptor.SampleDesc.Quality = 0; + resourceDescriptor.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + resourceDescriptor.Flags = D3D12_RESOURCE_FLAG_NONE; + + mUploadHeap = mDevice->GetResourceAllocator()->Allocate( + D3D12_HEAP_TYPE_UPLOAD, resourceDescriptor, D3D12_RESOURCE_STATE_GENERIC_READ); + + // TODO(b-brber): Record the GPU pointer for generic non-upload usage. + + if (FAILED(mUploadHeap->Map(0, nullptr, &mMappedPointer))) { + return DAWN_CONTEXT_LOST_ERROR("Unable to map staging buffer."); + } + + return {}; + } + + StagingBuffer::~StagingBuffer() { + // Invalidate the CPU virtual address & flush cache (if needed). + mUploadHeap->Unmap(0, nullptr); + mMappedPointer = nullptr; + + mDevice->GetResourceAllocator()->Release(mUploadHeap); + } + + ID3D12Resource* StagingBuffer::GetResource() const { + return mUploadHeap.Get(); + } + +}} // namespace dawn_native::d3d12 \ No newline at end of file diff --git a/src/dawn_native/d3d12/ResourceUploader.h b/src/dawn_native/d3d12/StagingBufferD3D12.h similarity index 52% rename from src/dawn_native/d3d12/ResourceUploader.h rename to src/dawn_native/d3d12/StagingBufferD3D12.h index c3307e54cc..b689df4a95 100644 --- a/src/dawn_native/d3d12/ResourceUploader.h +++ b/src/dawn_native/d3d12/StagingBufferD3D12.h @@ -1,4 +1,4 @@ -// Copyright 2017 The Dawn Authors +// Copyright 2018 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. @@ -12,37 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef DAWNNATIVE_D3D12_RESOURCEUPLOADER_H_ -#define DAWNNATIVE_D3D12_RESOURCEUPLOADER_H_ +#ifndef DAWNNATIVE_STAGINGBUFFERD3D12_H_ +#define DAWNNATIVE_STAGINGBUFFERD3D12_H_ +#include "dawn_native/StagingBuffer.h" #include "dawn_native/d3d12/d3d12_platform.h" -#include "dawn_native/Forward.h" - namespace dawn_native { namespace d3d12 { class Device; - class ResourceUploader { + class StagingBuffer : public StagingBufferBase { public: - ResourceUploader(Device* device); + StagingBuffer(size_t size, Device* device); + ~StagingBuffer(); - void BufferSubData(ComPtr resource, - uint32_t start, - uint32_t count, - const void* data); + ID3D12Resource* GetResource() const; + + MaybeError Initialize() override; private: - struct UploadHandle { - ComPtr resource; - uint8_t* mappedBuffer; - }; - - UploadHandle GetUploadBuffer(uint32_t requiredSize); - void Release(UploadHandle uploadHandle); - Device* mDevice; + ComPtr mUploadHeap; }; }} // namespace dawn_native::d3d12 -#endif // DAWNNATIVE_D3D12_RESOURCEUPLOADER_H_ +#endif // DAWNNATIVE_STAGINGBUFFERD3D12_H_ diff --git a/src/dawn_native/metal/BufferMTL.h b/src/dawn_native/metal/BufferMTL.h index a56af33fe4..5e65f83549 100644 --- a/src/dawn_native/metal/BufferMTL.h +++ b/src/dawn_native/metal/BufferMTL.h @@ -34,7 +34,7 @@ namespace dawn_native { namespace metal { void OnMapCommandSerialFinished(uint32_t mapSerial, uint32_t offset, bool isWrite); private: - void SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override; + MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override; void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override; void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override; void UnmapImpl() override; diff --git a/src/dawn_native/metal/BufferMTL.mm b/src/dawn_native/metal/BufferMTL.mm index d993e78789..85c4272a69 100644 --- a/src/dawn_native/metal/BufferMTL.mm +++ b/src/dawn_native/metal/BufferMTL.mm @@ -49,9 +49,10 @@ namespace dawn_native { namespace metal { } } - void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) { + MaybeError Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) { auto* uploader = ToBackend(GetDevice())->GetResourceUploader(); uploader->BufferSubData(mMtlBuffer, start, count, data); + return {}; } void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t) { diff --git a/src/dawn_native/metal/DeviceMTL.h b/src/dawn_native/metal/DeviceMTL.h index 434ffa8970..d250e909a6 100644 --- a/src/dawn_native/metal/DeviceMTL.h +++ b/src/dawn_native/metal/DeviceMTL.h @@ -52,12 +52,19 @@ namespace dawn_native { namespace metal { id GetMTLDevice(); id GetPendingCommandBuffer(); - Serial GetPendingCommandSerial() const; + Serial GetPendingCommandSerial() const override; void SubmitPendingCommandBuffer(); MapRequestTracker* GetMapTracker() const; ResourceUploader* GetResourceUploader() const; + ResultOrError> CreateStagingBuffer(size_t size) override; + MaybeError CopyFromStagingToBuffer(StagingBufferBase* source, + uint32_t sourceOffset, + BufferBase* destination, + uint32_t destinationOffset, + uint32_t size) override; + private: ResultOrError CreateBindGroupImpl( const BindGroupDescriptor* descriptor) override; diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm index f71fe9afc7..f6b4ab45e5 100644 --- a/src/dawn_native/metal/DeviceMTL.mm +++ b/src/dawn_native/metal/DeviceMTL.mm @@ -17,6 +17,7 @@ #include "dawn_native/BackendConnection.h" #include "dawn_native/BindGroup.h" #include "dawn_native/BindGroupLayout.h" +#include "dawn_native/DynamicUploader.h" #include "dawn_native/RenderPassDescriptor.h" #include "dawn_native/metal/BufferMTL.h" #include "dawn_native/metal/CommandBufferMTL.h" @@ -297,4 +298,16 @@ namespace dawn_native { namespace metal { mPCIInfo.name = std::string([mMtlDevice.name UTF8String]); } + ResultOrError> Device::CreateStagingBuffer(size_t size) { + return DAWN_UNIMPLEMENTED_ERROR("Device unable to create staging buffer."); + } + + MaybeError Device::CopyFromStagingToBuffer(StagingBufferBase* source, + uint32_t sourceOffset, + BufferBase* destination, + uint32_t destinationOffset, + uint32_t size) { + return DAWN_UNIMPLEMENTED_ERROR("Device unable to copy from staging buffer."); + } + }} // namespace dawn_native::metal diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp index 7244435371..4f765ba655 100644 --- a/src/dawn_native/null/DeviceNull.cpp +++ b/src/dawn_native/null/DeviceNull.cpp @@ -16,6 +16,7 @@ #include "dawn_native/BackendConnection.h" #include "dawn_native/Commands.h" +#include "dawn_native/DynamicUploader.h" #include @@ -57,6 +58,7 @@ namespace dawn_native { namespace null { // Device Device::Device(Adapter* adapter) : DeviceBase(adapter) { + mDynamicUploader = std::make_unique(this); } Device::~Device() { @@ -122,6 +124,20 @@ namespace dawn_native { namespace null { return new TextureView(texture, descriptor); } + ResultOrError> Device::CreateStagingBuffer(size_t size) { + std::unique_ptr stagingBuffer = + std::make_unique(size, this); + return std::move(stagingBuffer); + } + + MaybeError Device::CopyFromStagingToBuffer(StagingBufferBase* source, + uint32_t sourceOffset, + BufferBase* destination, + uint32_t destinationOffset, + uint32_t size) { + return DAWN_UNIMPLEMENTED_ERROR("Device unable to copy from staging buffer."); + } + Serial Device::GetCompletedCommandSerial() const { return mCompletedSerial; } @@ -130,6 +146,10 @@ namespace dawn_native { namespace null { return mLastSubmittedSerial; } + Serial Device::GetPendingCommandSerial() const { + return mLastSubmittedSerial + 1; + } + void Device::TickImpl() { SubmitPendingOperations(); } @@ -179,10 +199,11 @@ namespace dawn_native { namespace null { } } - void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) { + MaybeError Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) { ASSERT(start + count <= GetSize()); ASSERT(mBackingData); memcpy(mBackingData.get() + start, data, count); + return {}; } void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) { @@ -272,4 +293,15 @@ namespace dawn_native { namespace null { return dawn::TextureFormat::R8G8B8A8Unorm; } + // StagingBuffer + + StagingBuffer::StagingBuffer(size_t size, Device* device) : StagingBufferBase(size) { + } + + MaybeError StagingBuffer::Initialize() { + mBuffer = std::make_unique(GetSize()); + mMappedPointer = mBuffer.get(); + return {}; + } + }} // namespace dawn_native::null diff --git a/src/dawn_native/null/DeviceNull.h b/src/dawn_native/null/DeviceNull.h index 03f8967e3c..fd60126658 100644 --- a/src/dawn_native/null/DeviceNull.h +++ b/src/dawn_native/null/DeviceNull.h @@ -26,8 +26,10 @@ #include "dawn_native/Queue.h" #include "dawn_native/RenderPassDescriptor.h" #include "dawn_native/RenderPipeline.h" +#include "dawn_native/RingBuffer.h" #include "dawn_native/Sampler.h" #include "dawn_native/ShaderModule.h" +#include "dawn_native/StagingBuffer.h" #include "dawn_native/SwapChain.h" #include "dawn_native/Texture.h" #include "dawn_native/ToBackend.h" @@ -96,11 +98,19 @@ namespace dawn_native { namespace null { Serial GetCompletedCommandSerial() const final override; Serial GetLastSubmittedCommandSerial() const final override; + Serial GetPendingCommandSerial() const override; void TickImpl() override; void AddPendingOperation(std::unique_ptr operation); void SubmitPendingOperations(); + ResultOrError> CreateStagingBuffer(size_t size) override; + MaybeError CopyFromStagingToBuffer(StagingBufferBase* source, + uint32_t sourceOffset, + BufferBase* destination, + uint32_t destinationOffset, + uint32_t size) override; + private: ResultOrError CreateBindGroupImpl( const BindGroupDescriptor* descriptor) override; @@ -125,6 +135,7 @@ namespace dawn_native { namespace null { Serial mCompletedSerial = 0; Serial mLastSubmittedSerial = 0; std::vector> mPendingOperations; + std::unique_ptr mDynamicUploader; }; class Buffer : public BufferBase { @@ -135,7 +146,7 @@ namespace dawn_native { namespace null { void MapReadOperationCompleted(uint32_t serial, void* ptr, bool isWrite); private: - void SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override; + MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override; void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override; void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override; void UnmapImpl() override; @@ -186,6 +197,15 @@ namespace dawn_native { namespace null { dawn::TextureFormat GetPreferredFormat() const; }; + class StagingBuffer : public StagingBufferBase { + public: + StagingBuffer(size_t size, Device* device); + MaybeError Initialize() override; + + private: + std::unique_ptr mBuffer; + }; + }} // namespace dawn_native::null #endif // DAWNNATIVE_NULL_DEVICENULL_H_ diff --git a/src/dawn_native/opengl/BufferGL.cpp b/src/dawn_native/opengl/BufferGL.cpp index e14307f456..ed9aa53444 100644 --- a/src/dawn_native/opengl/BufferGL.cpp +++ b/src/dawn_native/opengl/BufferGL.cpp @@ -31,9 +31,10 @@ namespace dawn_native { namespace opengl { return mBuffer; } - void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) { + MaybeError Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) { glBindBuffer(GL_ARRAY_BUFFER, mBuffer); glBufferSubData(GL_ARRAY_BUFFER, start, count, data); + return {}; } void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) { diff --git a/src/dawn_native/opengl/BufferGL.h b/src/dawn_native/opengl/BufferGL.h index 497beec991..0f35484c92 100644 --- a/src/dawn_native/opengl/BufferGL.h +++ b/src/dawn_native/opengl/BufferGL.h @@ -30,7 +30,7 @@ namespace dawn_native { namespace opengl { GLuint GetHandle() const; private: - void SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override; + MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override; void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override; void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override; void UnmapImpl() override; diff --git a/src/dawn_native/opengl/DeviceGL.cpp b/src/dawn_native/opengl/DeviceGL.cpp index 934a9b68f3..f1f80edfbd 100644 --- a/src/dawn_native/opengl/DeviceGL.cpp +++ b/src/dawn_native/opengl/DeviceGL.cpp @@ -17,6 +17,7 @@ #include "dawn_native/BackendConnection.h" #include "dawn_native/BindGroup.h" #include "dawn_native/BindGroupLayout.h" +#include "dawn_native/DynamicUploader.h" #include "dawn_native/RenderPassDescriptor.h" #include "dawn_native/opengl/BufferGL.h" #include "dawn_native/opengl/CommandBufferGL.h" @@ -115,6 +116,10 @@ namespace dawn_native { namespace opengl { return mLastSubmittedSerial; } + Serial Device::GetPendingCommandSerial() const { + return mLastSubmittedSerial + 1; + } + void Device::TickImpl() { CheckPassedFences(); } @@ -144,4 +149,16 @@ namespace dawn_native { namespace opengl { } } + ResultOrError> Device::CreateStagingBuffer(size_t size) { + return DAWN_UNIMPLEMENTED_ERROR("Device unable to create staging buffer."); + } + + MaybeError Device::CopyFromStagingToBuffer(StagingBufferBase* source, + uint32_t sourceOffset, + BufferBase* destination, + uint32_t destinationOffset, + uint32_t size) { + return DAWN_UNIMPLEMENTED_ERROR("Device unable to copy from staging buffer."); + } + }} // namespace dawn_native::opengl diff --git a/src/dawn_native/opengl/DeviceGL.h b/src/dawn_native/opengl/DeviceGL.h index 192b9f2d0d..8c15162d88 100644 --- a/src/dawn_native/opengl/DeviceGL.h +++ b/src/dawn_native/opengl/DeviceGL.h @@ -48,8 +48,16 @@ namespace dawn_native { namespace opengl { Serial GetCompletedCommandSerial() const final override; Serial GetLastSubmittedCommandSerial() const final override; + Serial GetPendingCommandSerial() const override; void TickImpl() override; + ResultOrError> CreateStagingBuffer(size_t size) override; + MaybeError CopyFromStagingToBuffer(StagingBufferBase* source, + uint32_t sourceOffset, + BufferBase* destination, + uint32_t destinationOffset, + uint32_t size) override; + private: ResultOrError CreateBindGroupImpl( const BindGroupDescriptor* descriptor) override; diff --git a/src/dawn_native/vulkan/BufferVk.cpp b/src/dawn_native/vulkan/BufferVk.cpp index 73e1538419..4175097402 100644 --- a/src/dawn_native/vulkan/BufferVk.cpp +++ b/src/dawn_native/vulkan/BufferVk.cpp @@ -196,7 +196,7 @@ namespace dawn_native { namespace vulkan { mLastUsage = usage; } - void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) { + MaybeError Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) { Device* device = ToBackend(GetDevice()); VkCommandBuffer commands = device->GetPendingCommandBuffer(); @@ -204,6 +204,7 @@ namespace dawn_native { namespace vulkan { BufferUploader* uploader = device->GetBufferUploader(); uploader->BufferSubData(mHandle, start, count, data); + return {}; } void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t /*count*/) { diff --git a/src/dawn_native/vulkan/BufferVk.h b/src/dawn_native/vulkan/BufferVk.h index 41ef9c40d9..ae90b4f499 100644 --- a/src/dawn_native/vulkan/BufferVk.h +++ b/src/dawn_native/vulkan/BufferVk.h @@ -41,7 +41,7 @@ namespace dawn_native { namespace vulkan { void TransitionUsageNow(VkCommandBuffer commands, dawn::BufferUsageBit usage); private: - void SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override; + MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override; void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override; void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override; void UnmapImpl() override; diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp index da23c0ac47..d42df54166 100644 --- a/src/dawn_native/vulkan/DeviceVk.cpp +++ b/src/dawn_native/vulkan/DeviceVk.cpp @@ -17,6 +17,7 @@ #include "common/Platform.h" #include "dawn_native/BackendConnection.h" #include "dawn_native/Commands.h" +#include "dawn_native/DynamicUploader.h" #include "dawn_native/ErrorData.h" #include "dawn_native/vulkan/BindGroupLayoutVk.h" #include "dawn_native/vulkan/BindGroupVk.h" @@ -682,4 +683,16 @@ namespace dawn_native { namespace vulkan { commands->commandBuffer = VK_NULL_HANDLE; } + ResultOrError> Device::CreateStagingBuffer(size_t size) { + return DAWN_UNIMPLEMENTED_ERROR("Device unable to create staging buffer."); + } + + MaybeError Device::CopyFromStagingToBuffer(StagingBufferBase* source, + uint32_t sourceOffset, + BufferBase* destination, + uint32_t destinationOffset, + uint32_t size) { + return DAWN_UNIMPLEMENTED_ERROR("Device unable to copy from staging buffer."); + } + }} // namespace dawn_native::vulkan diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h index b3af48a2d8..27139cb35c 100644 --- a/src/dawn_native/vulkan/DeviceVk.h +++ b/src/dawn_native/vulkan/DeviceVk.h @@ -58,7 +58,7 @@ namespace dawn_native { namespace vulkan { RenderPassCache* GetRenderPassCache() const; VkCommandBuffer GetPendingCommandBuffer(); - Serial GetPendingCommandSerial() const; + Serial GetPendingCommandSerial() const override; void SubmitPendingCommands(); void AddWaitSemaphore(VkSemaphore semaphore); @@ -75,6 +75,13 @@ namespace dawn_native { namespace vulkan { const dawn_native::PCIInfo& GetPCIInfo() const override; + ResultOrError> CreateStagingBuffer(size_t size) override; + MaybeError CopyFromStagingToBuffer(StagingBufferBase* source, + uint32_t sourceOffset, + BufferBase* destination, + uint32_t destinationOffset, + uint32_t size) override; + private: ResultOrError CreateBindGroupImpl( const BindGroupDescriptor* descriptor) override; diff --git a/src/tests/end2end/BufferTests.cpp b/src/tests/end2end/BufferTests.cpp index f1e4752507..70dfac8346 100644 --- a/src/tests/end2end/BufferTests.cpp +++ b/src/tests/end2end/BufferTests.cpp @@ -222,10 +222,15 @@ TEST_P(BufferSetSubDataTests, SmallDataAtOffset) { TEST_P(BufferSetSubDataTests, ManySetSubData) { // TODO(cwallez@chromium.org): Use ringbuffers for SetSubData on explicit APIs. // otherwise this creates too many resources and can take freeze the driver(?) - DAWN_SKIP_TEST_IF(IsD3D12() || IsMetal() || IsVulkan()); + DAWN_SKIP_TEST_IF(IsMetal() || IsVulkan()); + // Note: Increasing the size of the buffer will likely cause timeout issues. + // In D3D12, timeout detection occurs when the GPU scheduler tries but cannot preempt the task + // executing these commands in-flight. If this takes longer than ~2s, a device reset occurs and + // fails the test. Since GPUs may or may not complete by then, this test must be disabled OR + // modified to be well-below the timeout limit. constexpr uint32_t kSize = 4000 * 1000; - constexpr uint32_t kElements = 1000 * 1000; + constexpr uint32_t kElements = 500 * 500; dawn::BufferDescriptor descriptor; descriptor.size = kSize; descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst; diff --git a/src/tests/unittests/RingBufferTests.cpp b/src/tests/unittests/RingBufferTests.cpp new file mode 100644 index 0000000000..1b8fc7bcf5 --- /dev/null +++ b/src/tests/unittests/RingBufferTests.cpp @@ -0,0 +1,201 @@ +// Copyright 2018 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 + +#include "dawn_native/null/DeviceNull.h" + +using namespace dawn_native; + +namespace { + + size_t ValidateValidUploadHandle(const UploadHandle& uploadHandle) { + ASSERT(uploadHandle.mappedBuffer != nullptr); + return uploadHandle.startOffset; + } + + void ValidateInvalidUploadHandle(const UploadHandle& uploadHandle) { + ASSERT_EQ(uploadHandle.mappedBuffer, nullptr); + } +} // namespace + +class RingBufferTests : public testing::Test { + protected: + void SetUp() override { + // TODO(b-brber): Create this device through the adapter. + mDevice = std::make_unique(/*adapter*/ nullptr); + } + + null::Device* GetDevice() const { + return mDevice.get(); + } + + std::unique_ptr CreateRingBuffer(size_t size) { + std::unique_ptr ringBuffer = std::make_unique(mDevice.get(), size); + DAWN_UNUSED(ringBuffer->Initialize()); + return ringBuffer; + } + + private: + std::unique_ptr mDevice; +}; + +// Number of basic tests for Ringbuffer +TEST_F(RingBufferTests, BasicTest) { + constexpr size_t sizeInBytes = 64000; + std::unique_ptr buffer = CreateRingBuffer(sizeInBytes); + + // Ensure no requests exist on empty buffer. + EXPECT_TRUE(buffer->Empty()); + + ASSERT_EQ(buffer->GetSize(), sizeInBytes); + + // Ensure failure upon sub-allocating an oversized request. + ValidateInvalidUploadHandle(buffer->SubAllocate(sizeInBytes + 1)); + + // Fill the entire buffer with two requests of equal size. + ValidateValidUploadHandle(buffer->SubAllocate(sizeInBytes / 2)); + ValidateValidUploadHandle(buffer->SubAllocate(sizeInBytes / 2)); + + // Ensure the buffer is full. + ValidateInvalidUploadHandle(buffer->SubAllocate(1)); +} + +// Tests that several ringbuffer allocations do not fail. +TEST_F(RingBufferTests, RingBufferManyAlloc) { + constexpr size_t maxNumOfFrames = 64000; + constexpr size_t frameSizeInBytes = 4; + + std::unique_ptr buffer = CreateRingBuffer(maxNumOfFrames * frameSizeInBytes); + + size_t offset = 0; + for (size_t i = 0; i < maxNumOfFrames; ++i) { + offset = ValidateValidUploadHandle(buffer->SubAllocate(frameSizeInBytes)); + GetDevice()->Tick(); + ASSERT_EQ(offset, i * frameSizeInBytes); + } +} + +// Tests ringbuffer sub-allocations of the same serial are correctly tracked. +TEST_F(RingBufferTests, AllocInSameFrame) { + constexpr size_t maxNumOfFrames = 3; + constexpr size_t frameSizeInBytes = 4; + + std::unique_ptr buffer = CreateRingBuffer(maxNumOfFrames * frameSizeInBytes); + + // F1 + // [xxxx|--------] + + ValidateValidUploadHandle(buffer->SubAllocate(frameSizeInBytes)); + GetDevice()->Tick(); + + // F1 F2 + // [xxxx|xxxx|----] + + ValidateValidUploadHandle(buffer->SubAllocate(frameSizeInBytes)); + + // F1 F2 + // [xxxx|xxxxxxxx] + + size_t offset = ValidateValidUploadHandle(buffer->SubAllocate(frameSizeInBytes)); + + ASSERT_EQ(offset, 8u); + ASSERT_EQ(buffer->GetUsedSize(), frameSizeInBytes * 3); + + buffer->Tick(1); + + // Used size does not change as previous sub-allocations were not tracked. + ASSERT_EQ(buffer->GetUsedSize(), frameSizeInBytes * 3); + + buffer->Tick(2); + + ASSERT_EQ(buffer->GetUsedSize(), 0u); + EXPECT_TRUE(buffer->Empty()); +} + +// Tests ringbuffer sub-allocation at various offsets. +TEST_F(RingBufferTests, RingBufferSubAlloc) { + constexpr size_t maxNumOfFrames = 10; + constexpr size_t frameSizeInBytes = 4; + + std::unique_ptr buffer = CreateRingBuffer(maxNumOfFrames * frameSizeInBytes); + + // Sub-alloc the first eight frames. + for (size_t i = 0; i < 8; ++i) { + ValidateValidUploadHandle(buffer->SubAllocate(frameSizeInBytes)); + buffer->Track(); + GetDevice()->Tick(); + } + + // Each frame corrresponds to the serial number (for simplicity). + // + // F1 F2 F3 F4 F5 F6 F7 F8 + // [xxxx|xxxx|xxxx|xxxx|xxxx|xxxx|xxxx|xxxx|--------] + // + + // Ensure an oversized allocation fails (only 8 bytes left) + ValidateInvalidUploadHandle(buffer->SubAllocate(frameSizeInBytes * 3)); + ASSERT_EQ(buffer->GetUsedSize(), frameSizeInBytes * 8); + + // Reclaim the first 3 frames. + buffer->Tick(3); + + // F4 F5 F6 F7 F8 + // [------------|xxxx|xxxx|xxxx|xxxx|xxxx|--------] + // + ASSERT_EQ(buffer->GetUsedSize(), frameSizeInBytes * 5); + + // Re-try the over-sized allocation. + size_t offset = ValidateValidUploadHandle(buffer->SubAllocate(frameSizeInBytes * 3)); + + // F9 F4 F5 F6 F7 F8 + // [xxxxxxxxxxxx|xxxx|xxxx|xxxx|xxxx|xxxx|xxxxxxxx] + // ^^^^^^^^ wasted + + // In this example, Tick(8) could not reclaim the wasted bytes. The wasted bytes + // were add to F9's sub-allocation. + // TODO(b-brber): Decide if Tick(8) should free these wasted bytes. + + ASSERT_EQ(offset, 0u); + ASSERT_EQ(buffer->GetUsedSize(), frameSizeInBytes * maxNumOfFrames); + + // Ensure we are full. + ValidateInvalidUploadHandle(buffer->SubAllocate(frameSizeInBytes)); + + // Reclaim the next two frames. + buffer->Tick(5); + + // F9 F4 F5 F6 F7 F8 + // [xxxxxxxxxxxx|----|----|xxxx|xxxx|xxxx|xxxxxxxx] + // + ASSERT_EQ(buffer->GetUsedSize(), frameSizeInBytes * 8); + + // Sub-alloc the chunk in the middle. + offset = ValidateValidUploadHandle(buffer->SubAllocate(frameSizeInBytes * 2)); + + ASSERT_EQ(offset, frameSizeInBytes * 3); + ASSERT_EQ(buffer->GetUsedSize(), frameSizeInBytes * maxNumOfFrames); + + // F9 F6 F7 F8 + // [xxxxxxxxxxxx|xxxx|xxxx|xxxx|xxxx|xxxx|xxxxxxxx] + // ^^^^^^^^^ untracked + + // Ensure we are full. + ValidateInvalidUploadHandle(buffer->SubAllocate(frameSizeInBytes)); + + // Reclaim all. + buffer->Tick(maxNumOfFrames); + + EXPECT_TRUE(buffer->Empty()); +} diff --git a/src/tests/unittests/SerialQueueTests.cpp b/src/tests/unittests/SerialQueueTests.cpp index 712c093aed..6f75eb78ae 100644 --- a/src/tests/unittests/SerialQueueTests.cpp +++ b/src/tests/unittests/SerialQueueTests.cpp @@ -95,6 +95,7 @@ TEST(SerialQueue, IterateUpTo) { expectedValues.erase(expectedValues.begin()); } ASSERT_TRUE(expectedValues.empty()); + EXPECT_EQ(queue.LastSerial(), 2u); } // Test ClearUpTo @@ -110,6 +111,7 @@ TEST(SerialQueue, ClearUpTo) { queue.Enqueue(vector3, 1); queue.ClearUpTo(0); + EXPECT_EQ(queue.LastSerial(), 1u); std::vector expectedValues = {9, 0}; for (int value : queue.IterateAll()) { @@ -141,3 +143,14 @@ TEST(SerialQueue, FirstSerial) { queue.Enqueue(vector1, 6); EXPECT_EQ(queue.FirstSerial(), 6u); } + +// Test LastSerial +TEST(SerialQueue, LastSerial) { + TestSerialQueue queue; + + queue.Enqueue({1}, 0); + EXPECT_EQ(queue.LastSerial(), 0u); + + queue.Enqueue({2}, 1); + EXPECT_EQ(queue.LastSerial(), 1u); +} \ No newline at end of file