Implement Queue::WriteTexture in D3D12

Bug: dawn:483
Change-Id: I9e5f54abc6675acbb11a021a3d38aea7195017c5
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/26320
Commit-Queue: Natasha Lee <natlee@microsoft.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp
index 3afc185..403e022 100644
--- a/src/dawn_native/Queue.cpp
+++ b/src/dawn_native/Queue.cpp
@@ -158,14 +158,13 @@
             return {};
         }
 
-        return WriteTextureImpl(destination, data, dataSize, dataLayout, writeSize);
+        return WriteTextureImpl(*destination, data, *dataLayout, *writeSize);
     }
 
-    MaybeError QueueBase::WriteTextureImpl(const TextureCopyView* destination,
+    MaybeError QueueBase::WriteTextureImpl(const TextureCopyView& destination,
                                            const void* data,
-                                           size_t dataSize,
-                                           const TextureDataLayout* dataLayout,
-                                           const Extent3D* writeSize) {
+                                           const TextureDataLayout& dataLayout,
+                                           const Extent3D& writeSize) {
         // TODO(tommek@google.com): This should be implemented.
         return {};
     }
diff --git a/src/dawn_native/Queue.h b/src/dawn_native/Queue.h
index aad5175..441fa1b 100644
--- a/src/dawn_native/Queue.h
+++ b/src/dawn_native/Queue.h
@@ -58,11 +58,10 @@
                                            uint64_t bufferOffset,
                                            const void* data,
                                            size_t size);
-        virtual MaybeError WriteTextureImpl(const TextureCopyView* destination,
+        virtual MaybeError WriteTextureImpl(const TextureCopyView& destination,
                                             const void* data,
-                                            size_t dataSize,
-                                            const TextureDataLayout* dataLayout,
-                                            const Extent3D* writeSize);
+                                            const TextureDataLayout& dataLayout,
+                                            const Extent3D& writeSize);
 
         MaybeError ValidateSubmit(uint32_t commandCount, CommandBufferBase* const* commands) const;
         MaybeError ValidateSignal(const Fence* fence, uint64_t signalValue) const;
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index c58f150..8fd4a5a 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -95,39 +95,6 @@
                    copySize.depth == srcSize.depth;
         }
 
-        void RecordCopyBufferToTextureFromTextureCopySplit(ID3D12GraphicsCommandList* commandList,
-                                                           const Texture2DCopySplit& baseCopySplit,
-                                                           Buffer* buffer,
-                                                           uint64_t baseOffset,
-                                                           uint64_t bufferBytesPerRow,
-                                                           Texture* texture,
-                                                           uint32_t textureMiplevel,
-                                                           uint32_t textureSlice,
-                                                           Aspect aspect) {
-            const D3D12_TEXTURE_COPY_LOCATION textureLocation =
-                ComputeTextureCopyLocationForTexture(texture, textureMiplevel, textureSlice,
-                                                     aspect);
-
-            const uint64_t offset = baseCopySplit.offset + baseOffset;
-
-            for (uint32_t i = 0; i < baseCopySplit.count; ++i) {
-                const Texture2DCopySplit::CopyInfo& info = baseCopySplit.copies[i];
-
-                // TODO(jiawei.shao@intel.com): pre-compute bufferLocation and sourceRegion as
-                // members in Texture2DCopySplit::CopyInfo.
-                const D3D12_TEXTURE_COPY_LOCATION bufferLocation =
-                    ComputeBufferLocationForCopyTextureRegion(texture, buffer->GetD3D12Resource(),
-                                                              info.bufferSize, offset,
-                                                              bufferBytesPerRow);
-                const D3D12_BOX sourceRegion =
-                    ComputeD3D12BoxFromOffsetAndSize(info.bufferOffset, info.copySize);
-
-                commandList->CopyTextureRegion(&textureLocation, info.textureOffset.x,
-                                               info.textureOffset.y, info.textureOffset.z,
-                                               &bufferLocation, &sourceRegion);
-            }
-        }
-
         void RecordCopyTextureToBufferFromTextureCopySplit(ID3D12GraphicsCommandList* commandList,
                                                            const Texture2DCopySplit& baseCopySplit,
                                                            Buffer* buffer,
@@ -713,41 +680,11 @@
                     texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopyDst,
                                                         subresources);
 
-                    // See comments in ComputeTextureCopySplits() for more details.
-                    const TextureCopySplits copySplits = ComputeTextureCopySplits(
-                        copy->destination.origin, copy->copySize, texture->GetFormat(),
-                        copy->source.offset, copy->source.bytesPerRow, copy->source.rowsPerImage);
-
-                    const uint64_t bytesPerSlice =
-                        copy->source.bytesPerRow *
-                        (copy->source.rowsPerImage / texture->GetFormat().blockHeight);
-
-                    // copySplits.copies2D[1] is always calculated for the second copy slice with
-                    // extra "bytesPerSlice" copy offset compared with the first copy slice. So
-                    // here we use an array bufferOffsetsForNextSlice to record the extra offsets
-                    // for each copy slice: bufferOffsetsForNextSlice[0] is the extra offset for
-                    // the next copy slice that uses copySplits.copies2D[0], and
-                    // bufferOffsetsForNextSlice[1] is the extra offset for the next copy slice
-                    // that uses copySplits.copies2D[1].
-                    std::array<uint64_t, TextureCopySplits::kMaxTextureCopySplits>
-                        bufferOffsetsForNextSlice = {{0u, 0u}};
-                    for (uint32_t copySlice = 0; copySlice < copy->copySize.depth; ++copySlice) {
-                        const uint32_t splitIndex = copySlice % copySplits.copies2D.size();
-
-                        const Texture2DCopySplit& copySplitPerLayerBase =
-                            copySplits.copies2D[splitIndex];
-                        const uint64_t bufferOffsetForNextSlice =
-                            bufferOffsetsForNextSlice[splitIndex];
-                        const uint32_t copyTextureLayer = copySlice + copy->destination.origin.z;
-
-                        RecordCopyBufferToTextureFromTextureCopySplit(
-                            commandList, copySplitPerLayerBase, buffer, bufferOffsetForNextSlice,
-                            copy->source.bytesPerRow, texture, copy->destination.mipLevel,
-                            copyTextureLayer, subresources.aspects);
-
-                        bufferOffsetsForNextSlice[splitIndex] +=
-                            bytesPerSlice * copySplits.copies2D.size();
-                    }
+                    // compute the copySplits and record the CopyTextureRegion commands
+                    CopyBufferToTextureWithCopySplit(
+                        commandContext, copy->destination, copy->copySize, texture,
+                        buffer->GetD3D12Resource(), copy->source.offset, copy->source.bytesPerRow,
+                        copy->source.rowsPerImage, subresources.aspects);
 
                     break;
                 }
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 7b73ccf..2e2e60e 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -42,6 +42,7 @@
 #include "dawn_native/d3d12/StagingDescriptorAllocatorD3D12.h"
 #include "dawn_native/d3d12/SwapChainD3D12.h"
 #include "dawn_native/d3d12/TextureD3D12.h"
