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:
Bryan Bernhart 2019-01-29 00:10:07 +00:00 committed by Commit Bot service account
parent 79ff8bead9
commit 74e95fff4a
38 changed files with 950 additions and 124 deletions

View File

@ -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",

View File

@ -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 {

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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_

View File

@ -44,6 +44,7 @@ namespace dawn_native {
class SamplerBase;
class ShaderModuleBase;
class ShaderModuleBuilder;
class StagingBufferBase;
class SwapChainBase;
class SwapChainBuilder;
class TextureBase;

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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*/) {

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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());
}

View File

@ -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);
}