Implement GPUCommandEncoder::ClearBuffer

Implements ClearBuffer for all backends.

Bug: dawn:1170
Change-Id: Ifc687d55727821c4fc134bf95020794c9d325025
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/68642
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Brandon Jones <bajones@chromium.org>
This commit is contained in:
Brandon Jones 2021-12-02 21:43:49 +00:00 committed by Dawn LUCI CQ
parent 2bf99905a7
commit d3105bfa47
22 changed files with 429 additions and 114 deletions

View File

@ -582,13 +582,11 @@
]
},
{
"name": "fill buffer",
"tags": ["upstream"],
"name": "clear buffer",
"args": [
{"name": "destination", "type": "buffer"},
{"name": "destination offset", "type": "uint64_t"},
{"name": "size", "type": "uint64_t"},
{"name": "value", "type": "uint8_t", "default": 0}
{"name": "buffer", "type": "buffer"},
{"name": "offset", "type": "uint64_t", "default": 0},
{"name": "size", "type": "uint64_t", "default": "WGPU_WHOLE_SIZE"}
]
},
{

View File

@ -540,6 +540,11 @@ namespace dawn_native {
CallMapCallback(mapID, status);
}
bool BufferBase::NeedsInitialization() const {
return !mIsDataInitialized &&
GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse);
}
bool BufferBase::IsDataInitialized() const {
return mIsDataInitialized;
}

View File