+#include "dawn_native/d3d12/UtilsD3D12.h"
 
 #include <sstream>
 
@@ -364,6 +365,33 @@
             sourceOffset, size);
     }
 
+    MaybeError Device::CopyFromStagingToTexture(const StagingBufferBase* source,
+                                                const TextureDataLayout& src,
+                                                TextureCopy* dst,
+                                                const Extent3D& copySizePixels) {
+        CommandRecordingContext* commandContext;
+        DAWN_TRY_ASSIGN(commandContext, GetPendingCommandContext());
+        Texture* texture = ToBackend(dst->texture.Get());
+        ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
+
+        SubresourceRange range = GetSubresourcesAffectedByCopy(*dst, copySizePixels);
+
+        if (IsCompleteSubresourceCopiedTo(texture, copySizePixels, dst->mipLevel)) {
+            texture->SetIsSubresourceContentInitialized(true, range);
+        } else {
+            texture->EnsureSubresourceContentInitialized(commandContext, range);
+        }
+
+        texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopyDst, range);
+
+        // compute the copySplits and record the CopyTextureRegion commands
+        CopyBufferToTextureWithCopySplit(commandContext, *dst, copySizePixels, texture,
+                                         ToBackend(source)->GetResource(), src.offset,
+                                         src.bytesPerRow, src.rowsPerImage, range.aspects);
+
+        return {};
+    }
+
     void Device::DeallocateMemory(ResourceHeapAllocation& allocation) {
         mResourceAllocatorManager->DeallocateMemory(allocation);
     }
diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h
index d4cb081..3ab0393 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -20,6 +20,7 @@
 #include "common/Constants.h"
 #include "common/SerialQueue.h"
 #include "dawn_native/BindingInfo.h"
+#include "dawn_native/Commands.h"
 #include "dawn_native/Device.h"
 #include "dawn_native/d3d12/CommandRecordingContext.h"
 #include "dawn_native/d3d12/D3D12Info.h"
@@ -99,6 +100,11 @@
                                          uint64_t destinationOffset,
                                          uint64_t size);
 
