Memory manager: buffer uploads (D3D) - Part 1
Manages a single persistently mapped GPU heap which is sub-allocated
inside of ring-buffer for uploads. To handle larger buffers without additional
unused heaps, ring buffers are created on-demand.
BUG=dawn:28
TEST=dawn_unittests
Change-Id: Ifc5a1b06baf8633f1e133245ac1ee76275431cc5
Reviewed-on: https://dawn-review.googlesource.com/c/3160
Commit-Queue: Bryan Bernhart <bryan.bernhart@intel.com>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 99d1ae4..19bc998 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -451,6 +451,8 @@
"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 @@
"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 @@
"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 @@
"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 60fae47..6f38213 100644
--- a/src/common/SerialStorage.h
+++ b/src/common/SerialStorage.h
@@ -110,6 +110,7 @@
void ClearUpTo(Serial serial);
Serial FirstSerial() const;
+ Serial LastSerial() const;
protected:
// Returns the first StorageIterator that a serial bigger than serial.
@@ -163,6 +164,12 @@
}
template <typename Derived>
+Serial SerialStorage<Derived>::LastSerial() const {
+ DAWN_ASSERT(!Empty());
+ return mStorage.back().first;
+}
+
+template <typename Derived>
typename SerialStorage<Derived>::ConstStorageIterator SerialStorage<Derived>::FindUpTo(
Serial serial) const {
auto it = mStorage.begin();
diff --git a/src/dawn_native/Buffer.cpp b/src/dawn_native/Buffer.cpp
index 8873567..543d62e 100644
--- a/src/dawn_native/Buffer.cpp
+++ b/src/dawn_native/Buffer.cpp
@@ -106,7 +106,9 @@
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 9a3fa2c..4472cec 100644
--- a/src/dawn_native/Buffer.h
+++ b/src/dawn_native/Buffer.h
@@ -63,7 +63,7 @@
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 82e2cf8..fc79075 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -31,6 +31,8 @@
class AdapterBase;
class FenceSignalTracker;
+ class DynamicUploader;
+ class StagingBufferBase;
class DeviceBase {
public:
@@ -60,6 +62,7 @@
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 @@
virtual const PCIInfo& GetPCIInfo() const;
+ virtual ResultOrError<std::unique_ptr<StagingBufferBase>> 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<BindGroupBase*> CreateBindGroupImpl(
const BindGroupDescriptor* descriptor) = 0;
diff --git a/src/dawn_native/DynamicUploader.cpp b/src/dawn_native/DynamicUploader.cpp
new file mode 100644
index 0000000..e127db4
--- /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> ringBuffer = std::make_unique<RingBuffer>(mDevice, size);
+ DAWN_TRY(ringBuffer->Initialize());
+ mRingBuffers.emplace_back(std::move(ringBuffer));
+ return {};
+ }
+
+ ResultOrError<UploadHandle> 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 0000000..848c07a
--- /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 <memory>
+
+// 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<UploadHandle> Allocate(uint32_t requiredSize, uint32_t alignment);
+ void Tick(Serial lastCompletedSerial);
+
+ RingBuffer* GetLargestBuffer();
+
+ MaybeError CreateAndAppendBuffer(size_t size);
+
+ bool IsEmpty() const;
+
+ private:
+ std::vector<std::unique_ptr<RingBuffer>> 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 5373f66..73a2555 100644
--- a/src/dawn_native/Forward.h
+++ b/src/dawn_native/Forward.h
@@ -44,6 +44,7 @@
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 0000000..51e97c2
--- /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<size_t>::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<uint8_t*>(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 0000000..dfa44f5
--- /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<StagingBufferBase> mStagingBuffer;
+
+ struct Request {
+ size_t endOffset;
+ size_t size;
+ };
+
+ SerialQueue<Request> 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 0000000..51f5fa8
--- /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 0000000..1da8900
--- /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 585a40d..1ad86a8 100644
--- a/src/dawn_native/ToBackend.h
+++ b/src/dawn_native/ToBackend.h
@@ -94,6 +94,11 @@
};
template <typename BackendTraits>
+ struct ToBackendTraits<StagingBufferBase, BackendTraits> {
+ using BackendType = typename BackendTraits::StagingBufferType;
+ };
+
+ template <typename BackendTraits>
struct ToBackendTraits<TextureBase, BackendTraits> {
using BackendType = typename BackendTraits::TextureType;
};
diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp
index 4b75d1f..69c25aa 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 @@
}
}
- 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 633d260..753aac4 100644
--- a/src/dawn_native/d3d12/BufferD3D12.h
+++ b/src/dawn_native/d3d12/BufferD3D12.h
@@ -39,11 +39,15 @@
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<ID3D12Resource> 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 a72ec89..0ad3f4d 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 @@
mDescriptorHeapAllocator = std::make_unique<DescriptorHeapAllocator>(this);
mMapRequestTracker = std::make_unique<MapRequestTracker>(this);
mResourceAllocator = std::make_unique<ResourceAllocator>(this);
- mResourceUploader = std::make_unique<ResourceUploader>(this);
+ mDynamicUploader = std::make_unique<DynamicUploader>(this);
NextSerial();
}
@@ -176,10 +177,6 @@
return mResourceAllocator.get();
}
- ResourceUploader* Device::GetResourceUploader() {
- return mResourceUploader.get();
- }
-
void Device::OpenCommandList(ComPtr<ID3D12GraphicsCommandList>* commandList) {
ComPtr<ID3D12GraphicsCommandList>& cmdList = *commandList;
if (!cmdList) {
@@ -223,6 +220,7 @@
mDescriptorHeapAllocator->Tick(mCompletedSerial);
mMapRequestTracker->Tick(mCompletedSerial);
mUsedComObjectRefs.ClearUpTo(mCompletedSerial);
+ mDynamicUploader->Tick(mCompletedSerial);
ExecuteCommandLists({});
NextSerial();
}
@@ -335,4 +333,33 @@
mPCIInfo.name = converter.to_bytes(adapterDesc.Description);
}
+ ResultOrError<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) {
+ std::unique_ptr<StagingBufferBase> stagingBuffer =
+ std::make_unique<StagingBuffer>(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<DynamicUploader*> 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 8230227..be489d9 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -31,7 +31,6 @@
class MapRequestTracker;
class PlatformFunctions;
class ResourceAllocator;
- class ResourceUploader;
void ASSERT_SUCCESS(HRESULT hr);
@@ -61,11 +60,10 @@
MapRequestTracker* GetMapRequestTracker() const;
const PlatformFunctions* GetFunctions();
ResourceAllocator* GetResourceAllocator();
- ResourceUploader* GetResourceUploader();
void OpenCommandList(ComPtr<ID3D12GraphicsCommandList>* commandList);
ComPtr<ID3D12GraphicsCommandList> GetPendingCommandList();
- Serial GetPendingCommandSerial() const;
+ Serial GetPendingCommandSerial() const override;
void NextSerial();
void WaitForSerial(Serial serial);
@@ -74,6 +72,15 @@
void ExecuteCommandLists(std::initializer_list<ID3D12CommandList*> commandLists);
+ ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer(size_t size) override;
+ MaybeError CopyFromStagingToBuffer(StagingBufferBase* source,
+ uint32_t sourceOffset,
+ BufferBase* destination,
+ uint32_t destinationOffset,
+ uint32_t size) override;
+
+ ResultOrError<DynamicUploader*> GetDynamicUploader() const;
+
private:
ResultOrError<BindGroupBase*> CreateBindGroupImpl(
const BindGroupDescriptor* descriptor) override;
@@ -121,9 +128,11 @@
std::unique_ptr<DescriptorHeapAllocator> mDescriptorHeapAllocator;
std::unique_ptr<MapRequestTracker> mMapRequestTracker;
std::unique_ptr<ResourceAllocator> mResourceAllocator;
- std::unique_ptr<ResourceUploader> mResourceUploader;
+ std::unique_ptr<DynamicUploader> 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 d4fa333..d803900 100644
--- a/src/dawn_native/d3d12/Forward.h
+++ b/src/dawn_native/d3d12/Forward.h
@@ -33,6 +33,7 @@
class RenderPipeline;
class Sampler;
class ShaderModule;
+ class StagingBuffer;
class SwapChain;
class Texture;
class TextureView;
@@ -52,6 +53,7 @@
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 6aa9a6b..0000000
--- 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<ID3D12Resource> 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<void**>(&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/ResourceUploader.h b/src/dawn_native/d3d12/ResourceUploader.h
deleted file mode 100644
index c3307e5..0000000
--- a/src/dawn_native/d3d12/ResourceUploader.h
+++ /dev/null
@@ -1,48 +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.
-
-#ifndef DAWNNATIVE_D3D12_RESOURCEUPLOADER_H_
-#define DAWNNATIVE_D3D12_RESOURCEUPLOADER_H_
-
-#include "dawn_native/d3d12/d3d12_platform.h"
-
-#include "dawn_native/Forward.h"
-
-namespace dawn_native { namespace d3d12 {
-
- class Device;
-
- class ResourceUploader {
- public:
- ResourceUploader(Device* device);
-
- void BufferSubData(ComPtr<ID3D12Resource> resource,
- uint32_t start,
- uint32_t count,
- const void* data);
-
- private:
- struct UploadHandle {
- ComPtr<ID3D12Resource> resource;
- uint8_t* mappedBuffer;
- };
-
- UploadHandle GetUploadBuffer(uint32_t requiredSize);
- void Release(UploadHandle uploadHandle);
-
- Device* mDevice;
- };
-}} // namespace dawn_native::d3d12
-
-#endif // DAWNNATIVE_D3D12_RESOURCEUPLOADER_H_
diff --git a/src/dawn_native/d3d12/StagingBufferD3D12.cpp b/src/dawn_native/d3d12/StagingBufferD3D12.cpp
new file mode 100644
index 0000000..bb711f4
--- /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/StagingBufferD3D12.h b/src/dawn_native/d3d12/StagingBufferD3D12.h
new file mode 100644
index 0000000..b689df4
--- /dev/null
+++ b/src/dawn_native/d3d12/StagingBufferD3D12.h
@@ -0,0 +1,40 @@
+// 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_STAGINGBUFFERD3D12_H_
+#define DAWNNATIVE_STAGINGBUFFERD3D12_H_
+
+#include "dawn_native/StagingBuffer.h"
+#include "dawn_native/d3d12/d3d12_platform.h"
+
+namespace dawn_native { namespace d3d12 {
+
+ class Device;
+
+ class StagingBuffer : public StagingBufferBase {
+ public:
+ StagingBuffer(size_t size, Device* device);
+ ~StagingBuffer();
+
+ ID3D12Resource* GetResource() const;
+
+ MaybeError Initialize() override;
+
+ private:
+ Device* mDevice;
+ ComPtr<ID3D12Resource> mUploadHeap;
+ };
+}} // namespace dawn_native::d3d12
+
+#endif // DAWNNATIVE_STAGINGBUFFERD3D12_H_
diff --git a/src/dawn_native/metal/BufferMTL.h b/src/dawn_native/metal/BufferMTL.h
index a56af33..5e65f83 100644
--- a/src/dawn_native/metal/BufferMTL.h
+++ b/src/dawn_native/metal/BufferMTL.h
@@ -34,7 +34,7 @@
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 d993e78..85c4272 100644
--- a/src/dawn_native/metal/BufferMTL.mm
+++ b/src/dawn_native/metal/BufferMTL.mm
@@ -49,9 +49,10 @@
}
}
- 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 434ffa8..d250e90 100644
--- a/src/dawn_native/metal/DeviceMTL.h
+++ b/src/dawn_native/metal/DeviceMTL.h
@@ -52,12 +52,19 @@
id<MTLDevice> GetMTLDevice();
id<MTLCommandBuffer> GetPendingCommandBuffer();
- Serial GetPendingCommandSerial() const;
+ Serial GetPendingCommandSerial() const override;
void SubmitPendingCommandBuffer();
MapRequestTracker* GetMapTracker() const;
ResourceUploader* GetResourceUploader() const;
+ ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer(size_t size) override;
+ MaybeError CopyFromStagingToBuffer(StagingBufferBase* source,
+ uint32_t sourceOffset,
+ BufferBase* destination,
+ uint32_t destinationOffset,
+ uint32_t size) override;
+
private:
ResultOrError<BindGroupBase*> CreateBindGroupImpl(
const BindGroupDescriptor* descriptor) override;
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index f71fe9a..f6b4ab4 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 @@
mPCIInfo.name = std::string([mMtlDevice.name UTF8String]);
}
+ ResultOrError<std::unique_ptr<StagingBufferBase>> 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 7244435..4f765ba 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 <spirv-cross/spirv_cross.hpp>
@@ -57,6 +58,7 @@
// Device
Device::Device(Adapter* adapter) : DeviceBase(adapter) {
+ mDynamicUploader = std::make_unique<DynamicUploader>(this);
}
Device::~Device() {
@@ -122,6 +124,20 @@
return new TextureView(texture, descriptor);
}
+ ResultOrError<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) {
+ std::unique_ptr<StagingBufferBase> stagingBuffer =
+ std::make_unique<StagingBuffer>(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 @@
return mLastSubmittedSerial;
}
+ Serial Device::GetPendingCommandSerial() const {
+ return mLastSubmittedSerial + 1;
+ }
+
void Device::TickImpl() {
SubmitPendingOperations();
}
@@ -179,10 +199,11 @@
}
}
- 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 @@
return dawn::TextureFormat::R8G8B8A8Unorm;
}
+ // StagingBuffer
+
+ StagingBuffer::StagingBuffer(size_t size, Device* device) : StagingBufferBase(size) {
+ }
+
+ MaybeError StagingBuffer::Initialize() {
+ mBuffer = std::make_unique<uint8_t[]>(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 03f8967..fd60126 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 @@
Serial GetCompletedCommandSerial() const final override;
Serial GetLastSubmittedCommandSerial() const final override;
+ Serial GetPendingCommandSerial() const override;
void TickImpl() override;
void AddPendingOperation(std::unique_ptr<PendingOperation> operation);
void SubmitPendingOperations();
+ ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer(size_t size) override;
+ MaybeError CopyFromStagingToBuffer(StagingBufferBase* source,
+ uint32_t sourceOffset,
+ BufferBase* destination,
+ uint32_t destinationOffset,
+ uint32_t size) override;
+
private:
ResultOrError<BindGroupBase*> CreateBindGroupImpl(
const BindGroupDescriptor* descriptor) override;
@@ -125,6 +135,7 @@
Serial mCompletedSerial = 0;
Serial mLastSubmittedSerial = 0;
std::vector<std::unique_ptr<PendingOperation>> mPendingOperations;
+ std::unique_ptr<DynamicUploader> mDynamicUploader;
};
class Buffer : public BufferBase {
@@ -135,7 +146,7 @@
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 @@
dawn::TextureFormat GetPreferredFormat() const;
};
+ class StagingBuffer : public StagingBufferBase {
+ public:
+ StagingBuffer(size_t size, Device* device);
+ MaybeError Initialize() override;
+
+ private:
+ std::unique_ptr<uint8_t[]> 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 e14307f..ed9aa53 100644
--- a/src/dawn_native/opengl/BufferGL.cpp
+++ b/src/dawn_native/opengl/BufferGL.cpp
@@ -31,9 +31,10 @@
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 497beec..0f35484 100644
--- a/src/dawn_native/opengl/BufferGL.h
+++ b/src/dawn_native/opengl/BufferGL.h
@@ -30,7 +30,7 @@
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 934a9b6..f1f80ed 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 @@
return mLastSubmittedSerial;
}
+ Serial Device::GetPendingCommandSerial() const {
+ return mLastSubmittedSerial + 1;
+ }
+
void Device::TickImpl() {
CheckPassedFences();
}
@@ -144,4 +149,16 @@
}
}
+ ResultOrError<std::unique_ptr<StagingBufferBase>> 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 192b9f2..8c15162 100644
--- a/src/dawn_native/opengl/DeviceGL.h
+++ b/src/dawn_native/opengl/DeviceGL.h
@@ -48,8 +48,16 @@
Serial GetCompletedCommandSerial() const final override;
Serial GetLastSubmittedCommandSerial() const final override;
+ Serial GetPendingCommandSerial() const override;
void TickImpl() override;
+ ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer(size_t size) override;
+ MaybeError CopyFromStagingToBuffer(StagingBufferBase* source,
+ uint32_t sourceOffset,
+ BufferBase* destination,
+ uint32_t destinationOffset,
+ uint32_t size) override;
+
private:
ResultOrError<BindGroupBase*> CreateBindGroupImpl(
const BindGroupDescriptor* descriptor) override;
diff --git a/src/dawn_native/vulkan/BufferVk.cpp b/src/dawn_native/vulkan/BufferVk.cpp
index 73e1538..4175097 100644
--- a/src/dawn_native/vulkan/BufferVk.cpp
+++ b/src/dawn_native/vulkan/BufferVk.cpp
@@ -196,7 +196,7 @@
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 @@
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 41ef9c4..ae90b4f 100644
--- a/src/dawn_native/vulkan/BufferVk.h
+++ b/src/dawn_native/vulkan/BufferVk.h
@@ -41,7 +41,7 @@
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 da23c0a..d42df54 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 @@
commands->commandBuffer = VK_NULL_HANDLE;
}
+ ResultOrError<std::unique_ptr<StagingBufferBase>> 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 b3af48a..27139cb 100644
--- a/src/dawn_native/vulkan/DeviceVk.h
+++ b/src/dawn_native/vulkan/DeviceVk.h
@@ -58,7 +58,7 @@
RenderPassCache* GetRenderPassCache() const;
VkCommandBuffer GetPendingCommandBuffer();
- Serial GetPendingCommandSerial() const;
+ Serial GetPendingCommandSerial() const override;
void SubmitPendingCommands();
void AddWaitSemaphore(VkSemaphore semaphore);
@@ -75,6 +75,13 @@
const dawn_native::PCIInfo& GetPCIInfo() const override;
+ ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer(size_t size) override;
+ MaybeError CopyFromStagingToBuffer(StagingBufferBase* source,
+ uint32_t sourceOffset,
+ BufferBase* destination,
+ uint32_t destinationOffset,
+ uint32_t size) override;
+
private:
ResultOrError<BindGroupBase*> CreateBindGroupImpl(
const BindGroupDescriptor* descriptor) override;
diff --git a/src/tests/end2end/BufferTests.cpp b/src/tests/end2end/BufferTests.cpp
index f1e4752..70dfac8 100644
--- a/src/tests/end2end/BufferTests.cpp
+++ b/src/tests/end2end/BufferTests.cpp
@@ -222,10 +222,15 @@
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 0000000..1b8fc7b
--- /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 <gtest/gtest.h>
+
+#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<null::Device>(/*adapter*/ nullptr);
+ }
+
+ null::Device* GetDevice() const {
+ return mDevice.get();
+ }
+
+ std::unique_ptr<RingBuffer> CreateRingBuffer(size_t size) {
+ std::unique_ptr<RingBuffer> ringBuffer = std::make_unique<RingBuffer>(mDevice.get(), size);
+ DAWN_UNUSED(ringBuffer->Initialize());
+ return ringBuffer;
+ }
+
+ private:
+ std::unique_ptr<null::Device> mDevice;
+};
+
+// Number of basic tests for Ringbuffer
+TEST_F(RingBufferTests, BasicTest) {
+ constexpr size_t sizeInBytes = 64000;
+ std::unique_ptr<RingBuffer> 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<RingBuffer> 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<RingBuffer> 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<RingBuffer> 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 712c093..6f75eb7 100644
--- a/src/tests/unittests/SerialQueueTests.cpp
+++ b/src/tests/unittests/SerialQueueTests.cpp
@@ -95,6 +95,7 @@
expectedValues.erase(expectedValues.begin());
}
ASSERT_TRUE(expectedValues.empty());
+ EXPECT_EQ(queue.LastSerial(), 2u);
}
// Test ClearUpTo
@@ -110,6 +111,7 @@
queue.Enqueue(vector3, 1);
queue.ClearUpTo(0);
+ EXPECT_EQ(queue.LastSerial(), 1u);
std::vector<int> expectedValues = {9, 0};
for (int value : queue.IterateAll()) {
@@ -141,3 +143,14 @@
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