@ -64,6 +64,7 @@ namespace dawn_native {
MaybeError ValidateCanUseOnQueueNow() const;
bool IsFullBufferRange(uint64_t offset, uint64_t size) const;
bool NeedsInitialization() const;
bool IsDataInitialized() const;
void SetIsDataInitialized();

View File

@ -879,6 +879,57 @@ namespace dawn_native {
destination->texture, copySize);
}
void CommandEncoder::APIClearBuffer(BufferBase* buffer, uint64_t offset, uint64_t size) {
mEncodingContext.TryEncode(
this,
[&](CommandAllocator* allocator) -> MaybeError {
if (GetDevice()->IsValidationEnabled()) {
DAWN_TRY(GetDevice()->ValidateObject(buffer));
uint64_t bufferSize = buffer->GetSize();
DAWN_INVALID_IF(offset > bufferSize,
"Buffer offset (%u) is larger than the size (%u) of %s.",
offset, bufferSize, buffer);
uint64_t remainingSize = bufferSize - offset;
if (size == wgpu::kWholeSize) {
size = remainingSize;
} else {
DAWN_INVALID_IF(size > remainingSize,
"Buffer range (offset: %u, size: %u) doesn't fit in "
"the size (%u) of %s.",
offset, size, bufferSize, buffer);
}
DAWN_TRY_CONTEXT(ValidateCanUseAs(buffer, wgpu::BufferUsage::CopyDst),
"validating buffer %s usage.", buffer);
// Size must be a multiple of 4 bytes on macOS.
DAWN_INVALID_IF(size % 4 != 0, "Fill size (%u) is not a multiple of 4 bytes.",
size);
// Offset must be multiples of 4 bytes on macOS.
DAWN_INVALID_IF(offset % 4 != 0, "Offset (%u) is not a multiple of 4 bytes,",
offset);
mTopLevelBuffers.insert(buffer);
} else {
if (size == wgpu::kWholeSize) {
DAWN_ASSERT(buffer->GetSize() >= offset);
size = buffer->GetSize() - offset;
}
}
ClearBufferCmd* cmd = allocator->Allocate<ClearBufferCmd>(Command::ClearBuffer);
cmd->buffer = buffer;
cmd->offset = offset;
cmd->size = size;
return {};
},
"encoding %s.ClearBuffer(%s, %u, %u).", this, buffer, offset, size);
}
void CommandEncoder::APIInjectValidationError(const char* message) {
if (mEncodingContext.CheckCurrentEncoder(this)) {
mEncodingContext.HandleError(DAWN_VALIDATION_ERROR(message));

View File

@ -59,6 +59,7 @@ namespace dawn_native {
void APICopyTextureToTextureInternal(const ImageCopyTexture* source,
const ImageCopyTexture* destination,
const Extent3D* copySize);
void APIClearBuffer(BufferBase* destination, uint64_t destinationOffset, uint64_t size);
void APIInjectValidationError(const char* message);
void APIInsertDebugMarker(const char* groupLabel);

View File

@ -121,6 +121,11 @@ namespace dawn_native {
cmd->~ExecuteBundlesCmd();
break;
}
case Command::ClearBuffer: {
ClearBufferCmd* cmd = commands->NextCommand<ClearBufferCmd>();
cmd->~ClearBufferCmd();
break;
}
case Command::InsertDebugMarker: {
InsertDebugMarkerCmd* cmd = commands->NextCommand<InsertDebugMarkerCmd>();
commands->NextData<char>(cmd->length + 1);
@ -280,6 +285,10 @@ namespace dawn_native {
break;
}
case Command::ClearBuffer:
commands->NextCommand<ClearBufferCmd>();
break;
case Command::InsertDebugMarker: {
InsertDebugMarkerCmd* cmd = commands->NextCommand<InsertDebugMarkerCmd>();
commands->NextData<char>(cmd->length + 1);

View File

@ -36,6 +36,7 @@ namespace dawn_native {
BeginComputePass,
BeginOcclusionQuery,
BeginRenderPass,
ClearBuffer,
CopyBufferToBuffer,
CopyBufferToTexture,
CopyTextureToBuffer,
@ -196,6 +197,12 @@ namespace dawn_native {
uint32_t count;
};
struct ClearBufferCmd {
Ref<BufferBase> buffer;
uint64_t offset;
uint64_t size;
};
struct InsertDebugMarkerCmd {
uint32_t length;
};

View File

@ -400,37 +400,34 @@ namespace dawn_native { namespace d3d12 {
}
MaybeError Buffer::EnsureDataInitialized(CommandRecordingContext* commandContext) {
if (IsDataInitialized() ||
!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
if (!NeedsInitialization()) {
return {};
}
DAWN_TRY(InitializeToZero(commandContext));
return {};
}
MaybeError Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
ResultOrError<bool> Buffer::EnsureDataInitializedAsDestination(
CommandRecordingContext* commandContext,
uint64_t offset,
uint64_t size) {
if (IsDataInitialized() ||
!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
return {};
if (!NeedsInitialization()) {
return {false};
}
if (IsFullBufferRange(offset, size)) {
SetIsDataInitialized();
} else {
DAWN_TRY(InitializeToZero(commandContext));
return {false};
}
return {};
DAWN_TRY(InitializeToZero(commandContext));
return {true};
}
MaybeError Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
const CopyTextureToBufferCmd* copy) {
if (IsDataInitialized() ||
!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
if (!NeedsInitialization()) {
return {};
}
@ -449,8 +446,7 @@ namespace dawn_native { namespace d3d12 {
}
MaybeError Buffer::InitializeToZero(CommandRecordingContext* commandContext) {
ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse));
ASSERT(!IsDataInitialized());
ASSERT(NeedsInitialization());
// TODO(crbug.com/dawn/484): skip initializing the buffer when it is created on a heap
// that has already been zero initialized.

View File

@ -43,11 +43,13 @@ namespace dawn_native { namespace d3d12 {
bool CheckIsResidentForTesting() const;
MaybeError EnsureDataInitialized(CommandRecordingContext* commandContext);
MaybeError EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
ResultOrError<bool> EnsureDataInitializedAsDestination(
CommandRecordingContext* commandContext,
uint64_t offset,
uint64_t size);
MaybeError EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
const CopyTextureToBufferCmd* copy);
// Dawn API
void SetLabelImpl() override;

View File

@ -137,22 +137,6 @@ namespace dawn_native { namespace d3d12 {
}
}
MaybeError ClearBufferToZero(Device* device,
Buffer* destination,
uint64_t destinationOffset,
uint64_t size) {
DynamicUploader* uploader = device->GetDynamicUploader();
UploadHandle uploadHandle;
DAWN_TRY_ASSIGN(uploadHandle,
uploader->Allocate(size, device->GetPendingCommandSerial(),
kCopyBufferToBufferOffsetAlignment));
memset(uploadHandle.mappedBuffer, 0u, size);
return device->CopyFromStagingToBuffer(uploadHandle.stagingBuffer,
uploadHandle.startOffset, destination,
destinationOffset, size);
}
void RecordFirstIndexOffset(ID3D12GraphicsCommandList* commandList,
RenderPipeline* pipeline,
uint32_t firstVertex,
@ -731,8 +715,11 @@ namespace dawn_native { namespace d3d12 {
Buffer* dstBuffer = ToBackend(copy->destination.Get());
DAWN_TRY(srcBuffer->EnsureDataInitialized(commandContext));
DAWN_TRY(dstBuffer->EnsureDataInitializedAsDestination(
bool cleared;
DAWN_TRY_ASSIGN(cleared,
dstBuffer->EnsureDataInitializedAsDestination(
commandContext, copy->destinationOffset, copy->size));
DAWN_UNUSED(cleared);
srcBuffer->TrackUsageAndTransitionNow(commandContext,
wgpu::BufferUsage::CopySrc);
@ -933,6 +920,26 @@ namespace dawn_native { namespace d3d12 {
break;
}
case Command::ClearBuffer: {
ClearBufferCmd* cmd = mCommands.NextCommand<ClearBufferCmd>();
if (cmd->size == 0) {
// Skip no-op fills.
break;
}
Buffer* dstBuffer = ToBackend(cmd->buffer.Get());
bool clearedToZero;
DAWN_TRY_ASSIGN(clearedToZero, dstBuffer->EnsureDataInitializedAsDestination(
commandContext, cmd->offset, cmd->size));
if (!clearedToZero) {
DAWN_TRY(device->ClearBufferToZero(commandContext, cmd->buffer.Get(),
cmd->offset, cmd->size));
}
break;
}
case Command::ResolveQuerySet: {
ResolveQuerySetCmd* cmd = mCommands.NextCommand<ResolveQuerySetCmd>();
QuerySet* querySet = ToBackend(cmd->querySet.Get());
@ -941,8 +948,11 @@ namespace dawn_native { namespace d3d12 {
Buffer* destination = ToBackend(cmd->destination.Get());
uint64_t destinationOffset = cmd->destinationOffset;
DAWN_TRY(destination->EnsureDataInitializedAsDestination(
commandContext, destinationOffset, queryCount * sizeof(uint64_t)));
bool cleared;
DAWN_TRY_ASSIGN(cleared, destination->EnsureDataInitializedAsDestination(
commandContext, destinationOffset,
queryCount * sizeof(uint64_t)));
DAWN_UNUSED(cleared);
// Resolving unavailable queries is undefined behaviour on D3D12, we only can
// resolve the available part of sparse queries. In order to resolve the
@ -952,7 +962,8 @@ namespace dawn_native { namespace d3d12 {
auto endIt = querySet->GetQueryAvailability().begin() + firstQuery + queryCount;
bool hasUnavailableQueries = std::find(startIt, endIt, false) != endIt;
if (hasUnavailableQueries) {
DAWN_TRY(ClearBufferToZero(device, destination, destinationOffset,
DAWN_TRY(device->ClearBufferToZero(commandContext, destination,
destinationOffset,
queryCount * sizeof(uint64_t)));
}
@ -1030,8 +1041,10 @@ namespace dawn_native { namespace d3d12 {
ASSERT(uploadHandle.mappedBuffer != nullptr);
memcpy(uploadHandle.mappedBuffer, data, size);
DAWN_TRY(dstBuffer->EnsureDataInitializedAsDestination(commandContext, offset,
size));
bool cleared;
DAWN_TRY_ASSIGN(cleared, dstBuffer->EnsureDataInitializedAsDestination(
commandContext, offset, size));
DAWN_UNUSED(cleared);
dstBuffer->TrackUsageAndTransitionNow(commandContext,
wgpu::BufferUsage::CopyDst);
commandList->CopyBufferRegion(

View File

@ -465,8 +465,10 @@ namespace dawn_native { namespace d3d12 {
Buffer* dstBuffer = ToBackend(destination);
DAWN_TRY(dstBuffer->EnsureDataInitializedAsDestination(commandRecordingContext,
destinationOffset, size));
bool cleared;
DAWN_TRY_ASSIGN(cleared, dstBuffer->EnsureDataInitializedAsDestination(
commandRecordingContext, destinationOffset, size));
DAWN_UNUSED(cleared);
CopyFromStagingToBufferImpl(commandRecordingContext, source, sourceOffset, destination,
destinationOffset, size);

View File

@ -32,11 +32,11 @@ namespace dawn_native { namespace metal {
const BufferDescriptor* descriptor);
id<MTLBuffer> GetMTLBuffer() const;
void EnsureDataInitialized(CommandRecordingContext* commandContext);
void EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
bool EnsureDataInitialized(CommandRecordingContext* commandContext);
bool EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
uint64_t offset,
uint64_t size);
void EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
bool EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
const CopyTextureToBufferCmd* copy);
static uint64_t QueryMaxBufferLength(id<MTLDevice> mtlDevice);

View File

@ -176,47 +176,48 @@ namespace dawn_native { namespace metal {
mMtlBuffer = nullptr;
}
void Buffer::EnsureDataInitialized(CommandRecordingContext* commandContext) {
if (IsDataInitialized() ||
!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
return;
bool Buffer::EnsureDataInitialized(CommandRecordingContext* commandContext) {
if (!NeedsInitialization()) {
return false;
}
InitializeToZero(commandContext);
return true;
}
void Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
bool Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
uint64_t offset,
uint64_t size) {
if (IsDataInitialized() ||
!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
return;
if (!NeedsInitialization()) {
return false;
}
if (IsFullBufferRange(offset, size)) {
SetIsDataInitialized();
} else {
InitializeToZero(commandContext);
}
return false;
}
void Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
InitializeToZero(commandContext);
return true;
}
bool Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* commandContext,
const CopyTextureToBufferCmd* copy) {
if (IsDataInitialized() ||
!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
return;
if (!NeedsInitialization()) {
return false;
}
if (IsFullBufferOverwrittenInTextureToBufferCopy(copy)) {
SetIsDataInitialized();
} else {
InitializeToZero(commandContext);
return false;
}
InitializeToZero(commandContext);
return true;
}
void Buffer::InitializeToZero(CommandRecordingContext* commandContext) {
ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse));
ASSERT(!IsDataInitialized());
ASSERT(NeedsInitialization());
ClearBuffer(commandContext, uint8_t(0u));

View File

@ -914,6 +914,26 @@ namespace dawn_native { namespace metal {
break;
}
case Command::ClearBuffer: {
ClearBufferCmd* cmd = mCommands.NextCommand<ClearBufferCmd>();
if (cmd->size == 0) {
// Skip no-op copies.
break;
}
Buffer* dstBuffer = ToBackend(cmd->buffer.Get());
bool clearedToZero = dstBuffer->EnsureDataInitializedAsDestination(
commandContext, cmd->offset, cmd->size);
if (!clearedToZero) {
[commandContext->EnsureBlit() fillBuffer:dstBuffer->GetMTLBuffer()
range:NSMakeRange(cmd->offset, cmd->size)
value:0u];
}
break;
}
case Command::ResolveQuerySet: {
ResolveQuerySetCmd* cmd = mCommands.NextCommand<ResolveQuerySetCmd>();
QuerySet* querySet = ToBackend(cmd->querySet.Get());

View File

@ -67,44 +67,45 @@ namespace dawn_native { namespace opengl {
return mBuffer;
}
void Buffer::EnsureDataInitialized() {
if (IsDataInitialized() ||
!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
return;
bool Buffer::EnsureDataInitialized() {
if (!NeedsInitialization()) {
return false;
}
InitializeToZero();
return true;
}
void Buffer::EnsureDataInitializedAsDestination(uint64_t offset, uint64_t size) {
if (IsDataInitialized() ||
!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
return;
bool Buffer::EnsureDataInitializedAsDestination(uint64_t offset, uint64_t size) {
if (!NeedsInitialization()) {
return false;
}
if (IsFullBufferRange(offset, size)) {
SetIsDataInitialized();
} else {
InitializeToZero();
}
return false;
}
void Buffer::EnsureDataInitializedAsDestination(const CopyTextureToBufferCmd* copy) {
if (IsDataInitialized() ||
!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
return;
InitializeToZero();
return true;
}
bool Buffer::EnsureDataInitializedAsDestination(const CopyTextureToBufferCmd* copy) {
if (!NeedsInitialization()) {
return false;
}
if (IsFullBufferOverwrittenInTextureToBufferCopy(copy)) {
SetIsDataInitialized();
} else {
InitializeToZero();
return false;
}
InitializeToZero();
return true;
}
void Buffer::InitializeToZero() {
ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse));
ASSERT(!IsDataInitialized());
ASSERT(NeedsInitialization());
const uint64_t size = GetAllocatedSize();
Device* device = ToBackend(GetDevice());

View File

@ -33,9 +33,9 @@ namespace dawn_native { namespace opengl {
GLuint GetHandle() const;
void EnsureDataInitialized();
void EnsureDataInitializedAsDestination(uint64_t offset, uint64_t size);
void EnsureDataInitializedAsDestination(const CopyTextureToBufferCmd* copy);
bool EnsureDataInitialized();
bool EnsureDataInitializedAsDestination(uint64_t offset, uint64_t size);
bool EnsureDataInitializedAsDestination(const CopyTextureToBufferCmd* copy);
private:
Buffer(Device* device, const BufferDescriptor* descriptor, bool shouldLazyClear);

View File

@ -825,6 +825,27 @@ namespace dawn_native { namespace opengl {
break;
}
case Command::ClearBuffer: {
ClearBufferCmd* cmd = mCommands.NextCommand<ClearBufferCmd>();
if (cmd->size == 0) {
// Skip no-op fills.
break;
}
Buffer* dstBuffer = ToBackend(cmd->buffer.Get());
bool clearedToZero =
dstBuffer->EnsureDataInitializedAsDestination(cmd->offset, cmd->size);
if (!clearedToZero) {
const std::vector<uint8_t> clearValues(cmd->size, 0u);
gl.BindBuffer(GL_ARRAY_BUFFER, dstBuffer->GetHandle());
gl.BufferSubData(GL_ARRAY_BUFFER, cmd->offset, cmd->size,
clearValues.data());
}
break;
}
case Command::ResolveQuerySet: {
// TODO(crbug.com/dawn/434): Resolve non-precise occlusion query.
SkipCommand(&mCommands, type);

View File

@ -340,42 +340,44 @@ namespace dawn_native { namespace vulkan {
}
}
void Buffer::EnsureDataInitialized(CommandRecordingContext* recordingContext) {
if (IsDataInitialized() ||
!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
return;
bool Buffer::EnsureDataInitialized(CommandRecordingContext* recordingContext) {
if (!NeedsInitialization()) {
return false;
}
InitializeToZero(recordingContext);
return true;
}
void Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext,
bool Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext,
uint64_t offset,
uint64_t size) {
if (IsDataInitialized() ||
!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
return;
if (!NeedsInitialization()) {
return false;
}
if (IsFullBufferRange(offset, size)) {
SetIsDataInitialized();
} else {
InitializeToZero(recordingContext);
}
return false;
}
void Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext,
InitializeToZero(recordingContext);
return true;
}
bool Buffer::EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext,
const CopyTextureToBufferCmd* copy) {
if (IsDataInitialized() ||
!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
return;
if (!NeedsInitialization()) {
return false;
}
if (IsFullBufferOverwrittenInTextureToBufferCopy(copy)) {
SetIsDataInitialized();
} else {
InitializeToZero(recordingContext);
return false;
}
InitializeToZero(recordingContext);
return true;
}
void Buffer::SetLabelImpl() {
@ -384,8 +386,7 @@ namespace dawn_native { namespace vulkan {
}
void Buffer::InitializeToZero(CommandRecordingContext* recordingContext) {
ASSERT(GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse));
ASSERT(!IsDataInitialized());
ASSERT(NeedsInitialization());
ClearBuffer(recordingContext, 0u);
GetDevice()->IncrementLazyClearCountForTesting();

View File

@ -42,11 +42,12 @@ namespace dawn_native { namespace vulkan {
VkPipelineStageFlags* srcStages,
VkPipelineStageFlags* dstStages);
void EnsureDataInitialized(CommandRecordingContext* recordingContext);
void EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext,
// All the Ensure methods return true if the buffer was initialized to zero.
bool EnsureDataInitialized(CommandRecordingContext* recordingContext);
bool EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext,
uint64_t offset,
uint64_t size);
void EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext,
bool EnsureDataInitializedAsDestination(CommandRecordingContext* recordingContext,
const CopyTextureToBufferCmd* copy);
// Dawn API

View File

@ -707,6 +707,27 @@ namespace dawn_native { namespace vulkan {
break;
}
case Command::ClearBuffer: {
ClearBufferCmd* cmd = mCommands.NextCommand<ClearBufferCmd>();
if (cmd->size == 0) {
// Skip no-op fills.
break;
}
Buffer* dstBuffer = ToBackend(cmd->buffer.Get());
bool clearedToZero = dstBuffer->EnsureDataInitializedAsDestination(
recordingContext, cmd->offset, cmd->size);
if (!clearedToZero) {
dstBuffer->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopyDst);
device->fn.CmdFillBuffer(recordingContext->commandBuffer,
dstBuffer->GetHandle(), cmd->offset, cmd->size,
0u);
}
break;
}
case Command::BeginRenderPass: {
BeginRenderPassCmd* cmd = mCommands.NextCommand<BeginRenderPassCmd>();

View File

@ -547,6 +547,40 @@ class CopyTests_B2B : public DawnTest {
}
};
class ClearBufferTests : public DawnTest {
protected:
// This is the same signature as ClearBuffer except that the buffers are replaced by
// only their size.
void DoTest(uint64_t bufferSize, uint64_t clearOffset, uint64_t clearSize) {
ASSERT(bufferSize % 4 == 0);
ASSERT(clearSize % 4 == 0);
// Create our test buffer, filled with non-zeroes
std::vector<uint32_t> bufferData(static_cast<size_t>(bufferSize / sizeof(uint32_t)));
for (size_t i = 0; i < bufferData.size(); i++) {
bufferData[i] = i + 1;
}
wgpu::Buffer buffer = utils::CreateBufferFromData(
device, bufferData.data(), bufferData.size() * sizeof(uint32_t),
wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc);
std::vector<uint8_t> fillData(static_cast<size_t>(clearSize), 0u);
// Submit the fill
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.ClearBuffer(buffer, clearOffset, clearSize);
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
// Check destination is exactly the expected content.
EXPECT_BUFFER_U32_RANGE_EQ(bufferData.data(), buffer, 0, clearOffset / sizeof(uint32_t));
EXPECT_BUFFER_U8_RANGE_EQ(fillData.data(), buffer, clearOffset, clearSize);
uint64_t clearEnd = clearOffset + clearSize;
EXPECT_BUFFER_U32_RANGE_EQ(bufferData.data() + clearEnd / sizeof(uint32_t), buffer,
clearEnd, (bufferSize - clearEnd) / sizeof(uint32_t));
}
};
// Test that copying an entire texture with 256-byte aligned dimensions works
TEST_P(CopyTests_T2B, FullTextureAligned) {
constexpr uint32_t kWidth = 256;
@ -2407,3 +2441,31 @@ DAWN_INSTANTIATE_TEST(CopyTests_B2B,
OpenGLBackend(),
OpenGLESBackend(),
VulkanBackend());
// Test clearing full buffers
TEST_P(ClearBufferTests, FullClear) {
DoTest(kSmallBufferSize, 0, kSmallBufferSize);
DoTest(kLargeBufferSize, 0, kLargeBufferSize);
}
// Test clearing small pieces of a buffer at different corner case offsets
TEST_P(ClearBufferTests, SmallClearInBigBuffer) {
constexpr uint64_t kEndOffset = kLargeBufferSize - kSmallBufferSize;
DoTest(kLargeBufferSize, 0, kSmallBufferSize);
DoTest(kLargeBufferSize, kSmallBufferSize, kSmallBufferSize);
DoTest(kLargeBufferSize, kEndOffset, kSmallBufferSize);
}
// Test zero-size clears
TEST_P(ClearBufferTests, ZeroSizedClear) {
DoTest(kLargeBufferSize, 0, 0);
DoTest(kLargeBufferSize, kSmallBufferSize, 0);
DoTest(kLargeBufferSize, kLargeBufferSize, 0);
}
DAWN_INSTANTIATE_TEST(ClearBufferTests,
D3D12Backend(),
MetalBackend(),
OpenGLBackend(),
OpenGLESBackend(),
VulkanBackend());

View File

@ -2345,3 +2345,105 @@ TEST_F(CopyCommandTest_CompressedTextureFormats, CopyToMultipleArrayLayers) {
{testWidth - blockWidth, testHeight - blockHeight, 16});
}
}
class CopyCommandTest_ClearBuffer : public CopyCommandTest {};
TEST_F(CopyCommandTest_ClearBuffer, Success) {
wgpu::Buffer destination = CreateBuffer(16, wgpu::BufferUsage::CopyDst);
// Clear different ranges, including some that touch the OOB condition
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.ClearBuffer(destination, 0, 16);
encoder.ClearBuffer(destination, 0, 8);
encoder.ClearBuffer(destination, 8, 8);
encoder.Finish();
}
// Size is allowed to be omitted
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.ClearBuffer(destination, 0);
encoder.ClearBuffer(destination, 8);
encoder.Finish();
}
// Size and Offset are allowed to be omitted
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.ClearBuffer(destination);
encoder.Finish();
}
}
// Test a successful ClearBuffer where the last external reference is dropped.
TEST_F(CopyCommandTest_ClearBuffer, DroppedBuffer) {
wgpu::Buffer destination = CreateBuffer(16, wgpu::BufferUsage::CopyDst);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.ClearBuffer(destination, 0, 8);
wgpu::CommandBuffer commandBuffer = encoder.Finish();
destination = nullptr;
device.GetQueue().Submit(1, &commandBuffer);
}
// Test ClearBuffer copies with OOB
TEST_F(CopyCommandTest_ClearBuffer, OutOfBounds) {
wgpu::Buffer destination = CreateBuffer(16, wgpu::BufferUsage::CopyDst);
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.ClearBuffer(destination, 8, 12);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
{
// Despite being zero length, should still raise an error due to being out of bounds.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.ClearBuffer(destination, 20, 0);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
// Test ClearBuffer with incorrect buffer usage
TEST_F(CopyCommandTest_ClearBuffer, BadUsage) {
wgpu::Buffer vertex = CreateBuffer(16, wgpu::BufferUsage::Vertex);
// Destination with incorrect usage
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.ClearBuffer(vertex, 0, 16);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Test ClearBuffer with unaligned data size
TEST_F(CopyCommandTest_ClearBuffer, UnalignedSize) {
wgpu::Buffer destination = CreateBuffer(16, wgpu::BufferUsage::CopyDst);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.ClearBuffer(destination, 0, 2);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Test ClearBuffer with unaligned offset
TEST_F(CopyCommandTest_ClearBuffer, UnalignedOffset) {
wgpu::Buffer destination = CreateBuffer(16, wgpu::BufferUsage::CopyDst);
// Unaligned destination offset
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.ClearBuffer(destination, 2, 4);
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Test ClearBuffer with buffers in error state cause errors.
TEST_F(CopyCommandTest_ClearBuffer, BuffersInErrorState) {
wgpu::BufferDescriptor errorBufferDescriptor;
errorBufferDescriptor.size = 4;
errorBufferDescriptor.usage =
wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
ASSERT_DEVICE_ERROR(wgpu::Buffer errorBuffer = device.CreateBuffer(&errorBufferDescriptor));
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.ClearBuffer(errorBuffer, 0, 4);
ASSERT_DEVICE_ERROR(encoder.Finish());
}