+        MaybeError CopyFromStagingToTexture(const StagingBufferBase* source,
+                                            const TextureDataLayout& src,
+                                            TextureCopy* dst,
+                                            const Extent3D& copySizePixels);
+
         ResultOrError<ResourceHeapAllocation> AllocateMemory(
             D3D12_HEAP_TYPE heapType,
             const D3D12_RESOURCE_DESC& resourceDescriptor,
diff --git a/src/dawn_native/d3d12/QueueD3D12.cpp b/src/dawn_native/d3d12/QueueD3D12.cpp
index 710e41f..c880892 100644
--- a/src/dawn_native/d3d12/QueueD3D12.cpp
+++ b/src/dawn_native/d3d12/QueueD3D12.cpp
@@ -14,6 +14,11 @@
 
 #include "dawn_native/d3d12/QueueD3D12.h"
 
+#include "common/Math.h"
+#include "dawn_native/Buffer.h"
+#include "dawn_native/CommandValidation.h"
+#include "dawn_native/Commands.h"
+#include "dawn_native/DynamicUploader.h"
 #include "dawn_native/d3d12/CommandBufferD3D12.h"
 #include "dawn_native/d3d12/D3D12Error.h"
 #include "dawn_native/d3d12/DeviceD3D12.h"
@@ -22,6 +27,46 @@
 
 namespace dawn_native { namespace d3d12 {
 
+    namespace {
+        ResultOrError<UploadHandle> UploadTextureDataAligningBytesPerRow(
+            DeviceBase* device,
+            const void* data,
+            uint32_t alignedBytesPerRow,
+            uint32_t optimallyAlignedBytesPerRow,
+            uint32_t alignedRowsPerImage,
+            const TextureDataLayout& dataLayout,
+            const Format& textureFormat,
+            const Extent3D& writeSizePixel) {
+            uint32_t newDataSizeBytes = ComputeRequiredBytesInCopy(
+                textureFormat, writeSizePixel, optimallyAlignedBytesPerRow, alignedRowsPerImage);
+
+            UploadHandle uploadHandle;
+            DAWN_TRY_ASSIGN(uploadHandle, device->GetDynamicUploader()->Allocate(
+                                              newDataSizeBytes, device->GetPendingCommandSerial()));
+            ASSERT(uploadHandle.mappedBuffer != nullptr);
+
+            uint8_t* dstPointer = static_cast<uint8_t*>(uploadHandle.mappedBuffer);
+            const uint8_t* srcPointer = static_cast<const uint8_t*>(data);
+            srcPointer += dataLayout.offset;
+
+            uint32_t alignedRowsPerImageInBlock = alignedRowsPerImage / textureFormat.blockHeight;
+            uint32_t dataRowsPerImageInBlock = dataLayout.rowsPerImage / textureFormat.blockHeight;
+            if (dataRowsPerImageInBlock == 0) {
+                dataRowsPerImageInBlock = writeSizePixel.height / textureFormat.blockHeight;
+            }
+
+            ASSERT(dataRowsPerImageInBlock >= alignedRowsPerImageInBlock);
+            uint64_t imageAdditionalStride =
+                dataLayout.bytesPerRow * (dataRowsPerImageInBlock - alignedRowsPerImageInBlock);
+
+            CopyTextureData(dstPointer, srcPointer, writeSizePixel.depth,
+                            alignedRowsPerImageInBlock, imageAdditionalStride, alignedBytesPerRow,
+                            optimallyAlignedBytesPerRow, dataLayout.bytesPerRow);
+
+            return uploadHandle;
+        }
+    }  // namespace
+
     Queue::Queue(Device* device) : QueueBase(device) {
     }
 
@@ -47,4 +92,43 @@
         return {};
     }
 
+    MaybeError Queue::WriteTextureImpl(const TextureCopyView& destination,
+                                       const void* data,
+                                       const TextureDataLayout& dataLayout,
+                                       const Extent3D& writeSizePixel) {
+        const TexelBlockInfo& blockInfo =
+            destination.texture->GetFormat().GetTexelBlockInfo(destination.aspect);
+
+        // We are only copying the part of the data that will appear in the texture.
+        // Note that validating texture copy range ensures that writeSizePixel->width and
+        // writeSizePixel->height are multiples of blockWidth and blockHeight respectively.
+        uint32_t alignedBytesPerRow =
+            (writeSizePixel.width) / blockInfo.blockWidth * blockInfo.blockByteSize;
+        uint32_t alignedRowsPerImage = writeSizePixel.height;
+        uint32_t optimallyAlignedBytesPerRow =
+            Align(alignedBytesPerRow, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
+
+        UploadHandle uploadHandle;
+        DAWN_TRY_ASSIGN(
+            uploadHandle,
+            UploadTextureDataAligningBytesPerRow(
+                GetDevice(), data, alignedBytesPerRow, optimallyAlignedBytesPerRow,
+                alignedRowsPerImage, dataLayout, destination.texture->GetFormat(), writeSizePixel));
+
+        TextureDataLayout passDataLayout = dataLayout;
+        passDataLayout.offset = uploadHandle.startOffset;
+        passDataLayout.bytesPerRow = optimallyAlignedBytesPerRow;
+        passDataLayout.rowsPerImage = alignedRowsPerImage;
+
+        TextureCopy textureCopy;
+        textureCopy.texture = destination.texture;
+        textureCopy.mipLevel = destination.mipLevel;
+        textureCopy.origin = destination.origin;
+        textureCopy.aspect = ConvertAspect(destination.texture->GetFormat(), destination.aspect);
+
+        return ToBackend(GetDevice())
+            ->CopyFromStagingToTexture(uploadHandle.stagingBuffer, passDataLayout, &textureCopy,
+                                       writeSizePixel);
+    }
+
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/QueueD3D12.h b/src/dawn_native/d3d12/QueueD3D12.h
index f211d0b..6b23a45 100644
--- a/src/dawn_native/d3d12/QueueD3D12.h
+++ b/src/dawn_native/d3d12/QueueD3D12.h
@@ -31,6 +31,10 @@
 
       private:
         MaybeError SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) override;
+        MaybeError WriteTextureImpl(const TextureCopyView& destination,
+                                    const void* data,
+                                    const TextureDataLayout& dataLayout,
+                                    const Extent3D& writeSizePixel) override;
     };
 
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/UtilsD3D12.cpp b/src/dawn_native/d3d12/UtilsD3D12.cpp
index 97de204..6a3b4bc 100644
--- a/src/dawn_native/d3d12/UtilsD3D12.cpp
+++ b/src/dawn_native/d3d12/UtilsD3D12.cpp
@@ -15,6 +15,9 @@
 #include "dawn_native/d3d12/UtilsD3D12.h"
 
 #include "common/Assert.h"
+#include "dawn_native/Format.h"
+#include "dawn_native/d3d12/BufferD3D12.h"
+#include "dawn_native/d3d12/CommandRecordingContext.h"
 
 #include <stringapiset.h>
 
@@ -65,7 +68,7 @@
     D3D12_TEXTURE_COPY_LOCATION ComputeTextureCopyLocationForTexture(const Texture* texture,
                                                                      uint32_t level,
                                                                      uint32_t slice,
-                                                                     Aspect aspect) {
+                                                                     const Aspect& aspect) {
         D3D12_TEXTURE_COPY_LOCATION copyLocation;
         copyLocation.pResource = texture->GetD3D12Resource();
         copyLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
@@ -136,4 +139,78 @@
         }
     }
 
