blob: 446a917528ddee82097c5c37971463b808eb4b82 [file] [log] [blame] [edit]
// Copyright 2020 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <vector>
#include "dawn/common/Math.h"
#include "dawn/tests/DawnTest.h"
#include "dawn/utils/TestUtils.h"
#include "dawn/utils/TextureUtils.h"
#include "dawn/utils/WGPUHelpers.h"
namespace dawn {
namespace {
class QueueTests : public DawnTest {};
// Test that GetQueue always returns the same object.
TEST_P(QueueTests, GetQueueSameObject) {
wgpu::Queue q1 = device.GetQueue();
wgpu::Queue q2 = device.GetQueue();
EXPECT_EQ(q1.Get(), q2.Get());
}
DAWN_INSTANTIATE_TEST(QueueTests,
D3D11Backend(),
D3D12Backend(),
MetalBackend(),
NullBackend(),
OpenGLBackend(),
OpenGLESBackend(),
VulkanBackend());
class QueueWriteBufferTests : public DawnTest {};
// Test the simplest WriteBuffer setting one u32 at offset 0.
TEST_P(QueueWriteBufferTests, SmallDataAtZero) {
wgpu::BufferDescriptor descriptor;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
uint32_t value = 0x01020304;
queue.WriteBuffer(buffer, 0, &value, sizeof(value));
EXPECT_BUFFER_U32_EQ(value, buffer, 0);
}
// Test an empty WriteBuffer
TEST_P(QueueWriteBufferTests, ZeroSized) {
wgpu::BufferDescriptor descriptor;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
uint32_t initialValue = 0x42;
queue.WriteBuffer(buffer, 0, &initialValue, sizeof(initialValue));
queue.WriteBuffer(buffer, 0, nullptr, 0);
// The content of the buffer isn't changed
EXPECT_BUFFER_U32_EQ(initialValue, buffer, 0);
}
// Call WriteBuffer at offset 0 via a u32 twice. Test that data is updated accoordingly.
TEST_P(QueueWriteBufferTests, SetTwice) {
wgpu::BufferDescriptor descriptor;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
uint32_t value = 0x01020304;
queue.WriteBuffer(buffer, 0, &value, sizeof(value));
EXPECT_BUFFER_U32_EQ(value, buffer, 0);
value = 0x05060708;
queue.WriteBuffer(buffer, 0, &value, sizeof(value));
EXPECT_BUFFER_U32_EQ(value, buffer, 0);
}
// Test that WriteBuffer offset works.
TEST_P(QueueWriteBufferTests, SmallDataAtOffset) {
wgpu::BufferDescriptor descriptor;
descriptor.size = 4000;
descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
constexpr uint64_t kOffset = 2000;
uint32_t value = 0x01020304;
queue.WriteBuffer(buffer, kOffset, &value, sizeof(value));
EXPECT_BUFFER_U32_EQ(value, buffer, kOffset);
}
// Stress test for many calls to WriteBuffer
TEST_P(QueueWriteBufferTests, ManyWriteBuffer) {
// 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.
// TODO(crbug.com/dawn/228): Re-enable once the issue with Metal on 10.14.6 is fixed.
DAWN_SUPPRESS_TEST_IF(IsMacOS() && IsIntel() && IsMetal());
// The Vulkan Validation Layers' memory barrier validation keeps track of every range written
// to independently which causes validation of each WriteBuffer to take increasing time, and
// this test to take forever. Skip it when VVLs are enabled.
DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsBackendValidationEnabled());
constexpr uint64_t kSize = 4000 * 1000;
constexpr uint32_t kElements = 250 * 250;
wgpu::BufferDescriptor descriptor;
descriptor.size = kSize;
descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
std::vector<uint32_t> expectedData;
for (uint32_t i = 0; i < kElements; ++i) {
queue.WriteBuffer(buffer, i * sizeof(uint32_t), &i, sizeof(i));
expectedData.push_back(i);
}
EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), buffer, 0, kElements);
}
// Test using WriteBuffer for lots of data
TEST_P(QueueWriteBufferTests, LargeWriteBuffer) {
constexpr uint64_t kSize = 4000 * 1000;
constexpr uint32_t kElements = 1000 * 1000;
wgpu::BufferDescriptor descriptor;
descriptor.size = kSize;
descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
std::vector<uint32_t> expectedData;
for (uint32_t i = 0; i < kElements; ++i) {
expectedData.push_back(i);
}
queue.WriteBuffer(buffer, 0, expectedData.data(), kElements * sizeof(uint32_t));
EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), buffer, 0, kElements);
}
// Test using WriteBuffer for super large data block
TEST_P(QueueWriteBufferTests, SuperLargeWriteBuffer) {
constexpr uint64_t kSize = 12000 * 1000;
constexpr uint64_t kElements = 3000 * 1000;
wgpu::BufferDescriptor descriptor;
descriptor.size = kSize;
descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
std::vector<uint32_t> expectedData;
for (uint32_t i = 0; i < kElements; ++i) {
expectedData.push_back(i);
}
queue.WriteBuffer(buffer, 0, expectedData.data(), kElements * sizeof(uint32_t));
EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), buffer, 0, kElements);
}
// Test using the max buffer size. Regression test for dawn:1985. We don't bother validating the
// results for this case since that would take a lot longer, just that there are no errors.
TEST_P(QueueWriteBufferTests, MaxBufferSizeWriteBuffer) {
uint32_t maxBufferSize = GetSupportedLimits().limits.maxBufferSize;
wgpu::BufferDescriptor descriptor;
descriptor.size = maxBufferSize;
descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
std::vector<uint8_t> data(maxBufferSize);
queue.WriteBuffer(buffer, 0, data.data(), maxBufferSize);
}
// Test a special code path: writing when dynamic uploader already contatins some unaligned
// data, it might be necessary to use a ring buffer with properly aligned offset.
TEST_P(QueueWriteBufferTests, UnalignedDynamicUploader) {
utils::UnalignDynamicUploader(device);
wgpu::BufferDescriptor descriptor;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
uint32_t value = 0x01020304;
queue.WriteBuffer(buffer, 0, &value, sizeof(value));
EXPECT_BUFFER_U32_EQ(value, buffer, 0);
}
DAWN_INSTANTIATE_TEST(QueueWriteBufferTests,
D3D11Backend(),
D3D12Backend(),
MetalBackend(),
OpenGLBackend(),
OpenGLESBackend(),
VulkanBackend());
// For MinimumDataSpec bytesPerRow and rowsPerImage, compute a default from the copy extent.
constexpr uint32_t kStrideComputeDefault = 0xFFFF'FFFEul;
namespace {
using TextureFormat = wgpu::TextureFormat;
DAWN_TEST_PARAM_STRUCT(WriteTextureFormatParams, TextureFormat);
} // namespace
class QueueWriteTextureTests : public DawnTestWithParams<WriteTextureFormatParams> {
protected:
struct TextureSpec {
wgpu::Origin3D copyOrigin;
wgpu::Extent3D textureSize;
uint32_t level;
};
struct DataSpec {
uint64_t size;
uint64_t offset;
uint32_t bytesPerRow;
uint32_t rowsPerImage;
};
static DataSpec MinimumDataSpec(wgpu::Extent3D writeSize,
uint32_t overrideBytesPerRow = kStrideComputeDefault,
uint32_t overrideRowsPerImage = kStrideComputeDefault) {
uint32_t bytesPerRow =
writeSize.width * utils::GetTexelBlockSizeInBytes(GetParam().mTextureFormat);
if (overrideBytesPerRow != kStrideComputeDefault) {
bytesPerRow = overrideBytesPerRow;
}
uint32_t rowsPerImage = writeSize.height;
if (overrideRowsPerImage != kStrideComputeDefault) {
rowsPerImage = overrideRowsPerImage;
}
uint32_t totalDataSize = utils::RequiredBytesInCopy(bytesPerRow, rowsPerImage, writeSize,
GetParam().mTextureFormat);
return {totalDataSize, 0, bytesPerRow, rowsPerImage};
}
static void PackTextureData(const uint8_t* srcData,
uint32_t width,
uint32_t height,
uint32_t srcBytesPerRow,
uint8_t* dstData,
uint32_t dstBytesPerRow,
uint32_t bytesPerTexel) {
for (uint64_t y = 0; y < height; ++y) {
for (uint64_t x = 0; x < width; ++x) {
uint64_t src = x * bytesPerTexel + y * srcBytesPerRow;
uint64_t dst = x * bytesPerTexel + y * dstBytesPerRow;
for (uint64_t i = 0; i < bytesPerTexel; i++) {
dstData[dst + i] = srcData[src + i];
}
}
}
}
static void FillData(uint8_t* data, size_t count) {
for (size_t i = 0; i < count; ++i) {
data[i] = static_cast<uint8_t>(i % 253);
}
}
void DoTest(const TextureSpec& textureSpec,
const DataSpec& dataSpec,
const wgpu::Extent3D& copySize) {
// Create data of size `size` and populate it
std::vector<uint8_t> data(dataSpec.size);
FillData(data.data(), data.size());
// Create a texture that is `width` x `height` with (`level` + 1) mip levels.
wgpu::TextureDescriptor descriptor = {};
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.size = textureSpec.textureSize;
descriptor.format = GetParam().mTextureFormat;
descriptor.mipLevelCount = textureSpec.level + 1;
descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
wgpu::Texture texture = device.CreateTexture(&descriptor);
wgpu::TextureDataLayout textureDataLayout = utils::CreateTextureDataLayout(
dataSpec.offset, dataSpec.bytesPerRow, dataSpec.rowsPerImage);
wgpu::ImageCopyTexture imageCopyTexture =
utils::CreateImageCopyTexture(texture, textureSpec.level, textureSpec.copyOrigin);
queue.WriteTexture(&imageCopyTexture, data.data(), dataSpec.size, &textureDataLayout,
&copySize);
const uint32_t bytesPerTexel = utils::GetTexelBlockSizeInBytes(GetParam().mTextureFormat);
wgpu::Extent3D mipSize = {textureSpec.textureSize.width >> textureSpec.level,
textureSpec.textureSize.height >> textureSpec.level,
textureSpec.textureSize.depthOrArrayLayers};
uint32_t bytesPerRow = dataSpec.bytesPerRow;
if (bytesPerRow == wgpu::kCopyStrideUndefined) {
bytesPerRow = mipSize.width * bytesPerTexel;
}
uint32_t alignedBytesPerRow = Align(bytesPerRow, bytesPerTexel);
uint32_t appliedRowsPerImage =
dataSpec.rowsPerImage > 0 ? dataSpec.rowsPerImage : mipSize.height;
uint32_t bytesPerImage = bytesPerRow * appliedRowsPerImage;
const uint32_t maxArrayLayer = textureSpec.copyOrigin.z + copySize.depthOrArrayLayers;
uint64_t dataOffset = dataSpec.offset;
const uint32_t byteSizeLastLayer =
alignedBytesPerRow * (mipSize.height - 1) + mipSize.width * bytesPerTexel;
for (uint32_t slice = textureSpec.copyOrigin.z; slice < maxArrayLayer; ++slice) {
// Pack the data in the specified copy region to have the same
// format as the expected texture data.
std::vector<uint8_t> expected(byteSizeLastLayer, 0);
PackTextureData(data.data() + dataOffset, copySize.width, copySize.height,
dataSpec.bytesPerRow, expected.data(), copySize.width * bytesPerTexel,
bytesPerTexel);
EXPECT_TEXTURE_EQ(expected.data(), texture,
{textureSpec.copyOrigin.x, textureSpec.copyOrigin.y, slice},
{copySize.width, copySize.height, 1}, descriptor.format,
static_cast<uint8_t>(0), textureSpec.level)
<< "Write to texture failed copying " << dataSpec.size << "-byte data with offset "
<< dataSpec.offset << " and bytes per row " << dataSpec.bytesPerRow << " to [("
<< textureSpec.copyOrigin.x << ", " << textureSpec.copyOrigin.y << "), ("
<< textureSpec.copyOrigin.x + copySize.width << ", "
<< textureSpec.copyOrigin.y + copySize.height << ")) region of "
<< textureSpec.textureSize.width << " x " << textureSpec.textureSize.height
<< " texture at mip level " << textureSpec.level << " layer " << slice << std::endl;
dataOffset += bytesPerImage;
}
}
void DoSimpleWriteTextureTest(uint32_t width, uint32_t height) {
constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm;
constexpr uint32_t kPixelSize = 4;
std::vector<uint32_t> data(width * height);
for (size_t i = 0; i < data.size(); i++) {
data[i] = 0xFFFFFFFF;
}
wgpu::TextureDescriptor descriptor = {};
descriptor.size = {width, height, 1};
descriptor.format = kFormat;
descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc;
wgpu::Texture texture = device.CreateTexture(&descriptor);
wgpu::ImageCopyTexture imageCopyTexture =
utils::CreateImageCopyTexture(texture, 0, {0, 0, 0});
wgpu::TextureDataLayout textureDataLayout =
utils::CreateTextureDataLayout(0, width * kPixelSize);
wgpu::Extent3D copyExtent = {width, height, 1};
device.GetQueue().WriteTexture(&imageCopyTexture, data.data(), width * height * kPixelSize,
&textureDataLayout, &copyExtent);
EXPECT_TEXTURE_EQ(data.data(), texture, {0, 0}, {width, height});
}
};
// Test writing the whole texture for varying texture sizes.
TEST_P(QueueWriteTextureTests, VaryingTextureSize) {
for (unsigned int w : {127, 128}) {
for (unsigned int h : {63, 64}) {
for (unsigned int d : {1, 3, 4}) {
TextureSpec textureSpec;
textureSpec.textureSize = {w, h, d};
textureSpec.copyOrigin = {0, 0, 0};
textureSpec.level = 0;
DoTest(textureSpec, MinimumDataSpec({w, h, d}), {w, h, d});
}
}
}
}
// Test uploading a large amount of data with writeTexture.
TEST_P(QueueWriteTextureTests, LargeWriteTexture) {
TextureSpec textureSpec;
textureSpec.textureSize = {2048, 2048, 2};
textureSpec.copyOrigin = {0, 0, 0};
textureSpec.level = 0;
DoTest(textureSpec, MinimumDataSpec(textureSpec.textureSize), textureSpec.textureSize);
}
// Test writing a pixel with an offset.
TEST_P(QueueWriteTextureTests, VaryingTextureOffset) {
constexpr uint32_t kWidth = 259;
constexpr uint32_t kHeight = 127;
DataSpec pixelData = MinimumDataSpec({1, 1, 1});
constexpr wgpu::Extent3D kCopySize = {1, 1, 1};
constexpr wgpu::Extent3D kTextureSize = {kWidth, kHeight, 1};
TextureSpec defaultTextureSpec;
defaultTextureSpec.textureSize = kTextureSize;
defaultTextureSpec.level = 0;
for (unsigned int w : {0u, kWidth / 7, kWidth / 3, kWidth - 1}) {
for (unsigned int h : {0u, kHeight / 7, kHeight / 3, kHeight - 1}) {
TextureSpec textureSpec = defaultTextureSpec;
textureSpec.copyOrigin = {w, h, 0};
DoTest(textureSpec, pixelData, kCopySize);
}
}
}
// Test writing a pixel with an offset to a texture array
TEST_P(QueueWriteTextureTests, VaryingTextureArrayOffset) {
// TODO(crbug.com/dawn/2095): Failing on ANGLE + SwiftShader, needs investigation.
DAWN_SUPPRESS_TEST_IF(IsANGLESwiftShader());
constexpr uint32_t kWidth = 259;
constexpr uint32_t kHeight = 127;
constexpr uint32_t kDepth = 62;
DataSpec pixelData = MinimumDataSpec({1, 1, 1});
constexpr wgpu::Extent3D kCopySize = {1, 1, 1};
constexpr wgpu::Extent3D kTextureSize = {kWidth, kHeight, kDepth};
TextureSpec defaultTextureSpec;
defaultTextureSpec.textureSize = kTextureSize;
defaultTextureSpec.level = 0;
for (unsigned int w : {0u, kWidth / 7, kWidth / 3, kWidth - 1}) {
for (unsigned int h : {0u, kHeight / 7, kHeight / 3, kHeight - 1}) {
for (unsigned int d : {0u, kDepth / 7, kDepth / 3, kDepth - 1}) {
TextureSpec textureSpec = defaultTextureSpec;
textureSpec.copyOrigin = {w, h, d};
DoTest(textureSpec, pixelData, kCopySize);
}
}
}
}
// Test writing with varying write sizes.
TEST_P(QueueWriteTextureTests, VaryingWriteSize) {
constexpr uint32_t kWidth = 257;
constexpr uint32_t kHeight = 127;
for (unsigned int w : {13, 63, 128, 256}) {
for (unsigned int h : {16, 19, 32, 63}) {
TextureSpec textureSpec;
textureSpec.copyOrigin = {0, 0, 0};
textureSpec.level = 0;
textureSpec.textureSize = {kWidth, kHeight, 1};
DoTest(textureSpec, MinimumDataSpec({w, h, 1}), {w, h, 1});
}
}
}
// Test writing with varying write sizes to texture arrays.
TEST_P(QueueWriteTextureTests, VaryingArrayWriteSize) {
constexpr uint32_t kWidth = 257;
constexpr uint32_t kHeight = 127;
constexpr uint32_t kDepth = 65;
for (unsigned int w : {13, 63, 128, 256}) {
for (unsigned int h : {16, 19, 32, 63}) {
for (unsigned int d : {3, 6}) {
TextureSpec textureSpec;
textureSpec.copyOrigin = {0, 0, 0};
textureSpec.level = 0;
textureSpec.textureSize = {kWidth, kHeight, kDepth};
DoTest(textureSpec, MinimumDataSpec({w, h, d}), {w, h, d});
}
}
}
}
// Test writing to varying mips
TEST_P(QueueWriteTextureTests, TextureWriteToMip) {
constexpr uint32_t kWidth = 259;
constexpr uint32_t kHeight = 127;
TextureSpec defaultTextureSpec;
defaultTextureSpec.copyOrigin = {0, 0, 0};
defaultTextureSpec.textureSize = {kWidth, kHeight, 1};
for (unsigned int i = 1; i < 4; ++i) {
TextureSpec textureSpec = defaultTextureSpec;
textureSpec.level = i;
DoTest(textureSpec, MinimumDataSpec({kWidth >> i, kHeight >> i, 1}),
{kWidth >> i, kHeight >> i, 1});
}
}
// Test writing with different multiples of texel block size as data offset
TEST_P(QueueWriteTextureTests, VaryingDataOffset) {
constexpr uint32_t kWidth = 259;
constexpr uint32_t kHeight = 127;
TextureSpec textureSpec;
textureSpec.copyOrigin = {0, 0, 0};
textureSpec.textureSize = {kWidth, kHeight, 1};
textureSpec.level = 0;
for (uint64_t offset : {1, 2, 4, 17, 64, 128, 300}) {
DataSpec dataSpec = MinimumDataSpec({kWidth, kHeight, 1});
dataSpec.size += offset;
dataSpec.offset += offset;
DoTest(textureSpec, dataSpec, {kWidth, kHeight, 1});
}
}
// Test writing with rowsPerImage greater than needed.
TEST_P(QueueWriteTextureTests, VaryingRowsPerImage) {
constexpr uint32_t kWidth = 65;
constexpr uint32_t kHeight = 31;
constexpr uint32_t kDepth = 17;
constexpr wgpu::Extent3D copySize = {kWidth - 1, kHeight - 1, kDepth - 1};
for (unsigned int r : {1, 2, 3, 64, 200}) {
TextureSpec textureSpec;
textureSpec.copyOrigin = {1, 1, 1};
textureSpec.textureSize = {kWidth, kHeight, kDepth};
textureSpec.level = 0;
DataSpec dataSpec = MinimumDataSpec(copySize, kStrideComputeDefault, copySize.height + r);
DoTest(textureSpec, dataSpec, copySize);
}
}
// Test with bytesPerRow greater than needed
TEST_P(QueueWriteTextureTests, VaryingBytesPerRow) {
constexpr uint32_t kWidth = 257;
constexpr uint32_t kHeight = 129;
TextureSpec textureSpec;
textureSpec.textureSize = {kWidth, kHeight, 1};
textureSpec.copyOrigin = {1, 2, 0};
textureSpec.level = 0;
constexpr wgpu::Extent3D copyExtent = {17, 19, 1};
for (unsigned int b : {1, 2, 3, 4}) {
uint32_t bytesPerRow =
copyExtent.width * utils::GetTexelBlockSizeInBytes(GetParam().mTextureFormat) + b;
DoTest(textureSpec, MinimumDataSpec(copyExtent, bytesPerRow), copyExtent);
}
}
// Test that writing with bytesPerRow = 0 and bytesPerRow < bytesInACompleteRow works
// when we're copying one row only
TEST_P(QueueWriteTextureTests, BytesPerRowWithOneRowCopy) {
constexpr uint32_t kWidth = 259;
constexpr uint32_t kHeight = 127;
TextureSpec textureSpec;
textureSpec.copyOrigin = {0, 0, 0};
textureSpec.textureSize = {kWidth, kHeight, 1};
textureSpec.level = 0;
{
constexpr wgpu::Extent3D copyExtent = {5, 1, 1};
DataSpec dataSpec = MinimumDataSpec(copyExtent);
// bytesPerRow undefined
dataSpec.bytesPerRow = wgpu::kCopyStrideUndefined;
DoTest(textureSpec, dataSpec, copyExtent);
}
}
// Test with bytesPerRow greater than needed in a write to a texture array.
TEST_P(QueueWriteTextureTests, VaryingArrayBytesPerRow) {
// TODO(crbug.com/dawn/2095): Failing on ANGLE + SwiftShader, needs investigation.
DAWN_SUPPRESS_TEST_IF(IsANGLESwiftShader());
constexpr uint32_t kWidth = 257;
constexpr uint32_t kHeight = 129;
constexpr uint32_t kLayers = 65;
TextureSpec textureSpec;
textureSpec.textureSize = {kWidth, kHeight, kLayers};
textureSpec.copyOrigin = {1, 2, 3};
textureSpec.level = 0;
constexpr wgpu::Extent3D copyExtent = {17, 19, 21};
// Test with bytesPerRow divisible by blockWidth
for (unsigned int b : {1, 2, 3, 65, 300}) {
uint32_t bytesPerRow =
(copyExtent.width + b) * utils::GetTexelBlockSizeInBytes(GetParam().mTextureFormat);
uint32_t rowsPerImage = 23;
DoTest(textureSpec, MinimumDataSpec(copyExtent, bytesPerRow, rowsPerImage), copyExtent);
}
// Test with bytesPerRow not divisible by blockWidth
for (unsigned int b : {1, 2, 3, 19, 301}) {
uint32_t bytesPerRow =
copyExtent.width * utils::GetTexelBlockSizeInBytes(GetParam().mTextureFormat) + b;
uint32_t rowsPerImage = 23;
DoTest(textureSpec, MinimumDataSpec(copyExtent, bytesPerRow, rowsPerImage), copyExtent);
}
}
// Test valid special cases of bytesPerRow and rowsPerImage (0 or undefined).
TEST_P(QueueWriteTextureTests, StrideSpecialCases) {
TextureSpec textureSpec;
textureSpec.copyOrigin = {0, 0, 0};
textureSpec.textureSize = {4, 4, 4};
textureSpec.level = 0;
// bytesPerRow 0
for (const wgpu::Extent3D copyExtent :
{wgpu::Extent3D{0, 2, 2}, {0, 0, 2}, {0, 2, 0}, {0, 0, 0}}) {
DoTest(textureSpec, MinimumDataSpec(copyExtent, 0, 2), copyExtent);
}
// bytesPerRow undefined
for (const wgpu::Extent3D copyExtent :
{wgpu::Extent3D{2, 1, 1}, {2, 0, 1}, {2, 1, 0}, {2, 0, 0}}) {
DoTest(textureSpec, MinimumDataSpec(copyExtent, wgpu::kCopyStrideUndefined, 2), copyExtent);
}
// rowsPerImage 0
for (const wgpu::Extent3D copyExtent :
{wgpu::Extent3D{2, 0, 2}, {2, 0, 0}, {0, 0, 2}, {0, 0, 0}}) {
DoTest(textureSpec, MinimumDataSpec(copyExtent, 256, 0), copyExtent);
}
// rowsPerImage undefined
for (const wgpu::Extent3D copyExtent : {wgpu::Extent3D{2, 2, 1}, {2, 2, 0}}) {
DoTest(textureSpec, MinimumDataSpec(copyExtent, 256, wgpu::kCopyStrideUndefined),
copyExtent);
}
}
// Testing a special code path: writing when dynamic uploader already contatins some unaligned
// data, it might be necessary to use a ring buffer with properly aligned offset.
TEST_P(QueueWriteTextureTests, UnalignedDynamicUploader) {
utils::UnalignDynamicUploader(device);
constexpr wgpu::Extent3D size = {10, 10, 1};
TextureSpec textureSpec;
textureSpec.textureSize = size;
textureSpec.copyOrigin = {0, 0, 0};
textureSpec.level = 0;
DoTest(textureSpec, MinimumDataSpec(size), size);
}
// This tests for a bug that occurred within the D3D12 CopyTextureSplitter, which incorrectly copied
// data when the internal offset was larger than 256, but less than 512 and the copy size was 64
// width or less with a height of 1.
TEST_P(QueueWriteTextureTests, WriteTo64x1TextureFromUnalignedDynamicUploader) {
// First, WriteTexture with 96 pixels, or 384 bytes to create an offset in the dynamic uploader.
DoSimpleWriteTextureTest(96, 1);
// Now test writing to a 64x1 texture. Because a 64x1 texture's row pitch is equal to its slice
// pitch, the texture copy offset could be calculated incorrectly inside the internal D3D12
// TextureCopySplitter.
DoSimpleWriteTextureTest(64, 1);
}
// This tests for a bug in the allocation of internal staging buffer, which incorrectly copied depth
// stencil data to the internal offset that is not a multiple of 4.
TEST_P(QueueWriteTextureTests, WriteStencilAspectWithSourceOffsetUnalignedTo4) {
// Copies to a single aspect are unsupported on OpenGL.
DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES());
wgpu::TextureDescriptor textureDescriptor;
textureDescriptor.format = wgpu::TextureFormat::Depth24PlusStencil8;
textureDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
textureDescriptor.size = {1, 1, 1};
wgpu::Texture dstTexture1 = device.CreateTexture(&textureDescriptor);
wgpu::Texture dstTexture2 = device.CreateTexture(&textureDescriptor);
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = 8u;
bufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
wgpu::Buffer outputBuffer = device.CreateBuffer(&bufferDescriptor);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
constexpr wgpu::Extent3D kWriteSize = {1, 1, 1};
constexpr uint8_t kData[] = {1, 2};
constexpr uint32_t kBytesPerRowForWriteTexture = 1u;
std::vector<uint8_t> expectedData(8, 0);
// In the first call of queue.writeTexture(), Dawn will allocate a new staging buffer in its
// internal ring buffer and write the user data into it at the offset 0.
{
constexpr uint32_t kDataOffset1 = 0u;
wgpu::TextureDataLayout textureDataLayout =
utils::CreateTextureDataLayout(kDataOffset1, kBytesPerRowForWriteTexture);
wgpu::ImageCopyTexture imageCopyTexture = utils::CreateImageCopyTexture(
dstTexture1, 0, {0, 0, 0}, wgpu::TextureAspect::StencilOnly);
queue.WriteTexture(&imageCopyTexture, kData, sizeof(kData), &textureDataLayout,
&kWriteSize);
constexpr uint32_t kOutputBufferOffset1 = 0u;
wgpu::ImageCopyBuffer imageCopyBuffer = utils::CreateImageCopyBuffer(
outputBuffer, kOutputBufferOffset1, kTextureBytesPerRowAlignment);
encoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer, &kWriteSize);
expectedData[kOutputBufferOffset1] = kData[kDataOffset1];
}
// In the second call of queue.writeTexture(), Dawn will still use the same staging buffer
// allocated in the first call, whose first 2 bytes have been used in the first call of
// queue.writeTexture(). Dawn should write the user data at the offset 4 bytes since the
// destination texture aspect is stencil.
{
constexpr uint32_t kDataOffset2 = 1u;
wgpu::TextureDataLayout textureDataLayout =
utils::CreateTextureDataLayout(kDataOffset2, kBytesPerRowForWriteTexture);
wgpu::ImageCopyTexture imageCopyTexture = utils::CreateImageCopyTexture(
dstTexture2, 0, {0, 0, 0}, wgpu::TextureAspect::StencilOnly);
queue.WriteTexture(&imageCopyTexture, kData, sizeof(kData), &textureDataLayout,
&kWriteSize);
constexpr uint32_t kOutputBufferOffset2 = 4u;
wgpu::ImageCopyBuffer imageCopyBuffer = utils::CreateImageCopyBuffer(
outputBuffer, kOutputBufferOffset2, kTextureBytesPerRowAlignment);
encoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer, &kWriteSize);
expectedData[kOutputBufferOffset2] = kData[kDataOffset2];
}
wgpu::CommandBuffer commandBuffer = encoder.Finish();
queue.Submit(1, &commandBuffer);
EXPECT_BUFFER_U8_RANGE_EQ(expectedData.data(), outputBuffer, 0, 8);
}
// Tests calling queue.writeTexture() to a depth texture after calling queue.writeTexture() on
// another texture always works. On some D3D12 backends the buffer offset of buffer-to-texture
// copies must be a multiple of 512 when the destination texture is a depth stencil texture.
TEST_P(QueueWriteTextureTests, WriteDepthAspectAfterOtherQueueWriteTextureCalls) {
// Copies to a single aspect are unsupported on OpenGL.
DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES());
wgpu::TextureDescriptor textureDescriptor;
textureDescriptor.format = wgpu::TextureFormat::Depth16Unorm;
textureDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
textureDescriptor.size = {1, 1, 1};
wgpu::Texture depthTexture1 = device.CreateTexture(&textureDescriptor);
wgpu::Texture depthTexture2 = device.CreateTexture(&textureDescriptor);
constexpr uint16_t kExpectedData1 = (204 << 8) | 205;
wgpu::ImageCopyTexture imageCopyTexture1 = utils::CreateImageCopyTexture(depthTexture1);
wgpu::TextureDataLayout textureDataLayout =
utils::CreateTextureDataLayout(0, sizeof(kExpectedData1));
queue.WriteTexture(&imageCopyTexture1, &kExpectedData1, sizeof(kExpectedData1),
&textureDataLayout, &textureDescriptor.size);
constexpr uint16_t kExpectedData2 = (206 << 8) | 207;
wgpu::ImageCopyTexture imageCopyTexture2 = utils::CreateImageCopyTexture(depthTexture2);
queue.WriteTexture(&imageCopyTexture2, &kExpectedData2, sizeof(kExpectedData2),
&textureDataLayout, &textureDescriptor.size);
EXPECT_TEXTURE_EQ(&kExpectedData1, depthTexture1, {0, 0}, {1, 1}, 0,
wgpu::TextureAspect::DepthOnly);
EXPECT_TEXTURE_EQ(&kExpectedData2, depthTexture2, {0, 0}, {1, 1}, 0,
wgpu::TextureAspect::DepthOnly);
}
// Tests calling queue.writeTexture() to the stencil aspect after calling queue.writeTexture() on
// another texture always works. On some D3D12 backends the buffer offset of buffer-to-texture
// copies must be a multiple of 512 when the destination texture is a depth stencil texture.
TEST_P(QueueWriteTextureTests, WriteStencilAspectAfterOtherQueueWriteTextureCalls) {
// Copies to a single aspect are unsupported on OpenGL.
DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES());
wgpu::TextureDescriptor textureDescriptor;
textureDescriptor.format = wgpu::TextureFormat::Depth24PlusStencil8;
textureDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
textureDescriptor.size = {1, 1, 1};
wgpu::Texture depthStencilTexture1 = device.CreateTexture(&textureDescriptor);
wgpu::Texture depthStencilTexture2 = device.CreateTexture(&textureDescriptor);
constexpr uint8_t kExpectedData1 = 204u;
wgpu::ImageCopyTexture imageCopyTexture1 = utils::CreateImageCopyTexture(
depthStencilTexture1, 0, {0, 0, 0}, wgpu::TextureAspect::StencilOnly);
wgpu::TextureDataLayout textureDataLayout =
utils::CreateTextureDataLayout(0, sizeof(kExpectedData1));
queue.WriteTexture(&imageCopyTexture1, &kExpectedData1, sizeof(kExpectedData1),
&textureDataLayout, &textureDescriptor.size);
constexpr uint8_t kExpectedData2 = 205;
wgpu::ImageCopyTexture imageCopyTexture2 = utils::CreateImageCopyTexture(
depthStencilTexture2, 0, {0, 0, 0}, wgpu::TextureAspect::StencilOnly);
queue.WriteTexture(&imageCopyTexture2, &kExpectedData2, sizeof(kExpectedData2),
&textureDataLayout, &textureDescriptor.size);
EXPECT_TEXTURE_EQ(&kExpectedData1, depthStencilTexture1, {0, 0}, {1, 1}, 0,
wgpu::TextureAspect::StencilOnly);
EXPECT_TEXTURE_EQ(&kExpectedData2, depthStencilTexture2, {0, 0}, {1, 1}, 0,
wgpu::TextureAspect::StencilOnly);
}
DAWN_INSTANTIATE_TEST_P(QueueWriteTextureTests,
{D3D11Backend(), D3D12Backend(),
D3D12Backend({"d3d12_use_temp_buffer_in_depth_stencil_texture_and_buffer_"
"copy_with_non_zero_buffer_offset"}),
MetalBackend(),
MetalBackend({"use_blit_for_buffer_to_depth_texture_copy",
"use_blit_for_buffer_to_stencil_texture_copy"}),
OpenGLBackend(), OpenGLESBackend(), VulkanBackend()},
{
wgpu::TextureFormat::R8Unorm,
wgpu::TextureFormat::RG8Unorm,
wgpu::TextureFormat::RGBA8Unorm,
});
} // anonymous namespace
} // namespace dawn