Remove device dependencies from ringbuffer.
Allows ringbuffer sub-allocator to be used for non-staging memory.
BUG=dawn:155
Change-Id: Id0021907f520909aaebaf79e992124a47797d38d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/9760
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Bryan Bernhart <bryan.bernhart@intel.com>
diff --git a/BUILD.gn b/BUILD.gn
index d02dfa2..ca4f82a 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -216,8 +216,8 @@
"src/dawn_native/ResourceHeap.h",
"src/dawn_native/ResourceMemoryAllocation.cpp",
"src/dawn_native/ResourceMemoryAllocation.h",
- "src/dawn_native/RingBuffer.cpp",
- "src/dawn_native/RingBuffer.h",
+ "src/dawn_native/RingBufferAllocator.cpp",
+ "src/dawn_native/RingBufferAllocator.h",
"src/dawn_native/Sampler.cpp",
"src/dawn_native/Sampler.h",
"src/dawn_native/ShaderModule.cpp",
@@ -769,7 +769,7 @@
"src/tests/unittests/PerStageTests.cpp",
"src/tests/unittests/RefCountedTests.cpp",
"src/tests/unittests/ResultTests.cpp",
- "src/tests/unittests/RingBufferTests.cpp",
+ "src/tests/unittests/RingBufferAllocatorTests.cpp",
"src/tests/unittests/SerialMapTests.cpp",
"src/tests/unittests/SerialQueueTests.cpp",
"src/tests/unittests/ToBackendTests.cpp",
diff --git a/src/dawn_native/Buffer.cpp b/src/dawn_native/Buffer.cpp
index bd5b2fb..21bc305 100644
--- a/src/dawn_native/Buffer.cpp
+++ b/src/dawn_native/Buffer.cpp
@@ -167,9 +167,7 @@
// error buffer.
// TODO(enga): Suballocate and reuse memory from a larger staging buffer so we don't create
// many small buffers.
- DynamicUploader* uploader = nullptr;
- DAWN_TRY_ASSIGN(uploader, GetDevice()->GetDynamicUploader());
- DAWN_TRY_ASSIGN(mStagingBuffer, uploader->CreateStagingBuffer(GetSize()));
+ DAWN_TRY_ASSIGN(mStagingBuffer, GetDevice()->CreateStagingBuffer(GetSize()));
ASSERT(mStagingBuffer->GetMappedPointer() != nullptr);
*mappedPointer = reinterpret_cast<uint8_t*>(mStagingBuffer->GetMappedPointer());
@@ -252,11 +250,11 @@
}
MaybeError BufferBase::SetSubDataImpl(uint32_t start, uint32_t count, const void* data) {
- DynamicUploader* uploader = nullptr;
- DAWN_TRY_ASSIGN(uploader, GetDevice()->GetDynamicUploader());
+ DynamicUploader* uploader = GetDevice()->GetDynamicUploader();
UploadHandle uploadHandle;
- DAWN_TRY_ASSIGN(uploadHandle, uploader->Allocate(count));
+ DAWN_TRY_ASSIGN(uploadHandle,
+ uploader->Allocate(count, GetDevice()->GetPendingCommandSerial()));
ASSERT(uploadHandle.mappedBuffer != nullptr);
memcpy(uploadHandle.mappedBuffer, data, count);
@@ -311,8 +309,7 @@
ASSERT(mStagingBuffer);
DAWN_TRY(GetDevice()->CopyFromStagingToBuffer(mStagingBuffer.get(), 0, this, 0, GetSize()));
- DynamicUploader* uploader = nullptr;
- DAWN_TRY_ASSIGN(uploader, GetDevice()->GetDynamicUploader());
+ DynamicUploader* uploader = GetDevice()->GetDynamicUploader();
uploader->ReleaseStagingBuffer(std::move(mStagingBuffer));
return {};
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 7bc8011..1bdc792 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -704,10 +704,7 @@
// Other implementation details
- ResultOrError<DynamicUploader*> DeviceBase::GetDynamicUploader() const {
- if (mDynamicUploader->IsEmpty()) {
- DAWN_TRY(mDynamicUploader->CreateAndAppendBuffer());
- }
+ DynamicUploader* DeviceBase::GetDynamicUploader() const {
return mDynamicUploader.get();
}
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index cf69282..c66826a 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -167,7 +167,7 @@
uint64_t destinationOffset,
uint64_t size) = 0;
- ResultOrError<DynamicUploader*> GetDynamicUploader() const;
+ DynamicUploader* GetDynamicUploader() const;
std::vector<const char*> GetEnabledExtensions() const;
std::vector<const char*> GetTogglesUsed() const;
diff --git a/src/dawn_native/DynamicUploader.cpp b/src/dawn_native/DynamicUploader.cpp
index c716332..b77cb94 100644
--- a/src/dawn_native/DynamicUploader.cpp
+++ b/src/dawn_native/DynamicUploader.cpp
@@ -18,15 +18,9 @@
namespace dawn_native {
- DynamicUploader::DynamicUploader(DeviceBase* device) : mDevice(device) {
- }
-
- ResultOrError<std::unique_ptr<StagingBufferBase>> DynamicUploader::CreateStagingBuffer(
- size_t size) {
- std::unique_ptr<StagingBufferBase> stagingBuffer;
- DAWN_TRY_ASSIGN(stagingBuffer, mDevice->CreateStagingBuffer(size));
- DAWN_TRY(stagingBuffer->Initialize());
- return stagingBuffer;
+ DynamicUploader::DynamicUploader(DeviceBase* device, size_t size) : mDevice(device) {
+ mRingBuffers.emplace_back(
+ std::unique_ptr<RingBuffer>(new RingBuffer{nullptr, RingBufferAllocator(size)}));
}
void DynamicUploader::ReleaseStagingBuffer(std::unique_ptr<StagingBufferBase> stagingBuffer) {
@@ -34,73 +28,78 @@
mDevice->GetPendingCommandSerial());
}
- 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) {
+ ResultOrError<UploadHandle> DynamicUploader::Allocate(size_t allocationSize, Serial serial) {
// Note: Validation ensures size is already aligned.
// First-fit: find next smallest buffer large enough to satisfy the allocation request.
- RingBuffer* targetRingBuffer = GetLargestBuffer();
+ RingBuffer* targetRingBuffer = mRingBuffers.back().get();
for (auto& ringBuffer : mRingBuffers) {
+ const RingBufferAllocator& ringBufferAllocator = ringBuffer->mAllocator;
// Prevent overflow.
- ASSERT(ringBuffer->GetSize() >= ringBuffer->GetUsedSize());
- const size_t remainingSize = ringBuffer->GetSize() - ringBuffer->GetUsedSize();
- if (size <= remainingSize) {
+ ASSERT(ringBufferAllocator.GetSize() >= ringBufferAllocator.GetUsedSize());
+ const size_t remainingSize =
+ ringBufferAllocator.GetSize() - ringBufferAllocator.GetUsedSize();
+ if (allocationSize <= remainingSize) {
targetRingBuffer = ringBuffer.get();
break;
}
}
- UploadHandle uploadHandle = UploadHandle{};
+ size_t startOffset = kInvalidOffset;
if (targetRingBuffer != nullptr) {
- uploadHandle = targetRingBuffer->SubAllocate(size);
+ startOffset = targetRingBuffer->mAllocator.Allocate(allocationSize, serial);
}
// Upon failure, append a newly created (and much larger) ring buffer to fulfill the
// request.
- if (uploadHandle.mappedBuffer == nullptr) {
+ if (startOffset == kInvalidOffset) {
// Compute the new max size (in powers of two to preserve alignment).
- size_t newMaxSize = targetRingBuffer->GetSize() * 2;
- while (newMaxSize < size) {
+ size_t newMaxSize = targetRingBuffer->mAllocator.GetSize() * 2;
+ while (newMaxSize < allocationSize) {
newMaxSize *= 2;
}
// TODO(bryan.bernhart@intel.com): Fall-back to no sub-allocations should this fail.
- DAWN_TRY(CreateAndAppendBuffer(newMaxSize));
- targetRingBuffer = GetLargestBuffer();
- uploadHandle = targetRingBuffer->SubAllocate(size);
+ mRingBuffers.emplace_back(std::unique_ptr<RingBuffer>(
+ new RingBuffer{nullptr, RingBufferAllocator(newMaxSize)}));
+
+ targetRingBuffer = mRingBuffers.back().get();
+ startOffset = targetRingBuffer->mAllocator.Allocate(allocationSize, serial);
}
- uploadHandle.stagingBuffer = targetRingBuffer->GetStagingBuffer();
+ ASSERT(startOffset != kInvalidOffset);
+
+ // Allocate the staging buffer backing the ringbuffer.
+ // Note: the first ringbuffer will be lazily created.
+ if (targetRingBuffer->mStagingBuffer == nullptr) {
+ std::unique_ptr<StagingBufferBase> stagingBuffer;
+ DAWN_TRY_ASSIGN(stagingBuffer,
+ mDevice->CreateStagingBuffer(targetRingBuffer->mAllocator.GetSize()));
+ targetRingBuffer->mStagingBuffer = std::move(stagingBuffer);
+ }
+
+ ASSERT(targetRingBuffer->mStagingBuffer != nullptr);
+
+ UploadHandle uploadHandle;
+ uploadHandle.stagingBuffer = targetRingBuffer->mStagingBuffer.get();
+ uploadHandle.mappedBuffer =
+ static_cast<uint8_t*>(uploadHandle.stagingBuffer->GetMappedPointer()) + startOffset;
+ uploadHandle.startOffset = startOffset;
return uploadHandle;
}
- void DynamicUploader::Tick(Serial lastCompletedSerial) {
+ void DynamicUploader::Deallocate(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);
+ mRingBuffers[i]->mAllocator.Deallocate(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) {
+ if (mRingBuffers[i]->mAllocator.Empty() && i < mRingBuffers.size() - 1) {
mRingBuffers.erase(mRingBuffers.begin() + i);
}
}
mReleasedStagingBuffers.ClearUpTo(lastCompletedSerial);
}
-
- 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
index 0caecb9..f0d4510 100644
--- a/src/dawn_native/DynamicUploader.h
+++ b/src/dawn_native/DynamicUploader.h
@@ -16,37 +16,42 @@
#define DAWNNATIVE_DYNAMICUPLOADER_H_
#include "dawn_native/Forward.h"
-#include "dawn_native/RingBuffer.h"
+#include "dawn_native/RingBufferAllocator.h"
+#include "dawn_native/StagingBuffer.h"
// DynamicUploader is the front-end implementation used to manage multiple ring buffers for upload
// usage.
namespace dawn_native {
+ struct UploadHandle {
+ uint8_t* mappedBuffer = nullptr;
+ size_t startOffset = 0;
+ StagingBufferBase* stagingBuffer = nullptr;
+ };
+
class DynamicUploader {
public:
- DynamicUploader(DeviceBase* device);
+ DynamicUploader(DeviceBase* device, size_t size = kBaseUploadBufferSize);
~DynamicUploader() = default;
- // We add functions to Create/Release StagingBuffers to the DynamicUploader as there's
+ // We add functions to Release StagingBuffers to the DynamicUploader as there's
// currently no place to track the allocated staging buffers such that they're freed after
// pending commands are finished. This should be changed when better resource allocation is
// implemented.
- ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer(size_t size);
void ReleaseStagingBuffer(std::unique_ptr<StagingBufferBase> stagingBuffer);
- ResultOrError<UploadHandle> Allocate(uint32_t size);
- void Tick(Serial lastCompletedSerial);
-
- RingBuffer* GetLargestBuffer();
-
- MaybeError CreateAndAppendBuffer(size_t size = kBaseUploadBufferSize);
-
- bool IsEmpty() const;
+ ResultOrError<UploadHandle> Allocate(size_t allocationSize, Serial serial);
+ void Deallocate(Serial lastCompletedSerial);
private:
// TODO(bryan.bernhart@intel.com): Figure out this value.
static constexpr size_t kBaseUploadBufferSize = 64000;
+ struct RingBuffer {
+ std::unique_ptr<StagingBufferBase> mStagingBuffer;
+ RingBufferAllocator mAllocator;
+ };
+
std::vector<std::unique_ptr<RingBuffer>> mRingBuffers;
SerialQueue<std::unique_ptr<StagingBufferBase>> mReleasedStagingBuffers;
DeviceBase* mDevice;
diff --git a/src/dawn_native/RingBuffer.cpp b/src/dawn_native/RingBuffer.cpp
deleted file mode 100644
index 90b9213..0000000
--- a/src/dawn_native/RingBuffer.cpp
+++ /dev/null
@@ -1,150 +0,0 @@
-// 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"
-
-#include <limits>
-
-// 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(bryan.bernhart@intel.com): 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
diff --git a/src/dawn_native/RingBufferAllocator.cpp b/src/dawn_native/RingBufferAllocator.cpp
new file mode 100644
index 0000000..6cb94b7
--- /dev/null
+++ b/src/dawn_native/RingBufferAllocator.cpp
@@ -0,0 +1,116 @@
+// 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/RingBufferAllocator.h"
+
+// Note: Current RingBufferAllocator 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(bryan.bernhart@intel.com): Follow-up with ringbuffer optimization.
+namespace dawn_native {
+
+ RingBufferAllocator::RingBufferAllocator(size_t maxSize) : mMaxBlockSize(maxSize) {
+ }
+
+ void RingBufferAllocator::Deallocate(Serial lastCompletedSerial) {
+ // 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 RingBufferAllocator::GetSize() const {
+ return mMaxBlockSize;
+ }
+
+ size_t RingBufferAllocator::GetUsedSize() const {
+ return mUsedSize;
+ }
+
+ bool RingBufferAllocator::Empty() const {
+ return mInflightRequests.Empty();
+ }
+
+ // 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.
+ size_t RingBufferAllocator::Allocate(size_t allocationSize, Serial serial) {
+ // 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 (allocationSize == 0 || mUsedSize >= mMaxBlockSize) {
+ return kInvalidOffset;
+ }
+
+ size_t startOffset = kInvalidOffset;
+
+ // 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 + allocationSize <= mMaxBlockSize) {
+ startOffset = mUsedEndOffset;
+ mUsedEndOffset += allocationSize;
+ mUsedSize += allocationSize;
+ mCurrentRequestSize += allocationSize;
+ } else if (allocationSize <= mUsedStartOffset) { // Try to sub-alloc at front.
+ // Count the space at the end so that a subsequent
+ // sub-alloc cannot not succeed when the buffer is full.
+ const size_t requestSize = (mMaxBlockSize - mUsedEndOffset) + allocationSize;
+
+ startOffset = 0;
+ mUsedEndOffset = allocationSize;
+ mUsedSize += requestSize;
+ mCurrentRequestSize += requestSize;
+ }
+ } else if (mUsedEndOffset + allocationSize <=
+ mUsedStartOffset) { // Otherwise, buffer is split where sub-alloc must be
+ // in-between.
+ startOffset = mUsedEndOffset;
+ mUsedEndOffset += allocationSize;
+ mUsedSize += allocationSize;
+ mCurrentRequestSize += allocationSize;
+ }
+
+ if (startOffset != kInvalidOffset) {
+ Request request;
+ request.endOffset = mUsedEndOffset;
+ request.size = mCurrentRequestSize;
+
+ mInflightRequests.Enqueue(std::move(request), serial);
+ mCurrentRequestSize = 0; // reset
+ }
+
+ return startOffset;
+ }
+} // namespace dawn_native
diff --git a/src/dawn_native/RingBuffer.h b/src/dawn_native/RingBufferAllocator.h
similarity index 61%
rename from src/dawn_native/RingBuffer.h
rename to src/dawn_native/RingBufferAllocator.h
index dbc51bc..630da98 100644
--- a/src/dawn_native/RingBuffer.h
+++ b/src/dawn_native/RingBufferAllocator.h
@@ -12,46 +12,32 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef DAWNNATIVE_RINGBUFFER_H_
-#define DAWNNATIVE_RINGBUFFER_H_
+#ifndef DAWNNATIVE_RINGBUFFERALLOCATOR_H_
+#define DAWNNATIVE_RINGBUFFERALLOCATOR_H_
#include "common/SerialQueue.h"
-#include "dawn_native/StagingBuffer.h"
+#include <limits>
#include <memory>
-// RingBuffer is the front-end implementation used to manage a ring buffer in GPU memory.
+// RingBufferAllocator 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;
- };
+ static constexpr size_t kInvalidOffset = std::numeric_limits<size_t>::max();
- class DeviceBase;
-
- class RingBuffer {
+ class RingBufferAllocator {
public:
- RingBuffer(DeviceBase* device, size_t size);
- ~RingBuffer() = default;
+ RingBufferAllocator(size_t maxSize);
+ ~RingBufferAllocator() = default;
- MaybeError Initialize();
+ size_t Allocate(size_t allocationSize, Serial serial);
+ void Deallocate(Serial lastCompletedSerial);
- 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;
@@ -62,13 +48,11 @@
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 mMaxBlockSize = 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
+#endif // DAWNNATIVE_RINGBUFFERALLOCATOR_H_
\ No newline at end of file
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index c4e5e96..d9bb0a8 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -216,8 +216,8 @@
mCompletedSerial = mFence->GetCompletedValue();
// Uploader should tick before the resource allocator
- // as it enqueues resources to be released.
- mDynamicUploader->Tick(mCompletedSerial);
+ // as it enqueued resources to be released.
+ mDynamicUploader->Deallocate(mCompletedSerial);
mResourceAllocator->Tick(mCompletedSerial);
mCommandAllocatorManager->Tick(mCompletedSerial);
@@ -319,6 +319,7 @@
ResultOrError<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) {
std::unique_ptr<StagingBufferBase> stagingBuffer =
std::make_unique<StagingBuffer>(size, this);
+ DAWN_TRY(stagingBuffer->Initialize());
return std::move(stagingBuffer);
}
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index 9784358..e92345c 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -538,10 +538,10 @@
return DAWN_OUT_OF_MEMORY_ERROR("Unable to allocate buffer.");
}
uint32_t bufferSize = static_cast<uint32_t>(bufferSize64);
- DynamicUploader* uploader = nullptr;
- DAWN_TRY_ASSIGN(uploader, device->GetDynamicUploader());
+ DynamicUploader* uploader = device->GetDynamicUploader();
UploadHandle uploadHandle;
- DAWN_TRY_ASSIGN(uploadHandle, uploader->Allocate(bufferSize));
+ DAWN_TRY_ASSIGN(uploadHandle,
+ uploader->Allocate(bufferSize, device->GetPendingCommandSerial()));
std::fill(reinterpret_cast<uint32_t*>(uploadHandle.mappedBuffer),
reinterpret_cast<uint32_t*>(uploadHandle.mappedBuffer + bufferSize),
clearColor);
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index 52fd864..046ed13 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -154,7 +154,7 @@
void Device::TickImpl() {
Serial completedSerial = GetCompletedCommandSerial();
- mDynamicUploader->Tick(completedSerial);
+ mDynamicUploader->Deallocate(completedSerial);
mMapTracker->Tick(completedSerial);
if (mPendingCommands != nil) {
@@ -239,6 +239,7 @@
ResultOrError<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) {
std::unique_ptr<StagingBufferBase> stagingBuffer =
std::make_unique<StagingBuffer>(size, this);
+ DAWN_TRY(stagingBuffer->Initialize());
return std::move(stagingBuffer);
}
diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp
index 44e62db..303445a 100644
--- a/src/dawn_native/null/DeviceNull.cpp
+++ b/src/dawn_native/null/DeviceNull.cpp
@@ -150,6 +150,7 @@
ResultOrError<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) {
std::unique_ptr<StagingBufferBase> stagingBuffer =
std::make_unique<StagingBuffer>(size, this);
+ DAWN_TRY(stagingBuffer->Initialize());
return std::move(stagingBuffer);
}
diff --git a/src/dawn_native/null/DeviceNull.h b/src/dawn_native/null/DeviceNull.h
index 640ff58..232dff8 100644
--- a/src/dawn_native/null/DeviceNull.h
+++ b/src/dawn_native/null/DeviceNull.h
@@ -26,7 +26,7 @@
#include "dawn_native/PipelineLayout.h"
#include "dawn_native/Queue.h"
#include "dawn_native/RenderPipeline.h"
-#include "dawn_native/RingBuffer.h"
+#include "dawn_native/RingBufferAllocator.h"
#include "dawn_native/Sampler.h"
#include "dawn_native/ShaderModule.h"
#include "dawn_native/StagingBuffer.h"
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 1a66e2b..bd63b11 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -217,7 +217,7 @@
// Uploader should tick before the resource allocator
// as it enqueues resources to be released.
- mDynamicUploader->Tick(mCompletedSerial);
+ mDynamicUploader->Deallocate(mCompletedSerial);
mMemoryAllocator->Tick(mCompletedSerial);
@@ -561,6 +561,7 @@
ResultOrError<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) {
std::unique_ptr<StagingBufferBase> stagingBuffer =
std::make_unique<StagingBuffer>(size, this);
+ DAWN_TRY(stagingBuffer->Initialize());
return std::move(stagingBuffer);
}
diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp
index 43a18c5..1f29ea0 100644
--- a/src/dawn_native/vulkan/TextureVk.cpp
+++ b/src/dawn_native/vulkan/TextureVk.cpp
@@ -687,10 +687,10 @@
return DAWN_OUT_OF_MEMORY_ERROR("Unable to allocate buffer.");
}
uint32_t bufferSize = static_cast<uint32_t>(bufferSize64);
- DynamicUploader* uploader = nullptr;
- DAWN_TRY_ASSIGN(uploader, device->GetDynamicUploader());
+ DynamicUploader* uploader = device->GetDynamicUploader();
UploadHandle uploadHandle;
- DAWN_TRY_ASSIGN(uploadHandle, uploader->Allocate(bufferSize));
+ DAWN_TRY_ASSIGN(uploadHandle,
+ uploader->Allocate(bufferSize, device->GetPendingCommandSerial()));
std::fill(reinterpret_cast<uint32_t*>(uploadHandle.mappedBuffer),
reinterpret_cast<uint32_t*>(uploadHandle.mappedBuffer + bufferSize),
clearColor);
diff --git a/src/tests/unittests/RingBufferAllocatorTests.cpp b/src/tests/unittests/RingBufferAllocatorTests.cpp
new file mode 100644
index 0000000..cc381cc
--- /dev/null
+++ b/src/tests/unittests/RingBufferAllocatorTests.cpp
@@ -0,0 +1,161 @@
+// 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/RingBufferAllocator.h"
+
+using namespace dawn_native;
+
+// Number of basic tests for Ringbuffer
+TEST(RingBufferAllocatorTests, BasicTest) {
+ constexpr size_t sizeInBytes = 64000;
+ RingBufferAllocator allocator(sizeInBytes);
+
+ // Ensure no requests exist on empty buffer.
+ EXPECT_TRUE(allocator.Empty());
+
+ ASSERT_EQ(allocator.GetSize(), sizeInBytes);
+
+ // Ensure failure upon sub-allocating an oversized request.
+ ASSERT_EQ(allocator.Allocate(sizeInBytes + 1, 0), kInvalidOffset);
+
+ // Fill the entire buffer with two requests of equal size.
+ ASSERT_EQ(allocator.Allocate(sizeInBytes / 2, 1), 0u);
+ ASSERT_EQ(allocator.Allocate(sizeInBytes / 2, 2), 32000u);
+
+ // Ensure the buffer is full.
+ ASSERT_EQ(allocator.Allocate(1, 3), kInvalidOffset);
+}
+
+// Tests that several ringbuffer allocations do not fail.
+TEST(RingBufferAllocatorTests, RingBufferManyAlloc) {
+ constexpr size_t maxNumOfFrames = 64000;
+ constexpr size_t frameSizeInBytes = 4;
+
+ RingBufferAllocator allocator(maxNumOfFrames * frameSizeInBytes);
+
+ size_t offset = 0;
+ for (size_t i = 0; i < maxNumOfFrames; ++i) {
+ offset = allocator.Allocate(frameSizeInBytes, i);
+ ASSERT_EQ(offset, i * frameSizeInBytes);
+ }
+}
+
+// Tests ringbuffer sub-allocations of the same serial are correctly tracked.
+TEST(RingBufferAllocatorTests, AllocInSameFrame) {
+ constexpr size_t maxNumOfFrames = 3;
+ constexpr size_t frameSizeInBytes = 4;
+
+ RingBufferAllocator allocator(maxNumOfFrames * frameSizeInBytes);
+
+ // F1
+ // [xxxx|--------]
+ size_t offset = allocator.Allocate(frameSizeInBytes, 1);
+
+ // F1 F2
+ // [xxxx|xxxx|----]
+
+ offset = allocator.Allocate(frameSizeInBytes, 2);
+
+ // F1 F2
+ // [xxxx|xxxxxxxx]
+
+ offset = allocator.Allocate(frameSizeInBytes, 2);
+
+ ASSERT_EQ(offset, 8u);
+ ASSERT_EQ(allocator.GetUsedSize(), frameSizeInBytes * 3);
+
+ allocator.Deallocate(2);
+
+ ASSERT_EQ(allocator.GetUsedSize(), 0u);
+ EXPECT_TRUE(allocator.Empty());
+}
+
+// Tests ringbuffer sub-allocation at various offsets.
+TEST(RingBufferAllocatorTests, RingBufferSubAlloc) {
+ constexpr size_t maxNumOfFrames = 10;
+ constexpr size_t frameSizeInBytes = 4;
+
+ RingBufferAllocator allocator(maxNumOfFrames * frameSizeInBytes);
+
+ // Sub-alloc the first eight frames.
+ Serial serial = 1;
+ for (size_t i = 0; i < 8; ++i) {
+ allocator.Allocate(frameSizeInBytes, serial);
+ serial += 1;
+ }
+
+ // 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)
+ ASSERT_EQ(allocator.Allocate(frameSizeInBytes * 3, serial + 1), kInvalidOffset);
+ ASSERT_EQ(allocator.GetUsedSize(), frameSizeInBytes * 8);
+
+ // Reclaim the first 3 frames.
+ allocator.Deallocate(3);
+
+ // F4 F5 F6 F7 F8
+ // [------------|xxxx|xxxx|xxxx|xxxx|xxxx|--------]
+ //
+ ASSERT_EQ(allocator.GetUsedSize(), frameSizeInBytes * 5);
+
+ // Re-try the over-sized allocation.
+ size_t offset = allocator.Allocate(frameSizeInBytes * 3, serial);
+
+ // F9 F4 F5 F6 F7 F8
+ // [xxxxxxxxxxxx|xxxx|xxxx|xxxx|xxxx|xxxx|xxxxxxxx]
+ // ^^^^^^^^ wasted
+
+ // In this example, Deallocate(8) could not reclaim the wasted bytes. The wasted bytes
+ // were added to F9's sub-allocation.
+ // TODO(bryan.bernhart@intel.com): Decide if Deallocate(8) should free these wasted bytes.
+
+ ASSERT_EQ(offset, 0u);
+ ASSERT_EQ(allocator.GetUsedSize(), frameSizeInBytes * maxNumOfFrames);
+
+ // Ensure we are full.
+ ASSERT_EQ(allocator.Allocate(frameSizeInBytes, serial + 1), kInvalidOffset);
+
+ // Reclaim the next two frames.
+ allocator.Deallocate(5);
+
+ // F9 F4 F5 F6 F7 F8
+ // [xxxxxxxxxxxx|----|----|xxxx|xxxx|xxxx|xxxxxxxx]
+ //
+ ASSERT_EQ(allocator.GetUsedSize(), frameSizeInBytes * 8);
+
+ // Sub-alloc the chunk in the middle.
+ serial += 1;
+ offset = allocator.Allocate(frameSizeInBytes * 2, serial);
+
+ ASSERT_EQ(offset, frameSizeInBytes * 3);
+ ASSERT_EQ(allocator.GetUsedSize(), frameSizeInBytes * maxNumOfFrames);
+
+ // F9 F10 F6 F7 F8
+ // [xxxxxxxxxxxx|xxxxxxxxx|xxxx|xxxx|xxxx|xxxxxxxx]
+ //
+
+ // Ensure we are full.
+ ASSERT_EQ(allocator.Allocate(frameSizeInBytes, serial + 1), kInvalidOffset);
+
+ // Reclaim all.
+ allocator.Deallocate(maxNumOfFrames);
+
+ EXPECT_TRUE(allocator.Empty());
+}
diff --git a/src/tests/unittests/RingBufferTests.cpp b/src/tests/unittests/RingBufferTests.cpp
deleted file mode 100644
index 92ad835..0000000
--- a/src/tests/unittests/RingBufferTests.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-// 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(bryan.bernhart@intel.com): Create this device through the adapter.
- mDevice = std::make_unique<null::Device>(/*adapter*/ nullptr, /*deviceDescriptor*/ 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(bryan.bernhart@intel.com): 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());
-}