+    void RecordCopyBufferToTextureFromTextureCopySplit(ID3D12GraphicsCommandList* commandList,
+                                                       const Texture2DCopySplit& baseCopySplit,
+                                                       ID3D12Resource* bufferResource,
+                                                       uint64_t baseOffsetBytes,
+                                                       uint64_t bufferBytesPerRow,
+                                                       Texture* texture,
+                                                       uint32_t textureMiplevel,
+                                                       uint32_t textureSlice,
+                                                       const Aspect& aspect) {
+        const D3D12_TEXTURE_COPY_LOCATION textureLocation =
+            ComputeTextureCopyLocationForTexture(texture, textureMiplevel, textureSlice, aspect);
+
+        const uint64_t offsetBytes = baseCopySplit.offset + baseOffsetBytes;
+
+        for (uint32_t i = 0; i < baseCopySplit.count; ++i) {
+            const Texture2DCopySplit::CopyInfo& info = baseCopySplit.copies[i];
+
+            // TODO(jiawei.shao@intel.com): pre-compute bufferLocation and sourceRegion as
+            // members in Texture2DCopySplit::CopyInfo.
+            const D3D12_TEXTURE_COPY_LOCATION bufferLocation =
+                ComputeBufferLocationForCopyTextureRegion(texture, bufferResource, info.bufferSize,
+                                                          offsetBytes, bufferBytesPerRow);
+            const D3D12_BOX sourceRegion =
+                ComputeD3D12BoxFromOffsetAndSize(info.bufferOffset, info.copySize);
+
+            commandList->CopyTextureRegion(&textureLocation, info.textureOffset.x,
+                                           info.textureOffset.y, info.textureOffset.z,
+                                           &bufferLocation, &sourceRegion);
+        }
+    }
+
+    void CopyBufferToTextureWithCopySplit(CommandRecordingContext* commandContext,
+                                          const TextureCopy& textureCopy,
+                                          const Extent3D& copySize,
+                                          Texture* texture,
+                                          ID3D12Resource* bufferResource,
+                                          const uint64_t offsetBytes,
+                                          const uint32_t bytesPerRow,
+                                          const uint32_t rowsPerImage,
+                                          const Aspect& aspects) {
+        // See comments in ComputeTextureCopySplits() for more details.
+        const TextureCopySplits copySplits =
+            ComputeTextureCopySplits(textureCopy.origin, copySize, texture->GetFormat(),
+                                     offsetBytes, bytesPerRow, rowsPerImage);
+
+        const uint64_t bytesPerSlice =
+            bytesPerRow * (rowsPerImage / texture->GetFormat().blockHeight);
+
+        // copySplits.copies2D[1] is always calculated for the second copy slice with
+        // extra "bytesPerSlice" copy offset compared with the first copy slice. So
+        // here we use an array bufferOffsetsForNextSlice to record the extra offsets
+        // for each copy slice: bufferOffsetsForNextSlice[0] is the extra offset for
+        // the next copy slice that uses copySplits.copies2D[0], and
+        // bufferOffsetsForNextSlice[1] is the extra offset for the next copy slice
+        // that uses copySplits.copies2D[1].
+        std::array<uint64_t, TextureCopySplits::kMaxTextureCopySplits> bufferOffsetsForNextSlice = {
+            {0u, 0u}};
+
+        for (uint32_t copySlice = 0; copySlice < copySize.depth; ++copySlice) {
+            const uint32_t splitIndex = copySlice % copySplits.copies2D.size();
+
+            const Texture2DCopySplit& copySplitPerLayerBase = copySplits.copies2D[splitIndex];
+            const uint64_t bufferOffsetForNextSlice = bufferOffsetsForNextSlice[splitIndex];
+            const uint32_t copyTextureLayer = copySlice + textureCopy.origin.z;
+
+            RecordCopyBufferToTextureFromTextureCopySplit(
+                commandContext->GetCommandList(), copySplitPerLayerBase, bufferResource,
+                bufferOffsetForNextSlice, bytesPerRow, texture, textureCopy.mipLevel,
+                copyTextureLayer, aspects);
+
+            bufferOffsetsForNextSlice[splitIndex] += bytesPerSlice * copySplits.copies2D.size();
+        }
+    }
+
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/UtilsD3D12.h b/src/dawn_native/d3d12/UtilsD3D12.h
index 97bf74c..ad47570 100644
--- a/src/dawn_native/d3d12/UtilsD3D12.h
+++ b/src/dawn_native/d3d12/UtilsD3D12.h
@@ -15,6 +15,7 @@
 #ifndef DAWNNATIVE_D3D12_UTILSD3D12_H_
 #define DAWNNATIVE_D3D12_UTILSD3D12_H_
 
+#include "dawn_native/Commands.h"
 #include "dawn_native/d3d12/BufferD3D12.h"
 #include "dawn_native/d3d12/TextureCopySplitter.h"
 #include "dawn_native/d3d12/TextureD3D12.h"
@@ -30,7 +31,7 @@
     D3D12_TEXTURE_COPY_LOCATION ComputeTextureCopyLocationForTexture(const Texture* texture,
                                                                      uint32_t level,
                                                                      uint32_t slice,
-                                                                     Aspect aspect);
+                                                                     const Aspect& aspect);
 
     D3D12_TEXTURE_COPY_LOCATION ComputeBufferLocationForCopyTextureRegion(
         const Texture* texture,
@@ -42,6 +43,26 @@
 
     bool IsTypeless(DXGI_FORMAT format);
 
+    void RecordCopyBufferToTextureFromTextureCopySplit(ID3D12GraphicsCommandList* commandList,
+                                                       const Texture2DCopySplit& baseCopySplit,
+                                                       Buffer* buffer,
+                                                       uint64_t baseOffset,
+                                                       uint64_t bufferBytesPerRow,
+                                                       Texture* texture,
+                                                       uint32_t textureMiplevel,
+                                                       uint32_t textureSlice,
+                                                       const Aspect& aspect);
+
+    void CopyBufferToTextureWithCopySplit(CommandRecordingContext* commandContext,
+                                          const TextureCopy& textureCopy,
+                                          const Extent3D& copySize,
+                                          Texture* texture,
+                                          ID3D12Resource* bufferResource,
+                                          const uint64_t offset,
+                                          const uint32_t bytesPerRow,
+                                          const uint32_t rowsPerImage,
+                                          const Aspect& aspect);
+
 }}  // namespace dawn_native::d3d12
 
 #endif  // DAWNNATIVE_D3D12_UTILSD3D12_H_
