D3D12: Enable nonzero_clear_resources_on_creation_for_testing on buffer

This patch enables nonzero_clear_resources_on_creation_for_testing
toggle on buffer on D3D12 backends as a preparation of supporting
buffer lazy-initialization in Dawn.

BUG=dawn:414
TEST=dawn_end2end_tests

Change-Id: Id4f45ff5ccf906692c3855451b120aa56f68c7a9
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/23142
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp
index 8b42c95..879709f 100644
--- a/src/dawn_native/d3d12/BufferD3D12.cpp
+++ b/src/dawn_native/d3d12/BufferD3D12.cpp
@@ -17,6 +17,7 @@
 #include "common/Assert.h"
 #include "common/Constants.h"
 #include "common/Math.h"
+#include "dawn_native/DynamicUploader.h"
 #include "dawn_native/d3d12/CommandRecordingContext.h"
 #include "dawn_native/d3d12/D3D12Error.h"
 #include "dawn_native/d3d12/DeviceD3D12.h"
@@ -120,6 +121,11 @@
         DAWN_TRY_ASSIGN(
             mResourceAllocation,
             ToBackend(GetDevice())->AllocateMemory(heapType, resourceDescriptor, bufferUsage));
+
+        if (GetDevice()->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) {
+            DAWN_TRY(ClearBuffer(ClearValue::NonZero));
+        }
+
         return {};
     }
 
@@ -238,57 +244,60 @@
         return (GetUsage() & (wgpu::BufferUsage::MapRead | wgpu::BufferUsage::MapWrite)) != 0;
     }
 
-    MaybeError Buffer::MapAtCreationImpl(uint8_t** mappedPointer) {
+    MaybeError Buffer::MapBufferInternal(D3D12_RANGE mappedRange,
+                                         void** mappedPointer,
+                                         const char* contextInfo) {
         // The mapped buffer can be accessed at any time, so it must be locked to ensure it is never
         // evicted. This buffer should already have been made resident when it was created.
         Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
         DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockAllocation(heap));
 
+        DAWN_TRY(
+            CheckHRESULT(GetD3D12Resource()->Map(0, &mappedRange, mappedPointer), contextInfo));
+        return {};
+    }
+
+    void Buffer::UnmapBufferInternal(D3D12_RANGE mappedRange) {
+        GetD3D12Resource()->Unmap(0, &mappedRange);
+
+        // When buffers are mapped, they are locked to keep them in resident memory. We must unlock
+        // them when they are unmapped.
+        Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
+        ToBackend(GetDevice())->GetResidencyManager()->UnlockAllocation(heap);
+    }
+
+    MaybeError Buffer::MapAtCreationImpl(uint8_t** mappedPointer) {
         mWrittenMappedRange = {0, static_cast<size_t>(GetSize())};
-        DAWN_TRY(CheckHRESULT(GetD3D12Resource()->Map(0, &mWrittenMappedRange,
-                                                      reinterpret_cast<void**>(mappedPointer)),
-                              "D3D12 map at creation"));
+        DAWN_TRY(MapBufferInternal(mWrittenMappedRange, reinterpret_cast<void**>(mappedPointer),
+                                   "D3D12 map at creation"));
         mMappedData = reinterpret_cast<char*>(mappedPointer);
         return {};
     }
 
     MaybeError Buffer::MapReadAsyncImpl(uint32_t serial) {
-        // The mapped buffer can be accessed at any time, so we must make the buffer resident and
-        // lock it to ensure it is never evicted.
-        Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
-        DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockAllocation(heap));
-
         mWrittenMappedRange = {};
         D3D12_RANGE readRange = {0, static_cast<size_t>(GetSize())};
-        DAWN_TRY(CheckHRESULT(
-            GetD3D12Resource()->Map(0, &readRange, reinterpret_cast<void**>(&mMappedData)),
-            "D3D12 map read async"));
+        DAWN_TRY(MapBufferInternal(readRange, reinterpret_cast<void**>(&mMappedData),
+                                   "D3D12 map read async"));
+
         // There is no need to transition the resource to a new state: D3D12 seems to make the GPU
         // writes available when the fence is passed.
         return {};
     }
 
     MaybeError Buffer::MapWriteAsyncImpl(uint32_t serial) {
-        // The mapped buffer can be accessed at any time, so we must make the buffer resident and
-        // lock it to ensure it is never evicted.
-        Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
-        DAWN_TRY(ToBackend(GetDevice())->GetResidencyManager()->LockAllocation(heap));
-
         mWrittenMappedRange = {0, static_cast<size_t>(GetSize())};
-        DAWN_TRY(CheckHRESULT(GetD3D12Resource()->Map(0, &mWrittenMappedRange,
-                                                      reinterpret_cast<void**>(&mMappedData)),
-                              "D3D12 map write async"));
+        DAWN_TRY(MapBufferInternal(mWrittenMappedRange, reinterpret_cast<void**>(&mMappedData),
+                                   "D3D12 map write async"));
+
         // There is no need to transition the resource to a new state: D3D12 seems to make the CPU
         // writes available on queue submission.
         return {};
     }
 
     void Buffer::UnmapImpl() {
-        GetD3D12Resource()->Unmap(0, &mWrittenMappedRange);
-        // When buffers are mapped, they are locked to keep them in resident memory. We must unlock
-        // them when they are unmapped.
-        Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
-        ToBackend(GetDevice())->GetResidencyManager()->UnlockAllocation(heap);
+        UnmapBufferInternal(mWrittenMappedRange);
+
         mWrittenMappedRange = {};
         mMappedData = nullptr;
     }
