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>
This commit is contained in:
parent
79ff8bead9
commit
74e95fff4a
11
BUILD.gn
11
BUILD.gn
|
@ -451,6 +451,8 @@ source_set("libdawn_native_sources") {
|
|||
"src/dawn_native/ComputePipeline.h",
|
||||
"src/dawn_native/Device.cpp",
|
||||
"src/dawn_native/Device.h",
|
||||
"src/dawn_native/DynamicUploader.cpp",
|
||||
"src/dawn_native/DynamicUploader.h",
|
||||
"src/dawn_native/Error.cpp",
|
||||
"src/dawn_native/Error.h",
|
||||
"src/dawn_native/ErrorData.cpp",
|
||||
|
@ -485,10 +487,14 @@ source_set("libdawn_native_sources") {
|
|||
"src/dawn_native/RenderPassEncoder.h",
|
||||
"src/dawn_native/RenderPipeline.cpp",
|
||||
"src/dawn_native/RenderPipeline.h",
|
||||
"src/dawn_native/RingBuffer.cpp",
|
||||
"src/dawn_native/RingBuffer.h",
|
||||
"src/dawn_native/Sampler.cpp",
|
||||
"src/dawn_native/Sampler.h",
|
||||
"src/dawn_native/ShaderModule.cpp",
|
||||
"src/dawn_native/ShaderModule.h",
|
||||
"src/dawn_native/StagingBuffer.cpp",
|
||||
"src/dawn_native/StagingBuffer.h",
|
||||
"src/dawn_native/SwapChain.cpp",
|
||||
"src/dawn_native/SwapChain.h",
|
||||
"src/dawn_native/Texture.cpp",
|
||||
|
@ -533,12 +539,12 @@ source_set("libdawn_native_sources") {
|
|||
"src/dawn_native/d3d12/RenderPipelineD3D12.h",
|
||||
"src/dawn_native/d3d12/ResourceAllocator.cpp",
|
||||
"src/dawn_native/d3d12/ResourceAllocator.h",
|
||||
"src/dawn_native/d3d12/ResourceUploader.cpp",
|
||||
"src/dawn_native/d3d12/ResourceUploader.h",
|
||||
"src/dawn_native/d3d12/SamplerD3D12.cpp",
|
||||
"src/dawn_native/d3d12/SamplerD3D12.h",
|
||||
"src/dawn_native/d3d12/ShaderModuleD3D12.cpp",
|
||||
"src/dawn_native/d3d12/ShaderModuleD3D12.h",
|
||||
"src/dawn_native/d3d12/StagingBufferD3D12.cpp",
|
||||
"src/dawn_native/d3d12/StagingBufferD3D12.h",
|
||||
"src/dawn_native/d3d12/SwapChainD3D12.cpp",
|
||||
"src/dawn_native/d3d12/SwapChainD3D12.h",
|
||||
"src/dawn_native/d3d12/TextureCopySplitter.cpp",
|
||||
|
@ -899,6 +905,7 @@ test("dawn_unittests") {
|
|||
"src/tests/unittests/PerStageTests.cpp",
|
||||
"src/tests/unittests/RefCountedTests.cpp",
|
||||
"src/tests/unittests/ResultTests.cpp",
|
||||
"src/tests/unittests/RingBufferTests.cpp",
|
||||
"src/tests/unittests/SerialMapTests.cpp",
|
||||
"src/tests/unittests/SerialQueueTests.cpp",
|
||||
"src/tests/unittests/ToBackendTests.cpp",
|
||||
|
|
|
@ -110,6 +110,7 @@ class SerialStorage {
|
|||
void ClearUpTo(Serial serial);
|
||||
|
||||
Serial FirstSerial() const;
|
||||
Serial LastSerial() const;
|
||||
|
||||
protected:
|
||||
// Returns the first StorageIterator that a serial bigger than serial.
|
||||
|
@ -162,6 +163,12 @@ Serial SerialStorage<Derived>::FirstSerial() const {
|
|||
return mStorage.begin()->first;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -106,7 +106,9 @@ namespace dawn_native {
|
|||
return;
|
||||
}
|
||||
|
||||
SetSubDataImpl(start, count, data);
|
||||
if (GetDevice()->ConsumedError(SetSubDataImpl(start, count, data))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BufferBase::MapReadAsync(uint32_t start,
|
||||
|
|
|
@ -63,7 +63,7 @@ namespace dawn_native {
|
|||
void CallMapWriteCallback(uint32_t serial, dawnBufferMapAsyncStatus status, void* pointer);
|
||||
|
||||
private:
|
||||
virtual void SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) = 0;
|
||||
virtual MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) = 0;
|
||||
virtual void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t size) = 0;
|
||||
virtual void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t size) = 0;
|
||||
virtual void UnmapImpl() = 0;
|
||||
|
|
|
@ -31,6 +31,8 @@ namespace dawn_native {
|
|||
|
||||
class AdapterBase;
|
||||
class FenceSignalTracker;
|
||||
class DynamicUploader;
|
||||
class StagingBufferBase;
|
||||
|
||||
class DeviceBase {
|
||||
public:
|
||||
|
@ -60,6 +62,7 @@ namespace dawn_native {
|
|||
|
||||
virtual Serial GetCompletedCommandSerial() const = 0;
|
||||
virtual Serial GetLastSubmittedCommandSerial() const = 0;
|
||||
virtual Serial GetPendingCommandSerial() const = 0;
|
||||
virtual void TickImpl() = 0;
|
||||
|
||||
// Many Dawn objects are completely immutable once created which means that if two
|
||||
|
@ -111,6 +114,14 @@ namespace dawn_native {
|
|||
|
||||
virtual const PCIInfo& GetPCIInfo() const;
|
||||
|
||||
virtual ResultOrError<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;
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -44,6 +44,7 @@ namespace dawn_native {
|
|||
class SamplerBase;
|
||||
class ShaderModuleBase;
|
||||
class ShaderModuleBuilder;
|
||||
class StagingBufferBase;
|
||||
class SwapChainBase;
|
||||
class SwapChainBuilder;
|
||||
class TextureBase;
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -93,6 +93,11 @@ namespace dawn_native {
|
|||
using BackendType = typename BackendTraits::ShaderModuleType;
|
||||
};
|
||||
|
||||
template <typename BackendTraits>
|
||||
struct ToBackendTraits<StagingBufferBase, BackendTraits> {
|
||||
using BackendType = typename BackendTraits::StagingBufferType;
|
||||
};
|
||||
|
||||
template <typename BackendTraits>
|
||||
struct ToBackendTraits<TextureBase, BackendTraits> {
|
||||
using BackendType = typename BackendTraits::TextureType;
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
#include "common/Assert.h"
|
||||
#include "common/Constants.h"
|
||||
#include "common/Math.h"
|
||||
#include "dawn_native/DynamicUploader.h"
|
||||
#include "dawn_native/d3d12/DeviceD3D12.h"
|
||||
#include "dawn_native/d3d12/ResourceAllocator.h"
|
||||
#include "dawn_native/d3d12/ResourceUploader.h"
|
||||
|
||||
namespace dawn_native { namespace d3d12 {
|
||||
|
||||
|
@ -161,11 +161,22 @@ namespace dawn_native { namespace d3d12 {
|
|||
}
|
||||
}
|
||||
|
||||
void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) {
|
||||
MaybeError Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) {
|
||||
Device* device = ToBackend(GetDevice());
|
||||
|
||||
TransitionUsageNow(device->GetPendingCommandList(), dawn::BufferUsageBit::TransferDst);
|
||||
device->GetResourceUploader()->BufferSubData(mResource, start, count, data);
|
||||
DynamicUploader* uploader = nullptr;
|
||||
DAWN_TRY_ASSIGN(uploader, device->GetDynamicUploader());
|
||||
|
||||
UploadHandle uploadHandle;
|
||||
DAWN_TRY_ASSIGN(uploadHandle, uploader->Allocate(count, kDefaultAlignment));
|
||||
ASSERT(uploadHandle.mappedBuffer != nullptr);
|
||||
|
||||
memcpy(uploadHandle.mappedBuffer, data, count);
|
||||
|
||||
DAWN_TRY(device->CopyFromStagingToBuffer(uploadHandle.stagingBuffer,
|
||||
uploadHandle.startOffset, this, start, count));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) {
|
||||
|
|
|
@ -39,11 +39,15 @@ namespace dawn_native { namespace d3d12 {
|
|||
|
||||
private:
|
||||
// Dawn API
|
||||
void SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override;
|
||||
MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override;
|
||||
void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
|
||||
void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
|
||||
void UnmapImpl() override;
|
||||
|
||||
// TODO(b-brber): Remove once alignment constraint is added to validation (dawn:73).
|
||||
static constexpr size_t kDefaultAlignment =
|
||||
4; // D3D does not specify so we assume 4-byte alignment to be safe.
|
||||
|
||||
ComPtr<ID3D12Resource> mResource;
|
||||
bool mFixedResourceState = false;
|
||||
dawn::BufferUsageBit mLastUsage = dawn::BufferUsageBit::None;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "common/Assert.h"
|
||||
#include "dawn_native/BackendConnection.h"
|
||||
#include "dawn_native/DynamicUploader.h"
|
||||
#include "dawn_native/d3d12/BindGroupD3D12.h"
|
||||
#include "dawn_native/d3d12/BindGroupLayoutD3D12.h"
|
||||
#include "dawn_native/d3d12/BufferD3D12.h"
|
||||
|
@ -30,9 +31,9 @@
|
|||
#include "dawn_native/d3d12/RenderPassDescriptorD3D12.h"
|
||||
#include "dawn_native/d3d12/RenderPipelineD3D12.h"
|
||||
#include "dawn_native/d3d12/ResourceAllocator.h"
|
||||
#include "dawn_native/d3d12/ResourceUploader.h"
|
||||
#include "dawn_native/d3d12/SamplerD3D12.h"
|
||||
#include "dawn_native/d3d12/ShaderModuleD3D12.h"
|
||||
#include "dawn_native/d3d12/StagingBufferD3D12.h"
|
||||
#include "dawn_native/d3d12/SwapChainD3D12.h"
|
||||
#include "dawn_native/d3d12/TextureD3D12.h"
|
||||
|
||||
|
@ -134,7 +135,7 @@ namespace dawn_native { namespace d3d12 {
|
|||
mDescriptorHeapAllocator = std::make_unique<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 @@ namespace dawn_native { namespace d3d12 {
|
|||
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 @@ namespace dawn_native { namespace d3d12 {
|
|||
mDescriptorHeapAllocator->Tick(mCompletedSerial);
|
||||
mMapRequestTracker->Tick(mCompletedSerial);
|
||||
mUsedComObjectRefs.ClearUpTo(mCompletedSerial);
|
||||
mDynamicUploader->Tick(mCompletedSerial);
|
||||
ExecuteCommandLists({});
|
||||
NextSerial();
|
||||
}
|
||||
|
@ -335,4 +333,33 @@ namespace dawn_native { namespace d3d12 {
|
|||
mPCIInfo.name = converter.to_bytes(adapterDesc.Description);
|
||||
}
|
||||
|
||||
ResultOrError<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
|
||||
|
|
|
@ -31,7 +31,6 @@ namespace dawn_native { namespace d3d12 {
|
|||
class MapRequestTracker;
|
||||
class PlatformFunctions;
|
||||
class ResourceAllocator;
|
||||
class ResourceUploader;
|
||||
|
||||
void ASSERT_SUCCESS(HRESULT hr);
|
||||
|
||||
|
@ -61,11 +60,10 @@ namespace dawn_native { namespace d3d12 {
|
|||
MapRequestTracker* GetMapRequestTracker() const;
|
||||
const PlatformFunctions* GetFunctions();
|
||||
ResourceAllocator* GetResourceAllocator();
|
||||
ResourceUploader* GetResourceUploader();
|
||||
|
||||
void OpenCommandList(ComPtr<ID3D12GraphicsCommandList>* commandList);
|
||||
ComPtr<ID3D12GraphicsCommandList> GetPendingCommandList();
|
||||
Serial GetPendingCommandSerial() const;
|
||||
Serial GetPendingCommandSerial() const override;
|
||||
|
||||
void NextSerial();
|
||||
void WaitForSerial(Serial serial);
|
||||
|
@ -74,6 +72,15 @@ namespace dawn_native { namespace d3d12 {
|
|||
|
||||
void ExecuteCommandLists(std::initializer_list<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 @@ namespace dawn_native { namespace d3d12 {
|
|||
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
|
||||
|
|
|
@ -33,6 +33,7 @@ namespace dawn_native { namespace d3d12 {
|
|||
class RenderPipeline;
|
||||
class Sampler;
|
||||
class ShaderModule;
|
||||
class StagingBuffer;
|
||||
class SwapChain;
|
||||
class Texture;
|
||||
class TextureView;
|
||||
|
@ -52,6 +53,7 @@ namespace dawn_native { namespace d3d12 {
|
|||
using RenderPipelineType = RenderPipeline;
|
||||
using SamplerType = Sampler;
|
||||
using ShaderModuleType = ShaderModule;
|
||||
using StagingBufferType = StagingBuffer;
|
||||
using SwapChainType = SwapChain;
|
||||
using TextureType = Texture;
|
||||
using TextureViewType = TextureView;
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2017 The Dawn Authors
|
||||
// Copyright 2018 The Dawn Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,37 +12,29 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef DAWNNATIVE_D3D12_RESOURCEUPLOADER_H_
|
||||
#define DAWNNATIVE_D3D12_RESOURCEUPLOADER_H_
|
||||
#ifndef DAWNNATIVE_STAGINGBUFFERD3D12_H_
|
||||
#define DAWNNATIVE_STAGINGBUFFERD3D12_H_
|
||||
|
||||
#include "dawn_native/StagingBuffer.h"
|
||||
#include "dawn_native/d3d12/d3d12_platform.h"
|
||||
|
||||
#include "dawn_native/Forward.h"
|
||||
|
||||
namespace dawn_native { namespace d3d12 {
|
||||
|
||||
class Device;
|
||||
|
||||
class ResourceUploader {
|
||||
class StagingBuffer : public StagingBufferBase {
|
||||
public:
|
||||
ResourceUploader(Device* device);
|
||||
StagingBuffer(size_t size, Device* device);
|
||||
~StagingBuffer();
|
||||
|
||||
void BufferSubData(ComPtr<ID3D12Resource> resource,
|
||||
uint32_t start,
|
||||
uint32_t count,
|
||||
const void* data);
|
||||
ID3D12Resource* GetResource() const;
|
||||
|
||||
MaybeError Initialize() override;
|
||||
|
||||
private:
|
||||
struct UploadHandle {
|
||||
ComPtr<ID3D12Resource> resource;
|
||||
uint8_t* mappedBuffer;
|
||||
};
|
||||
|
||||
UploadHandle GetUploadBuffer(uint32_t requiredSize);
|
||||
void Release(UploadHandle uploadHandle);
|
||||
|
||||
Device* mDevice;
|
||||
ComPtr<ID3D12Resource> mUploadHeap;
|
||||
};
|
||||
}} // namespace dawn_native::d3d12
|
||||
|
||||
#endif // DAWNNATIVE_D3D12_RESOURCEUPLOADER_H_
|
||||
#endif // DAWNNATIVE_STAGINGBUFFERD3D12_H_
|
|
@ -34,7 +34,7 @@ namespace dawn_native { namespace metal {
|
|||
void OnMapCommandSerialFinished(uint32_t mapSerial, uint32_t offset, bool isWrite);
|
||||
|
||||
private:
|
||||
void SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override;
|
||||
MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override;
|
||||
void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
|
||||
void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
|
||||
void UnmapImpl() override;
|
||||
|
|
|
@ -49,9 +49,10 @@ namespace dawn_native { namespace metal {
|
|||
}
|
||||
}
|
||||
|
||||
void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) {
|
||||
MaybeError Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) {
|
||||
auto* uploader = ToBackend(GetDevice())->GetResourceUploader();
|
||||
uploader->BufferSubData(mMtlBuffer, start, count, data);
|
||||
return {};
|
||||
}
|
||||
|
||||
void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t) {
|
||||
|
|
|
@ -52,12 +52,19 @@ namespace dawn_native { namespace metal {
|
|||
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;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "dawn_native/BackendConnection.h"
|
||||
#include "dawn_native/BindGroup.h"
|
||||
#include "dawn_native/BindGroupLayout.h"
|
||||
#include "dawn_native/DynamicUploader.h"
|
||||
#include "dawn_native/RenderPassDescriptor.h"
|
||||
#include "dawn_native/metal/BufferMTL.h"
|
||||
#include "dawn_native/metal/CommandBufferMTL.h"
|
||||
|
@ -297,4 +298,16 @@ namespace dawn_native { namespace metal {
|
|||
mPCIInfo.name = std::string([mMtlDevice.name UTF8String]);
|
||||
}
|
||||
|
||||
ResultOrError<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
|
||||
|
|
|
@ -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 @@ namespace dawn_native { namespace null {
|
|||
// Device
|
||||
|
||||
Device::Device(Adapter* adapter) : DeviceBase(adapter) {
|
||||
mDynamicUploader = std::make_unique<DynamicUploader>(this);
|
||||
}
|
||||
|
||||
Device::~Device() {
|
||||
|
@ -122,6 +124,20 @@ namespace dawn_native { namespace null {
|
|||
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 @@ namespace dawn_native { namespace null {
|
|||
return mLastSubmittedSerial;
|
||||
}
|
||||
|
||||
Serial Device::GetPendingCommandSerial() const {
|
||||
return mLastSubmittedSerial + 1;
|
||||
}
|
||||
|
||||
void Device::TickImpl() {
|
||||
SubmitPendingOperations();
|
||||
}
|
||||
|
@ -179,10 +199,11 @@ namespace dawn_native { namespace null {
|
|||
}
|
||||
}
|
||||
|
||||
void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) {
|
||||
MaybeError Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) {
|
||||
ASSERT(start + count <= GetSize());
|
||||
ASSERT(mBackingData);
|
||||
memcpy(mBackingData.get() + start, data, count);
|
||||
return {};
|
||||
}
|
||||
|
||||
void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) {
|
||||
|
@ -272,4 +293,15 @@ namespace dawn_native { namespace null {
|
|||
return dawn::TextureFormat::R8G8B8A8Unorm;
|
||||
}
|
||||
|
||||
// StagingBuffer
|
||||
|
||||
StagingBuffer::StagingBuffer(size_t size, Device* device) : StagingBufferBase(size) {
|
||||
}
|
||||
|
||||
MaybeError StagingBuffer::Initialize() {
|
||||
mBuffer = std::make_unique<uint8_t[]>(GetSize());
|
||||
mMappedPointer = mBuffer.get();
|
||||
return {};
|
||||
}
|
||||
|
||||
}} // namespace dawn_native::null
|
||||
|
|
|
@ -26,8 +26,10 @@
|
|||
#include "dawn_native/Queue.h"
|
||||
#include "dawn_native/RenderPassDescriptor.h"
|
||||
#include "dawn_native/RenderPipeline.h"
|
||||
#include "dawn_native/RingBuffer.h"
|
||||
#include "dawn_native/Sampler.h"
|
||||
#include "dawn_native/ShaderModule.h"
|
||||
#include "dawn_native/StagingBuffer.h"
|
||||
#include "dawn_native/SwapChain.h"
|
||||
#include "dawn_native/Texture.h"
|
||||
#include "dawn_native/ToBackend.h"
|
||||
|
@ -96,11 +98,19 @@ namespace dawn_native { namespace null {
|
|||
|
||||
Serial GetCompletedCommandSerial() const final override;
|
||||
Serial GetLastSubmittedCommandSerial() const final override;
|
||||
Serial GetPendingCommandSerial() const override;
|
||||
void TickImpl() override;
|
||||
|
||||
void AddPendingOperation(std::unique_ptr<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 @@ namespace dawn_native { namespace null {
|
|||
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 @@ namespace dawn_native { namespace null {
|
|||
void MapReadOperationCompleted(uint32_t serial, void* ptr, bool isWrite);
|
||||
|
||||
private:
|
||||
void SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override;
|
||||
MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override;
|
||||
void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
|
||||
void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
|
||||
void UnmapImpl() override;
|
||||
|
@ -186,6 +197,15 @@ namespace dawn_native { namespace null {
|
|||
dawn::TextureFormat GetPreferredFormat() const;
|
||||
};
|
||||
|
||||
class StagingBuffer : public StagingBufferBase {
|
||||
public:
|
||||
StagingBuffer(size_t size, Device* device);
|
||||
MaybeError Initialize() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<uint8_t[]> mBuffer;
|
||||
};
|
||||
|
||||
}} // namespace dawn_native::null
|
||||
|
||||
#endif // DAWNNATIVE_NULL_DEVICENULL_H_
|
||||
|
|
|
@ -31,9 +31,10 @@ namespace dawn_native { namespace opengl {
|
|||
return mBuffer;
|
||||
}
|
||||
|
||||
void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) {
|
||||
MaybeError Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, start, count, data);
|
||||
return {};
|
||||
}
|
||||
|
||||
void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) {
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace dawn_native { namespace opengl {
|
|||
GLuint GetHandle() const;
|
||||
|
||||
private:
|
||||
void SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override;
|
||||
MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override;
|
||||
void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
|
||||
void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
|
||||
void UnmapImpl() override;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "dawn_native/BackendConnection.h"
|
||||
#include "dawn_native/BindGroup.h"
|
||||
#include "dawn_native/BindGroupLayout.h"
|
||||
#include "dawn_native/DynamicUploader.h"
|
||||
#include "dawn_native/RenderPassDescriptor.h"
|
||||
#include "dawn_native/opengl/BufferGL.h"
|
||||
#include "dawn_native/opengl/CommandBufferGL.h"
|
||||
|
@ -115,6 +116,10 @@ namespace dawn_native { namespace opengl {
|
|||
return mLastSubmittedSerial;
|
||||
}
|
||||
|
||||
Serial Device::GetPendingCommandSerial() const {
|
||||
return mLastSubmittedSerial + 1;
|
||||
}
|
||||
|
||||
void Device::TickImpl() {
|
||||
CheckPassedFences();
|
||||
}
|
||||
|
@ -144,4 +149,16 @@ namespace dawn_native { namespace opengl {
|
|||
}
|
||||
}
|
||||
|
||||
ResultOrError<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
|
||||
|
|
|
@ -48,8 +48,16 @@ namespace dawn_native { namespace opengl {
|
|||
|
||||
Serial GetCompletedCommandSerial() const final override;
|
||||
Serial GetLastSubmittedCommandSerial() const final override;
|
||||
Serial GetPendingCommandSerial() const override;
|
||||
void TickImpl() override;
|
||||
|
||||
ResultOrError<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;
|
||||
|
|
|
@ -196,7 +196,7 @@ namespace dawn_native { namespace vulkan {
|
|||
mLastUsage = usage;
|
||||
}
|
||||
|
||||
void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) {
|
||||
MaybeError Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) {
|
||||
Device* device = ToBackend(GetDevice());
|
||||
|
||||
VkCommandBuffer commands = device->GetPendingCommandBuffer();
|
||||
|
@ -204,6 +204,7 @@ namespace dawn_native { namespace vulkan {
|
|||
|
||||
BufferUploader* uploader = device->GetBufferUploader();
|
||||
uploader->BufferSubData(mHandle, start, count, data);
|
||||
return {};
|
||||
}
|
||||
|
||||
void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t /*count*/) {
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace dawn_native { namespace vulkan {
|
|||
void TransitionUsageNow(VkCommandBuffer commands, dawn::BufferUsageBit usage);
|
||||
|
||||
private:
|
||||
void SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override;
|
||||
MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override;
|
||||
void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
|
||||
void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
|
||||
void UnmapImpl() override;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "common/Platform.h"
|
||||
#include "dawn_native/BackendConnection.h"
|
||||
#include "dawn_native/Commands.h"
|
||||
#include "dawn_native/DynamicUploader.h"
|
||||
#include "dawn_native/ErrorData.h"
|
||||
#include "dawn_native/vulkan/BindGroupLayoutVk.h"
|
||||
#include "dawn_native/vulkan/BindGroupVk.h"
|
||||
|
@ -682,4 +683,16 @@ namespace dawn_native { namespace vulkan {
|
|||
commands->commandBuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
ResultOrError<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
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace dawn_native { namespace vulkan {
|
|||
RenderPassCache* GetRenderPassCache() const;
|
||||
|
||||
VkCommandBuffer GetPendingCommandBuffer();
|
||||
Serial GetPendingCommandSerial() const;
|
||||
Serial GetPendingCommandSerial() const override;
|
||||
void SubmitPendingCommands();
|
||||
void AddWaitSemaphore(VkSemaphore semaphore);
|
||||
|
||||
|
@ -75,6 +75,13 @@ namespace dawn_native { namespace vulkan {
|
|||
|
||||
const dawn_native::PCIInfo& GetPCIInfo() const override;
|
||||
|
||||
ResultOrError<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;
|
||||
|
|
|
@ -222,10 +222,15 @@ TEST_P(BufferSetSubDataTests, SmallDataAtOffset) {
|
|||
TEST_P(BufferSetSubDataTests, ManySetSubData) {
|
||||
// TODO(cwallez@chromium.org): Use ringbuffers for SetSubData on explicit APIs.
|
||||
// otherwise this creates too many resources and can take freeze the driver(?)
|
||||
DAWN_SKIP_TEST_IF(IsD3D12() || IsMetal() || IsVulkan());
|
||||
DAWN_SKIP_TEST_IF(IsMetal() || IsVulkan());
|
||||
|
||||
// Note: Increasing the size of the buffer will likely cause timeout issues.
|
||||
// In D3D12, timeout detection occurs when the GPU scheduler tries but cannot preempt the task
|
||||
// executing these commands in-flight. If this takes longer than ~2s, a device reset occurs and
|
||||
// fails the test. Since GPUs may or may not complete by then, this test must be disabled OR
|
||||
// modified to be well-below the timeout limit.
|
||||
constexpr uint32_t kSize = 4000 * 1000;
|
||||
constexpr uint32_t kElements = 1000 * 1000;
|
||||
constexpr uint32_t kElements = 500 * 500;
|
||||
dawn::BufferDescriptor descriptor;
|
||||
descriptor.size = kSize;
|
||||
descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst;
|
||||
|
|
|
@ -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());
|
||||
}
|
|
@ -95,6 +95,7 @@ TEST(SerialQueue, IterateUpTo) {
|
|||
expectedValues.erase(expectedValues.begin());
|
||||
}
|
||||
ASSERT_TRUE(expectedValues.empty());
|
||||
EXPECT_EQ(queue.LastSerial(), 2u);
|
||||
}
|
||||
|
||||
// Test ClearUpTo
|
||||
|
@ -110,6 +111,7 @@ TEST(SerialQueue, ClearUpTo) {
|
|||
queue.Enqueue(vector3, 1);
|
||||
|
||||
queue.ClearUpTo(0);
|
||||
EXPECT_EQ(queue.LastSerial(), 1u);
|
||||
|
||||
std::vector<int> expectedValues = {9, 0};
|
||||
for (int value : queue.IterateAll()) {
|
||||
|
@ -141,3 +143,14 @@ TEST(SerialQueue, FirstSerial) {
|
|||
queue.Enqueue(vector1, 6);
|
||||
EXPECT_EQ(queue.FirstSerial(), 6u);
|
||||
}
|
||||
|
||||
// Test LastSerial
|
||||
TEST(SerialQueue, LastSerial) {
|
||||
TestSerialQueue queue;
|
||||
|
||||
queue.Enqueue({1}, 0);
|
||||
EXPECT_EQ(queue.LastSerial(), 0u);
|
||||
|
||||
queue.Enqueue({2}, 1);
|
||||
EXPECT_EQ(queue.LastSerial(), 1u);
|
||||
}
|
Loading…
Reference in New Issue