diff --git a/src/dawn_native/metal/DeviceMTL.h b/src/dawn_native/metal/DeviceMTL.h
index 87a697b..8c4bc49 100644
--- a/src/dawn_native/metal/DeviceMTL.h
+++ b/src/dawn_native/metal/DeviceMTL.h
@@ -67,7 +67,7 @@
         MaybeError CopyFromStagingToTexture(StagingBufferBase* source,
                                             const TextureDataLayout& dataLayout,
                                             TextureCopy* dst,
-                                            const Extent3D copySize);
+                                            const Extent3D& copySizePixels);
 
       private:
         Device(AdapterBase* adapter, id<MTLDevice> mtlDevice, const DeviceDescriptor* descriptor);
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index fb7fdc3..79b4f81 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -271,7 +271,7 @@
     MaybeError Device::CopyFromStagingToTexture(StagingBufferBase* source,
                                                 const TextureDataLayout& dataLayout,
                                                 TextureCopy* dst,
-                                                const Extent3D copySize) {
+                                                const Extent3D& copySizePixels) {
         Texture* texture = ToBackend(dst->texture.Get());
 
         // This function assumes data is perfectly aligned. Otherwise, it might be necessary
@@ -280,19 +280,19 @@
         uint32_t blockSize = blockInfo.blockByteSize;
         uint32_t blockWidth = blockInfo.blockWidth;
         uint32_t blockHeight = blockInfo.blockHeight;
-        ASSERT(dataLayout.rowsPerImage == (copySize.height));
-        ASSERT(dataLayout.bytesPerRow == (copySize.width) / blockWidth * blockSize);
+        ASSERT(dataLayout.rowsPerImage == (copySizePixels.height));
+        ASSERT(dataLayout.bytesPerRow == (copySizePixels.width) / blockWidth * blockSize);
 
-        EnsureDestinationTextureInitialized(texture, *dst, copySize);
+        EnsureDestinationTextureInitialized(texture, *dst, copySizePixels);
 
         // Metal validation layer requires that if the texture's pixel format is a compressed
         // format, the sourceSize must be a multiple of the pixel format's block size or be
         // clamped to the edge of the texture if the block extends outside the bounds of a
         // texture.
         const Extent3D clampedSize =
-            texture->ClampToMipLevelVirtualSize(dst->mipLevel, dst->origin, copySize);
+            texture->ClampToMipLevelVirtualSize(dst->mipLevel, dst->origin, copySizePixels);
         const uint32_t copyBaseLayer = dst->origin.z;
-        const uint32_t copyLayerCount = copySize.depth;
+        const uint32_t copyLayerCount = copySizePixels.depth;
         const uint64_t bytesPerImage =
             dataLayout.rowsPerImage * dataLayout.bytesPerRow / blockHeight;
 
diff --git a/src/dawn_native/metal/QueueMTL.h b/src/dawn_native/metal/QueueMTL.h
index bda47eb..55915c2 100644
--- a/src/dawn_native/metal/QueueMTL.h
+++ b/src/dawn_native/metal/QueueMTL.h
@@ -28,11 +28,10 @@
 
       private:
         MaybeError SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) override;
-        MaybeError WriteTextureImpl(const TextureCopyView* destination,
+        MaybeError WriteTextureImpl(const TextureCopyView& destination,
                                     const void* data,
-                                    size_t dataSize,
-                                    const TextureDataLayout* dataLayout,
-                                    const Extent3D* writeSize) override;
+                                    const TextureDataLayout& dataLayout,
+                                    const Extent3D& writeSizePixel) override;
     };
 
 }}  // namespace dawn_native::metal
diff --git a/src/dawn_native/metal/QueueMTL.mm b/src/dawn_native/metal/QueueMTL.mm
index 237750c..44cbb0f 100644
--- a/src/dawn_native/metal/QueueMTL.mm
+++ b/src/dawn_native/metal/QueueMTL.mm
@@ -29,37 +29,36 @@
         ResultOrError<UploadHandle> UploadTextureDataAligningBytesPerRow(
             DeviceBase* device,
             const void* data,
-            size_t dataSize,
             uint32_t alignedBytesPerRow,
             uint32_t alignedRowsPerImage,
-            const TextureDataLayout* dataLayout,
+            const TextureDataLayout& dataLayout,
             const TexelBlockInfo& blockInfo,
-            const Extent3D* writeSize) {
-            uint32_t newDataSize = ComputeRequiredBytesInCopy(
-                blockInfo, *writeSize, alignedBytesPerRow, alignedRowsPerImage);
+            const Extent3D& writeSizePixel) {
+            uint32_t newDataSizeBytes = ComputeRequiredBytesInCopy(
+                blockInfo, writeSizePixel, alignedBytesPerRow, alignedRowsPerImage);
 
             UploadHandle uploadHandle;
             DAWN_TRY_ASSIGN(uploadHandle, device->GetDynamicUploader()->Allocate(
-                                              newDataSize, device->GetPendingCommandSerial()));
+                                              newDataSizeBytes, device->GetPendingCommandSerial()));
             ASSERT(uploadHandle.mappedBuffer != nullptr);
 
             uint8_t* dstPointer = static_cast<uint8_t*>(uploadHandle.mappedBuffer);
             const uint8_t* srcPointer = static_cast<const uint8_t*>(data);
-            srcPointer += dataLayout->offset;
+            srcPointer += dataLayout.offset;
 
             uint32_t alignedRowsPerImageInBlock = alignedRowsPerImage / blockInfo.blockHeight;
-            uint32_t dataRowsPerImageInBlock = dataLayout->rowsPerImage / blockInfo.blockHeight;
+            uint32_t dataRowsPerImageInBlock = dataLayout.rowsPerImage / blockInfo.blockHeight;
             if (dataRowsPerImageInBlock == 0) {
-                dataRowsPerImageInBlock = writeSize->height / blockInfo.blockHeight;
+                dataRowsPerImageInBlock = writeSizePixel.height / blockInfo.blockHeight;
             }
 
             ASSERT(dataRowsPerImageInBlock >= alignedRowsPerImageInBlock);
             uint64_t imageAdditionalStride =
-                dataLayout->bytesPerRow * (dataRowsPerImageInBlock - alignedRowsPerImageInBlock);
+                dataLayout.bytesPerRow * (dataRowsPerImageInBlock - alignedRowsPerImageInBlock);
 
-            CopyTextureData(dstPointer, srcPointer, writeSize->depth, alignedRowsPerImageInBlock,
-                            imageAdditionalStride, alignedBytesPerRow, alignedBytesPerRow,
-                            dataLayout->bytesPerRow);
+            CopyTextureData(dstPointer, srcPointer, writeSizePixel.depth,
+                            alignedRowsPerImageInBlock, imageAdditionalStride, alignedBytesPerRow,
+                            alignedBytesPerRow, dataLayout.bytesPerRow);
 
             return uploadHandle;
         }
@@ -86,40 +85,40 @@
     // We don't write from the CPU to the texture directly which can be done in Metal using the
     // replaceRegion function, because the function requires a non-private storage mode and Dawn
     // sets the private storage mode by default for all textures except IOSurfaces on macOS.