@@ -317,4 +326,39 @@
         return mResourceAllocation.GetInfo().mMethod == allocationMethod;
     }
 
+    MaybeError Buffer::ClearBuffer(ClearValue clearValue) {
+        // TODO(jiawei.shao@intel.com): support buffer lazy-initialization to 0.
+        ASSERT(clearValue == BufferBase::ClearValue::NonZero);
+        constexpr uint8_t kClearBufferValue = 1u;
+
+        Device* device = ToBackend(GetDevice());
+
+        // The state of the buffers on UPLOAD heap must always be GENERIC_READ and cannot be
+        // changed away, so we can only clear such buffer with buffer mapping.
+        if (D3D12HeapType(GetUsage()) == D3D12_HEAP_TYPE_UPLOAD) {
+            uint8_t* mappedData = nullptr;
+            D3D12_RANGE writeRange = {0, static_cast<size_t>(GetSize())};
+            DAWN_TRY(MapBufferInternal(writeRange, reinterpret_cast<void**>(&mappedData),
+                                       "D3D12 map at clear buffer"));
+
+            memset(mappedData, kClearBufferValue, GetSize());
+
+            UnmapBufferInternal(writeRange);
+            mappedData = nullptr;
+        } else {
+            // TODO(jiawei.shao@intel.com): use ClearUnorderedAccessView*() when the buffer usage
+            // includes STORAGE.
+            DynamicUploader* uploader = device->GetDynamicUploader();
+            UploadHandle uploadHandle;
+            DAWN_TRY_ASSIGN(uploadHandle,
+                            uploader->Allocate(GetSize(), device->GetPendingCommandSerial()));
+
+            memset(uploadHandle.mappedBuffer, kClearBufferValue, GetSize());
+
+            DAWN_TRY(device->CopyFromStagingToBuffer(uploadHandle.stagingBuffer,
+                                                     uploadHandle.startOffset, this, 0, GetSize()));
+        }
+
+        return {};
+    }
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/BufferD3D12.h b/src/dawn_native/d3d12/BufferD3D12.h
index 5410738..3c51b74 100644
--- a/src/dawn_native/d3d12/BufferD3D12.h
+++ b/src/dawn_native/d3d12/BufferD3D12.h
@@ -55,11 +55,17 @@
         bool IsMapWritable() const override;
         virtual MaybeError MapAtCreationImpl(uint8_t** mappedPointer) override;
         void* GetMappedPointerImpl() override;
+        MaybeError MapBufferInternal(D3D12_RANGE mappedRange,
+                                     void** mappedPointer,
+                                     const char* contextInfo);
+        void UnmapBufferInternal(D3D12_RANGE mappedRange);
 
         bool TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
                                                   D3D12_RESOURCE_BARRIER* barrier,
                                                   wgpu::BufferUsage newUsage);
 
+        MaybeError ClearBuffer(ClearValue clearValue);
+
         ResourceHeapAllocation mResourceAllocation;
         bool mFixedResourceState = false;
         wgpu::BufferUsage mLastUsage = wgpu::BufferUsage::None;
diff --git a/src/tests/end2end/NonzeroBufferCreationTests.cpp b/src/tests/end2end/NonzeroBufferCreationTests.cpp
index aeb62f9..e3fefa7 100644
--- a/src/tests/end2end/NonzeroBufferCreationTests.cpp
+++ b/src/tests/end2end/NonzeroBufferCreationTests.cpp
@@ -35,8 +35,8 @@
 }
 
 // Verify that each byte of the buffer has all been initialized to 1 with the toggle enabled when it
-// is created without CopyDst usage.
-TEST_P(NonzeroBufferCreationTests, BufferCreationWithoutCopyDstUsage) {
+// is created with MapWrite without CopyDst usage.
+TEST_P(NonzeroBufferCreationTests, BufferCreationWithMapWriteWithoutCopyDstUsage) {
     constexpr uint32_t kSize = 32u;
 
     wgpu::BufferDescriptor descriptor;
@@ -51,5 +51,6 @@
 }
 
 DAWN_INSTANTIATE_TEST(NonzeroBufferCreationTests,
+                      D3D12Backend({"nonzero_clear_resources_on_creation_for_testing"}),
                       MetalBackend({"nonzero_clear_resources_on_creation_for_testing"}),
                       VulkanBackend({"nonzero_clear_resources_on_creation_for_testing"}));