-    MaybeError Queue::WriteTextureImpl(const TextureCopyView* destination,
+    MaybeError Queue::WriteTextureImpl(const TextureCopyView& destination,
                                        const void* data,
-                                       size_t dataSize,
-                                       const TextureDataLayout* dataLayout,
-                                       const Extent3D* writeSize) {
+                                       const TextureDataLayout& dataLayout,
+                                       const Extent3D& writeSizePixel) {
         const TexelBlockInfo& blockInfo =
-            destination->texture->GetFormat().GetTexelBlockInfo(destination->aspect);
+            destination.texture->GetFormat().GetTexelBlockInfo(destination.aspect);
 
         // We are only copying the part of the data that will appear in the texture.
-        // Note that validating texture copy range ensures that writeSize->width and
-        // writeSize->height are multiples of blockWidth and blockHeight respectively.
+        // Note that validating texture copy range ensures that writeSizePixel->width and
+        // writeSizePixel->height are multiples of blockWidth and blockHeight respectively.
         uint32_t alignedBytesPerRow =
-            (writeSize->width) / blockInfo.blockWidth * blockInfo.blockByteSize;
-        uint32_t alignedRowsPerImage = writeSize->height;
+            (writeSizePixel.width) / blockInfo.blockWidth * blockInfo.blockByteSize;
+        uint32_t alignedRowsPerImage = writeSizePixel.height;
 
         UploadHandle uploadHandle;
-        DAWN_TRY_ASSIGN(uploadHandle, UploadTextureDataAligningBytesPerRow(
-                                          GetDevice(), data, dataSize, alignedBytesPerRow,
-                                          alignedRowsPerImage, dataLayout, blockInfo, writeSize));
+        DAWN_TRY_ASSIGN(uploadHandle,
+                        UploadTextureDataAligningBytesPerRow(GetDevice(), data, alignedBytesPerRow,
+                                                             alignedRowsPerImage, dataLayout,
+                                                             blockInfo, writeSizePixel));
 
-        TextureDataLayout passDataLayout = *dataLayout;
+        TextureDataLayout passDataLayout = dataLayout;
         passDataLayout.offset = uploadHandle.startOffset;
         passDataLayout.bytesPerRow = alignedBytesPerRow;
         passDataLayout.rowsPerImage = alignedRowsPerImage;
 
         TextureCopy textureCopy;
-        textureCopy.texture = destination->texture;
-        textureCopy.mipLevel = destination->mipLevel;
-        textureCopy.origin = destination->origin;
-        textureCopy.aspect = ConvertAspect(destination->texture->GetFormat(), destination->aspect);
+        textureCopy.texture = destination.texture;
+        textureCopy.mipLevel = destination.mipLevel;
+        textureCopy.origin = destination.origin;
+        textureCopy.aspect = ConvertAspect(destination.texture->GetFormat(), destination.aspect);
 
         return ToBackend(GetDevice())
             ->CopyFromStagingToTexture(uploadHandle.stagingBuffer, passDataLayout, &textureCopy,
-                                       *writeSize);
+                                       writeSizePixel);
     }
 
 }}  // namespace dawn_native::metal
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index f6afcfd..1b5e197 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -617,20 +617,21 @@
     MaybeError Device::CopyFromStagingToTexture(StagingBufferBase* source,
                                                 const TextureDataLayout& src,
                                                 TextureCopy* dst,
-                                                const Extent3D copySize) {
+                                                const Extent3D& copySizePixels) {
         // There is no need of a barrier to make host writes available and visible to the copy
         // operation for HOST_COHERENT memory. The Vulkan spec for vkQueueSubmit describes that it
         // does an implicit availability, visibility and domain operation.
 
         CommandRecordingContext* recordingContext = GetPendingRecordingContext();
 
-        VkBufferImageCopy region = ComputeBufferImageCopyRegion(src, *dst, copySize);
+        VkBufferImageCopy region = ComputeBufferImageCopyRegion(src, *dst, copySizePixels);
         VkImageSubresourceLayers subresource = region.imageSubresource;
 
         ASSERT(dst->texture->GetDimension() == wgpu::TextureDimension::e2D);
-        SubresourceRange range = GetSubresourcesAffectedByCopy(*dst, copySize);
+        SubresourceRange range = GetSubresourcesAffectedByCopy(*dst, copySizePixels);
 
-        if (IsCompleteSubresourceCopiedTo(dst->texture.Get(), copySize, subresource.mipLevel)) {
+        if (IsCompleteSubresourceCopiedTo(dst->texture.Get(), copySizePixels,
+                                          subresource.mipLevel)) {
             // Since texture has been overwritten, it has been "initialized"
             dst->texture->SetIsSubresourceContentInitialized(true, range);
         } else {
diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h
index 851a1e7..89ec1ea 100644
--- a/src/dawn_native/vulkan/DeviceVk.h
+++ b/src/dawn_native/vulkan/DeviceVk.h
@@ -91,7 +91,7 @@
         MaybeError CopyFromStagingToTexture(StagingBufferBase* source,
                                             const TextureDataLayout& src,
                                             TextureCopy* dst,
-                                            const Extent3D copySize);
+                                            const Extent3D& copySizePixels);
 
         ResultOrError<ResourceMemoryAllocation> AllocateMemory(VkMemoryRequirements requirements,
                                                                bool mappable);
diff --git a/src/dawn_native/vulkan/QueueVk.cpp b/src/dawn_native/vulkan/QueueVk.cpp
index 227ccb4..3842652 100644
--- a/src/dawn_native/vulkan/QueueVk.cpp
+++ b/src/dawn_native/vulkan/QueueVk.cpp
@@ -31,15 +31,14 @@
         ResultOrError<UploadHandle> UploadTextureDataAligningBytesPerRow(
             DeviceBase* device,
             const void* data,
-            size_t dataSize,
             uint32_t alignedBytesPerRow,
             uint32_t optimallyAlignedBytesPerRow,
             uint32_t alignedRowsPerImage,
-            const TextureDataLayout* dataLayout,
+            const TextureDataLayout& dataLayout,
             const TexelBlockInfo& blockInfo,
-            const Extent3D* writeSize) {
-            uint32_t newDataSize = ComputeRequiredBytesInCopy(
-                blockInfo, *writeSize, optimallyAlignedBytesPerRow, alignedRowsPerImage);
+            const Extent3D& writeSizePixel) {
+            uint32_t newDataSizeBytes = ComputeRequiredBytesInCopy(
+                blockInfo, writeSizePixel, optimallyAlignedBytesPerRow, alignedRowsPerImage);
 
             uint64_t optimalOffsetAlignment =
                 ToBackend(device)
@@ -48,18 +47,18 @@
 
             UploadHandle uploadHandle;
             DAWN_TRY_ASSIGN(uploadHandle, device->GetDynamicUploader()->Allocate(
-                                              newDataSize + optimalOffsetAlignment - 1,
+                                              newDataSizeBytes + optimalOffsetAlignment - 1,
                                               device->GetPendingCommandSerial()));
             ASSERT(uploadHandle.mappedBuffer != nullptr);
 
             uint8_t* dstPointer = static_cast<uint8_t*>(uploadHandle.mappedBuffer);
             const uint8_t* srcPointer = static_cast<const uint8_t*>(data);
-            srcPointer += dataLayout->offset;
+            srcPointer += dataLayout.offset;
 
             uint32_t alignedRowsPerImageInBlock = alignedRowsPerImage / blockInfo.blockHeight;
-            uint32_t dataRowsPerImageInBlock = dataLayout->rowsPerImage / blockInfo.blockHeight;
+            uint32_t dataRowsPerImageInBlock = dataLayout.rowsPerImage / blockInfo.blockHeight;
             if (dataRowsPerImageInBlock == 0) {
-                dataRowsPerImageInBlock = writeSize->height / blockInfo.blockHeight;
+                dataRowsPerImageInBlock = writeSizePixel.height / blockInfo.blockHeight;
             }
 
             uint64_t additionalOffset =
@@ -69,11 +68,11 @@
 
             ASSERT(dataRowsPerImageInBlock >= alignedRowsPerImageInBlock);
             uint64_t imageAdditionalStride =
-                dataLayout->bytesPerRow * (dataRowsPerImageInBlock - alignedRowsPerImageInBlock);
+                dataLayout.bytesPerRow * (dataRowsPerImageInBlock - alignedRowsPerImageInBlock);
 
-            CopyTextureData(dstPointer, srcPointer, writeSize->depth, alignedRowsPerImageInBlock,
-                            imageAdditionalStride, alignedBytesPerRow, optimallyAlignedBytesPerRow,
-                            dataLayout->bytesPerRow);
+            CopyTextureData(dstPointer, srcPointer, writeSizePixel.depth,
+                            alignedRowsPerImageInBlock, imageAdditionalStride, alignedBytesPerRow,
+                            optimallyAlignedBytesPerRow, dataLayout.bytesPerRow);
 
             return uploadHandle;
         }
@@ -105,20 +104,19 @@
         return {};
     }
 
-    MaybeError Queue::WriteTextureImpl(const TextureCopyView* destination,
+    MaybeError Queue::WriteTextureImpl(const TextureCopyView& destination,
                                        const void* data,
-                                       size_t dataSize,
-                                       const TextureDataLayout* dataLayout,
-                                       const Extent3D* writeSize) {
+                                       const TextureDataLayout& dataLayout,
+                                       const Extent3D& writeSizePixel) {
         const TexelBlockInfo& blockInfo =
-            destination->texture->GetFormat().GetTexelBlockInfo(destination->aspect);
+            destination.texture->GetFormat().GetTexelBlockInfo(destination.aspect);
 
         // We are only copying the part of the data that will appear in the texture.
-        // Note that validating texture copy range ensures that writeSize->width and
-        // writeSize->height are multiples of blockWidth and blockHeight respectively.
+        // Note that validating texture copy range ensures that writeSizePixel->width and
+        // writeSizePixel->height are multiples of blockWidth and blockHeight respectively.
         uint32_t alignedBytesPerRow =
-            (writeSize->width) / blockInfo.blockWidth * blockInfo.blockByteSize;
-        uint32_t alignedRowsPerImage = writeSize->height;
+            (writeSizePixel.width) / blockInfo.blockWidth * blockInfo.blockByteSize;
+        uint32_t alignedRowsPerImage = writeSizePixel.height;
 
         uint32_t optimalBytesPerRowAlignment =
             ToBackend(GetDevice())
@@ -128,24 +126,24 @@
             Align(alignedBytesPerRow, optimalBytesPerRowAlignment);
 
         UploadHandle uploadHandle;
-        DAWN_TRY_ASSIGN(uploadHandle, UploadTextureDataAligningBytesPerRow(
-                                          GetDevice(), data, dataSize, alignedBytesPerRow,
-                                          optimallyAlignedBytesPerRow, alignedRowsPerImage,
-                                          dataLayout, blockInfo, writeSize));
+        DAWN_TRY_ASSIGN(uploadHandle,
+                        UploadTextureDataAligningBytesPerRow(
+                            GetDevice(), data, alignedBytesPerRow, optimallyAlignedBytesPerRow,
+                            alignedRowsPerImage, dataLayout, blockInfo, writeSizePixel));
 
-        TextureDataLayout passDataLayout = *dataLayout;
+        TextureDataLayout passDataLayout = dataLayout;
         passDataLayout.offset = uploadHandle.startOffset;
         passDataLayout.bytesPerRow = optimallyAlignedBytesPerRow;
         passDataLayout.rowsPerImage = alignedRowsPerImage;
 
         TextureCopy textureCopy;
-        textureCopy.texture = destination->texture;
-        textureCopy.mipLevel = destination->mipLevel;
-        textureCopy.origin = destination->origin;
-        textureCopy.aspect = ConvertAspect(destination->texture->GetFormat(), destination->aspect);
+        textureCopy.texture = destination.texture;
+        textureCopy.mipLevel = destination.mipLevel;
+        textureCopy.origin = destination.origin;
+        textureCopy.aspect = ConvertAspect(destination.texture->GetFormat(), destination.aspect);
 
         return ToBackend(GetDevice())
             ->CopyFromStagingToTexture(uploadHandle.stagingBuffer, passDataLayout, &textureCopy,
-                                       *writeSize);
+                                       writeSizePixel);
     }
 }}  // namespace dawn_native::vulkan
diff --git a/src/dawn_native/vulkan/QueueVk.h b/src/dawn_native/vulkan/QueueVk.h
index bcfdbda..34a0e6a 100644
--- a/src/dawn_native/vulkan/QueueVk.h
+++ b/src/dawn_native/vulkan/QueueVk.h
@@ -31,11 +31,10 @@
         using QueueBase::QueueBase;
 
         MaybeError SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) override;
-        MaybeError WriteTextureImpl(const TextureCopyView* destination,
+        MaybeError WriteTextureImpl(const TextureCopyView& destination,
                                     const void* data,
-                                    size_t dataSize,
-                                    const TextureDataLayout* dataLayout,
-                                    const Extent3D* writeSize) override;
+                                    const TextureDataLayout& dataLayout,
+                                    const Extent3D& writeSizePixel) override;
     };
 
 }}  // namespace dawn_native::vulkan
diff --git a/src/tests/end2end/CompressedTextureFormatTests.cpp b/src/tests/end2end/CompressedTextureFormatTests.cpp
index 2054dfa..a6ae419 100644
--- a/src/tests/end2end/CompressedTextureFormatTests.cpp
+++ b/src/tests/end2end/CompressedTextureFormatTests.cpp
@@ -1179,4 +1179,7 @@
     }
 }
 
-DAWN_INSTANTIATE_TEST(CompressedTextureWriteTextureTest, MetalBackend(), VulkanBackend());
\ No newline at end of file
+DAWN_INSTANTIATE_TEST(CompressedTextureWriteTextureTest,
+                      MetalBackend(),
+                      VulkanBackend(),
+                      D3D12Backend());
\ No newline at end of file
diff --git a/src/tests/end2end/QueueTests.cpp b/src/tests/end2end/QueueTests.cpp
index edf9ccf..2ac7c8c 100644
--- a/src/tests/end2end/QueueTests.cpp
+++ b/src/tests/end2end/QueueTests.cpp
@@ -522,4 +522,4 @@
     }
 }
 
-DAWN_INSTANTIATE_TEST(QueueWriteTextureTests, MetalBackend(), VulkanBackend());
+DAWN_INSTANTIATE_TEST(QueueWriteTextureTests, MetalBackend(), VulkanBackend(), D3D12Backend());
diff --git a/src/tests/end2end/TextureZeroInitTests.cpp b/src/tests/end2end/TextureZeroInitTests.cpp
index b4646f5..7289757 100644
--- a/src/tests/end2end/TextureZeroInitTests.cpp
+++ b/src/tests/end2end/TextureZeroInitTests.cpp
@@ -1382,7 +1382,7 @@
 // In this test WriteTexture fully overwrites a texture
 TEST_P(TextureZeroInitTest, WriteWholeTexture) {
     // TODO(dawn:483): Remove this condition after implementing WriteTexture in those backends.
-    DAWN_SKIP_TEST_IF(IsOpenGL() || IsD3D12());
+    DAWN_SKIP_TEST_IF(IsOpenGL());
 
     wgpu::TextureDescriptor descriptor = CreateTextureDescriptor(
         1, 1, wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc, kColorFormat);
@@ -1417,7 +1417,7 @@
 // half.
 TEST_P(TextureZeroInitTest, WriteTextureHalf) {
     // TODO(dawn:483): Remove this condition after implementing WriteTexture in those backends.
-    DAWN_SKIP_TEST_IF(IsOpenGL() || IsD3D12());
+    DAWN_SKIP_TEST_IF(IsOpenGL());
 
     wgpu::TextureDescriptor descriptor = CreateTextureDescriptor(
         4, 1,
@@ -1457,7 +1457,7 @@
 // is needed for neither the subresources involved in the write nor the other subresources.
 TEST_P(TextureZeroInitTest, WriteWholeTextureArray) {
     // TODO(dawn:483): Remove this condition after implementing WriteTexture in those backends.
-    DAWN_SKIP_TEST_IF(IsOpenGL() || IsD3D12());
+    DAWN_SKIP_TEST_IF(IsOpenGL());
 
     wgpu::TextureDescriptor descriptor = CreateTextureDescriptor(
         1, 6, wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc, kColorFormat);
@@ -1500,7 +1500,7 @@
 // half.
 TEST_P(TextureZeroInitTest, WriteTextureArrayHalf) {
     // TODO(dawn:483): Remove this condition after implementing WriteTexture in those backends.
-    DAWN_SKIP_TEST_IF(IsOpenGL() || IsD3D12());
+    DAWN_SKIP_TEST_IF(IsOpenGL());
 
     wgpu::TextureDescriptor descriptor = CreateTextureDescriptor(
         4, 6,
@@ -1547,7 +1547,7 @@
 // In this test WriteTexture fully overwrites a texture at mip level.
 TEST_P(TextureZeroInitTest, WriteWholeTextureAtMipLevel) {
     // TODO(dawn:483): Remove this condition after implementing WriteTexture in those backends.
-    DAWN_SKIP_TEST_IF(IsOpenGL() || IsD3D12());
+    DAWN_SKIP_TEST_IF(IsOpenGL());
 
     wgpu::TextureDescriptor descriptor = CreateTextureDescriptor(
         4, 1, wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc, kColorFormat);
@@ -1586,7 +1586,7 @@
 // other half.
 TEST_P(TextureZeroInitTest, WriteTextureHalfAtMipLevel) {
     // TODO(dawn:483): Remove this condition after implementing WriteTexture in those backends.
-    DAWN_SKIP_TEST_IF(IsOpenGL() || IsD3D12());
+    DAWN_SKIP_TEST_IF(IsOpenGL());
 
     wgpu::TextureDescriptor descriptor = CreateTextureDescriptor(
         4, 1,