Implement Texture-to-Texture Copies

Implement texture-to-texture copies for D3D12, Vulkan, and Metal.
Includes end2end and unit tests.

Bug: dawn:18
Change-Id: Ib48453704599bee43a76af21e6164aa9b8db7075
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/5620
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
Brandon Jones
2019-03-26 11:06:23 +00:00
committed by Commit Bot service account
parent 041aca1620
commit d3d3aa03e1
13 changed files with 708 additions and 36 deletions

View File

@@ -40,6 +40,21 @@ class CopyTests : public DawnTest {
uint32_t rowPitch;
};
static void FillTextureData(uint32_t width,
uint32_t height,
uint32_t texelsPerRow,
uint32_t layer,
RGBA8* data) {
for (uint32_t y = 0; y < height; ++y) {
for (uint32_t x = 0; x < width; ++x) {
uint32_t i = x + y * texelsPerRow;
data[i] = RGBA8(static_cast<uint8_t>((x + layer * x) % 256),
static_cast<uint8_t>((y + layer * y) % 256),
static_cast<uint8_t>(x / 256), static_cast<uint8_t>(y / 256));
}
}
}
BufferSpec MinimumBufferSpec(uint32_t width, uint32_t height) {
uint32_t rowPitch = Align(width * kBytesPerTexel, kTextureRowPitchAlignment);
return { rowPitch * (height - 1) + width * kBytesPerTexel, 0, rowPitch };
@@ -58,18 +73,6 @@ class CopyTests : public DawnTest {
class CopyTests_T2B : public CopyTests {
protected:
static void FillTextureData(uint32_t width, uint32_t height, uint32_t texelsPerRow, RGBA8* data, uint32_t layer) {
for (unsigned int y = 0; y < height; ++y) {
for (unsigned int x = 0; x < width; ++x) {
unsigned int i = x + y * texelsPerRow;
data[i] = RGBA8(
static_cast<uint8_t>((x + layer * x)% 256),
static_cast<uint8_t>((y + layer * y)% 256),
static_cast<uint8_t>(x / 256),
static_cast<uint8_t>(y / 256));
}
}
}
void DoTest(const TextureSpec& textureSpec, const BufferSpec& bufferSpec) {
// Create a texture that is `width` x `height` with (`level` + 1) mip levels.
@@ -96,7 +99,8 @@ class CopyTests_T2B : public CopyTests {
std::vector<std::vector<RGBA8>> textureArrayData(textureSpec.arraySize);
for (uint32_t slice = 0; slice < textureSpec.arraySize; ++slice) {
textureArrayData[slice].resize(texelCountPerLayer);
FillTextureData(width, height, rowPitch / kBytesPerTexel, textureArrayData[slice].data(), slice);
FillTextureData(width, height, rowPitch / kBytesPerTexel, slice,
textureArrayData[slice].data());
// Create an upload buffer and use it to populate the current slice of the texture in `level` mip level
dawn::Buffer uploadBuffer = utils::CreateBufferFromData(device, textureArrayData[slice].data(),
@@ -245,6 +249,134 @@ protected:
};
class CopyTests_T2T : public CopyTests {
struct TextureSpec {
uint32_t width;
uint32_t height;
uint32_t x;
uint32_t y;
uint32_t level;
uint32_t arraySize = 1u;
};
struct CopySize {
uint32_t width;
uint32_t height;
};
protected:
void DoTest(const TextureSpec& srcSpec, const TextureSpec& dstSpec, const CopySize& copy) {
dawn::TextureDescriptor srcDescriptor;
srcDescriptor.dimension = dawn::TextureDimension::e2D;
srcDescriptor.size.width = srcSpec.width;
srcDescriptor.size.height = srcSpec.height;
srcDescriptor.size.depth = 1;
srcDescriptor.arrayLayerCount = srcSpec.arraySize;
srcDescriptor.sampleCount = 1;
srcDescriptor.format = dawn::TextureFormat::R8G8B8A8Unorm;
srcDescriptor.mipLevelCount = srcSpec.level + 1;
srcDescriptor.usage =
dawn::TextureUsageBit::TransferSrc | dawn::TextureUsageBit::TransferDst;
dawn::Texture srcTexture = device.CreateTexture(&srcDescriptor);
dawn::TextureDescriptor dstDescriptor;
dstDescriptor.dimension = dawn::TextureDimension::e2D;
dstDescriptor.size.width = dstSpec.width;
dstDescriptor.size.height = dstSpec.height;
dstDescriptor.size.depth = 1;
dstDescriptor.arrayLayerCount = dstSpec.arraySize;
dstDescriptor.sampleCount = 1;
dstDescriptor.format = dawn::TextureFormat::R8G8B8A8Unorm;
dstDescriptor.mipLevelCount = dstSpec.level + 1;
dstDescriptor.usage =
dawn::TextureUsageBit::TransferSrc | dawn::TextureUsageBit::TransferDst;
dawn::Texture dstTexture = device.CreateTexture(&dstDescriptor);
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
// Create an upload buffer and use it to populate the current slice of the texture in
// `level` mip level
uint32_t width = srcSpec.width >> srcSpec.level;
uint32_t height = srcSpec.height >> srcSpec.level;
uint32_t rowPitch = Align(kBytesPerTexel * width, kTextureRowPitchAlignment);
uint32_t texelsPerRow = rowPitch / kBytesPerTexel;
uint32_t texelCountPerLayer = texelsPerRow * (height - 1) + width;
std::vector<std::vector<RGBA8>> textureArrayData(srcSpec.arraySize);
for (uint32_t slice = 0; slice < srcSpec.arraySize; ++slice) {
textureArrayData[slice].resize(texelCountPerLayer);
FillTextureData(width, height, rowPitch / kBytesPerTexel, slice,
textureArrayData[slice].data());
dawn::Buffer uploadBuffer = utils::CreateBufferFromData(
device, textureArrayData[slice].data(),
static_cast<uint32_t>(sizeof(RGBA8) * textureArrayData[slice].size()),
dawn::BufferUsageBit::TransferSrc);
dawn::BufferCopyView bufferCopyView =
utils::CreateBufferCopyView(uploadBuffer, 0, rowPitch, 0);
dawn::TextureCopyView textureCopyView =
utils::CreateTextureCopyView(srcTexture, srcSpec.level, slice, {0, 0, 0});
dawn::Extent3D bufferCopySize = {width, height, 1};
encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &bufferCopySize);
}
// Create an upload buffer filled with empty data and use it to populate the `level` mip of
// the texture. Note: Prepopulating the texture with empty data ensures that there is not
// random data in the expectation and helps ensure that the padding due to the row pitch is
// not modified by the copy
{
uint32_t dstWidth = dstSpec.width >> dstSpec.level;
uint32_t dstHeight = dstSpec.height >> dstSpec.level;
uint32_t dstRowPitch = Align(kBytesPerTexel * dstWidth, kTextureRowPitchAlignment);
uint32_t dstTexelsPerRow = dstRowPitch / kBytesPerTexel;
uint32_t dstTexelCount = dstTexelsPerRow * (dstHeight - 1) + dstWidth;
std::vector<RGBA8> emptyData(dstTexelCount);
dawn::Buffer uploadBuffer = utils::CreateBufferFromData(
device, emptyData.data(), static_cast<uint32_t>(sizeof(RGBA8) * emptyData.size()),
dawn::BufferUsageBit::TransferSrc);
dawn::BufferCopyView bufferCopyView =
utils::CreateBufferCopyView(uploadBuffer, 0, dstRowPitch, 0);
dawn::TextureCopyView textureCopyView =
utils::CreateTextureCopyView(dstTexture, dstSpec.level, 0, {0, 0, 0});
dawn::Extent3D dstCopySize = {dstWidth, dstHeight, 1};
encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &dstCopySize);
}
// Perform the texture to texture copy
for (uint32_t slice = 0; slice < srcSpec.arraySize; ++slice) {
dawn::TextureCopyView srcTextureCopyView = utils::CreateTextureCopyView(
srcTexture, srcSpec.level, slice, {srcSpec.x, srcSpec.y, 0});
dawn::TextureCopyView dstTextureCopyView = utils::CreateTextureCopyView(
dstTexture, dstSpec.level, slice, {dstSpec.x, dstSpec.y, 0});
dawn::Extent3D copySize = {copy.width, copy.height, 1};
encoder.CopyTextureToTexture(&srcTextureCopyView, &dstTextureCopyView, &copySize);
}
dawn::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
std::vector<RGBA8> expected(rowPitch / kBytesPerTexel * (copy.height - 1) + copy.width);
for (uint32_t slice = 0; slice < srcSpec.arraySize; ++slice) {
std::fill(expected.begin(), expected.end(), RGBA8());
PackTextureData(
&textureArrayData[slice][srcSpec.x + srcSpec.y * (rowPitch / kBytesPerTexel)],
copy.width, copy.height, texelsPerRow, expected.data(), copy.width);
EXPECT_TEXTURE_RGBA8_EQ(expected.data(), dstTexture, dstSpec.x, dstSpec.y, copy.width,
copy.height, dstSpec.level, slice)
<< "Texture to Texture copy failed copying region [(" << srcSpec.x << ", "
<< srcSpec.y << "), (" << srcSpec.x + copy.width << ", " << srcSpec.y + copy.height
<< ")) from " << srcSpec.width << " x " << srcSpec.height
<< " texture at mip level " << srcSpec.level << " layer " << slice << " to [("
<< dstSpec.x << ", " << dstSpec.y << "), (" << dstSpec.x + copy.width << ", "
<< dstSpec.y + copy.height << ")) region of " << dstSpec.width << " x "
<< dstSpec.height << " texture at mip level " << dstSpec.level << std::endl;
}
}
};
// Test that copying an entire texture with 256-byte aligned dimensions works
TEST_P(CopyTests_T2B, FullTextureAligned) {
constexpr uint32_t kWidth = 256;
@@ -551,3 +683,51 @@ TEST_P(CopyTests_B2T, RowPitchUnaligned) {
}
DAWN_INSTANTIATE_TEST(CopyTests_B2T, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend);
TEST_P(CopyTests_T2T, Texture) {
constexpr uint32_t kWidth = 256;
constexpr uint32_t kHeight = 128;
DoTest({kWidth, kHeight, 0, 0, 0}, {kWidth, kHeight, 0, 0, 0}, {kWidth, kHeight});
}
TEST_P(CopyTests_T2T, TextureRegion) {
constexpr uint32_t kWidth = 256;
constexpr uint32_t kHeight = 128;
for (unsigned int w : {64, 128, 256}) {
for (unsigned int h : {16, 32, 48}) {
DoTest({kWidth, kHeight, 0, 0, 0, 1}, {kWidth, kHeight, 0, 0, 0, 1}, {w, h});
}
}
}
TEST_P(CopyTests_T2T, Texture2DArray) {
constexpr uint32_t kWidth = 256;
constexpr uint32_t kHeight = 128;
constexpr uint32_t kLayers = 6u;
DoTest({kWidth, kHeight, 0, 0, 0, kLayers}, {kWidth, kHeight, 0, 0, 0, kLayers},
{kWidth, kHeight});
}
TEST_P(CopyTests_T2T, Texture2DArrayRegion) {
constexpr uint32_t kWidth = 256;
constexpr uint32_t kHeight = 128;
constexpr uint32_t kLayers = 6u;
for (unsigned int w : {64, 128, 256}) {
for (unsigned int h : {16, 32, 48}) {
DoTest({kWidth, kHeight, 0, 0, 0, kLayers}, {kWidth, kHeight, 0, 0, 0, kLayers},
{w, h});
}
}
}
TEST_P(CopyTests_T2T, TextureMip) {
constexpr uint32_t kWidth = 256;
constexpr uint32_t kHeight = 128;
for (unsigned int i = 1; i < 4; ++i) {
DoTest({kWidth, kHeight, 0, 0, i}, {kWidth, kHeight, 0, 0, i}, {kWidth >> i, kHeight >> i});
}
}
// TODO(brandon1.jones@intel.com) Add test for ensuring blitCommandEncoder on Metal.
DAWN_INSTANTIATE_TEST(CopyTests_T2T, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend);

View File

@@ -49,6 +49,14 @@ class CopyCommandTest : public ValidationTest {
return (rowPitch * (height - 1) + width) * depth;
}
void ValidateExpectation(dawn::CommandEncoder encoder, utils::Expectation expectation) {
if (expectation == utils::Expectation::Success) {
encoder.Finish();
} else {
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
void TestB2TCopy(utils::Expectation expectation,
dawn::Buffer srcBuffer,
uint32_t srcOffset,
@@ -67,11 +75,7 @@ class CopyCommandTest : public ValidationTest {
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, &extent3D);
if (expectation == utils::Expectation::Success) {
encoder.Finish();
} else {
ASSERT_DEVICE_ERROR(encoder.Finish());
}
ValidateExpectation(encoder, expectation);
}
void TestT2BCopy(utils::Expectation expectation,
@@ -92,11 +96,28 @@ class CopyCommandTest : public ValidationTest {
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToBuffer(&textureCopyView, &bufferCopyView, &extent3D);
if (expectation == utils::Expectation::Success) {
encoder.Finish();
} else {
ASSERT_DEVICE_ERROR(encoder.Finish());
}
ValidateExpectation(encoder, expectation);
}
void TestT2TCopy(utils::Expectation expectation,
dawn::Texture srcTexture,
uint32_t srcLevel,
uint32_t srcSlice,
dawn::Origin3D srcOrigin,
dawn::Texture dstTexture,
uint32_t dstLevel,
uint32_t dstSlice,
dawn::Origin3D dstOrigin,
dawn::Extent3D extent3D) {
dawn::TextureCopyView srcTextureCopyView =
utils::CreateTextureCopyView(srcTexture, srcLevel, srcSlice, srcOrigin);
dawn::TextureCopyView dstTextureCopyView =
utils::CreateTextureCopyView(dstTexture, dstLevel, dstSlice, dstOrigin);
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToTexture(&srcTextureCopyView, &dstTextureCopyView, &extent3D);
ValidateExpectation(encoder, expectation);
}
};
@@ -738,3 +759,193 @@ TEST_F(CopyCommandTest_T2B, BufferOrTextureInErrorState) {
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
class CopyCommandTest_T2T : public CopyCommandTest {};
TEST_F(CopyCommandTest_T2T, Success) {
dawn::Texture source = Create2DTexture(16, 16, 5, 2, dawn::TextureFormat::R8G8B8A8Unorm,
dawn::TextureUsageBit::TransferSrc);
dawn::Texture destination = Create2DTexture(16, 16, 5, 2, dawn::TextureFormat::R8G8B8A8Unorm,
dawn::TextureUsageBit::TransferDst);
// Different copies, including some that touch the OOB condition
{
// Copy a region along top left boundary
TestT2TCopy(utils::Expectation::Success, source, 0, 0, {0, 0, 0}, destination, 0, 0,
{0, 0, 0}, {4, 4, 1});
// Copy entire texture
TestT2TCopy(utils::Expectation::Success, source, 0, 0, {0, 0, 0}, destination, 0, 0,
{0, 0, 0}, {16, 16, 1});
// Copy a region along bottom right boundary
TestT2TCopy(utils::Expectation::Success, source, 0, 0, {8, 8, 0}, destination, 0, 0,
{8, 8, 0}, {8, 8, 1});
// Copy region into mip
TestT2TCopy(utils::Expectation::Success, source, 0, 0, {0, 0, 0}, destination, 2, 0,
{0, 0, 0}, {4, 4, 1});
// Copy mip into region
TestT2TCopy(utils::Expectation::Success, source, 2, 0, {0, 0, 0}, destination, 0, 0,
{0, 0, 0}, {4, 4, 1});
// Copy between slices
TestT2TCopy(utils::Expectation::Success, source, 0, 1, {0, 0, 0}, destination, 0, 1,
{0, 0, 0}, {16, 16, 1});
}
// Empty copies are valid
{
// An empty copy
TestT2TCopy(utils::Expectation::Success, source, 0, 0, {0, 0, 0}, destination, 0, 0,
{0, 0, 0}, {0, 0, 1});
// An empty copy touching the side of the source texture
TestT2TCopy(utils::Expectation::Success, source, 0, 0, {0, 0, 0}, destination, 0, 0,
{16, 16, 0}, {0, 0, 1});
// An empty copy touching the side of the destination texture
TestT2TCopy(utils::Expectation::Success, source, 0, 0, {0, 0, 0}, destination, 0, 0,
{16, 16, 0}, {0, 0, 1});
}
}
TEST_F(CopyCommandTest_T2T, IncorrectUsage) {
dawn::Texture source = Create2DTexture(16, 16, 5, 2, dawn::TextureFormat::R8G8B8A8Unorm,
dawn::TextureUsageBit::TransferSrc);
dawn::Texture destination = Create2DTexture(16, 16, 5, 2, dawn::TextureFormat::R8G8B8A8Unorm,
dawn::TextureUsageBit::TransferDst);
// Incorrect source usage causes failure
TestT2TCopy(utils::Expectation::Failure, destination, 0, 0, {0, 0, 0}, destination, 0, 0,
{0, 0, 0}, {16, 16, 1});
// Incorrect destination usage causes failure
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, source, 0, 0, {0, 0, 0},
{16, 16, 1});
}
TEST_F(CopyCommandTest_T2T, OutOfBounds) {
dawn::Texture source = Create2DTexture(16, 16, 5, 2, dawn::TextureFormat::R8G8B8A8Unorm,
dawn::TextureUsageBit::TransferSrc);
dawn::Texture destination = Create2DTexture(16, 16, 5, 2, dawn::TextureFormat::R8G8B8A8Unorm,
dawn::TextureUsageBit::TransferDst);
// OOB on source
{
// x + width overflows
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {1, 0, 0}, destination, 0, 0,
{0, 0, 0}, {16, 16, 1});
// y + height overflows
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 1, 0}, destination, 0, 0,
{0, 0, 0}, {16, 16, 1});
// non-zero mip overflows
TestT2TCopy(utils::Expectation::Failure, source, 1, 0, {0, 0, 0}, destination, 0, 0,
{0, 0, 0}, {9, 9, 1});
// empty copy on non-existent mip fails
TestT2TCopy(utils::Expectation::Failure, source, 6, 0, {0, 0, 0}, destination, 0, 0,
{0, 0, 0}, {0, 0, 1});
// empty copy from non-existent slice fails
TestT2TCopy(utils::Expectation::Failure, source, 0, 2, {0, 0, 0}, destination, 0, 0,
{0, 0, 0}, {0, 0, 1});
}
// OOB on destination
{
// x + width overflows
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 0, 0,
{1, 0, 0}, {16, 16, 1});
// y + height overflows
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 0, 0,
{0, 1, 0}, {16, 16, 1});
// non-zero mip overflows
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 1, 0,
{0, 0, 0}, {9, 9, 1});
// empty copy on non-existent mip fails
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 6, 0,
{0, 0, 0}, {0, 0, 1});
// empty copy on non-existent slice fails
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 0, 2,
{0, 0, 0}, {0, 0, 1});
}
}
TEST_F(CopyCommandTest_T2T, 2DTextureDepthConstraints) {
dawn::Texture source = Create2DTexture(16, 16, 5, 2, dawn::TextureFormat::R8G8B8A8Unorm,
dawn::TextureUsageBit::TransferSrc);
dawn::Texture destination = Create2DTexture(16, 16, 5, 2, dawn::TextureFormat::R8G8B8A8Unorm,
dawn::TextureUsageBit::TransferDst);
// Empty copy on source with z > 0 fails
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 1}, destination, 0, 0, {0, 0, 0},
{0, 0, 1});
// Empty copy on destination with z > 0 fails
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 0, 0, {0, 0, 1},
{0, 0, 1});
// Empty copy with depth = 0 fails
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 0, 0, {0, 0, 0},
{0, 0, 0});
}
TEST_F(CopyCommandTest_T2T, 2DTextureDepthStencil) {
dawn::Texture source = Create2DTexture(16, 16, 1, 1, dawn::TextureFormat::D32FloatS8Uint,
dawn::TextureUsageBit::TransferSrc);
dawn::Texture destination = Create2DTexture(16, 16, 1, 1, dawn::TextureFormat::D32FloatS8Uint,
dawn::TextureUsageBit::TransferDst);
// Success when entire depth stencil subresource is copied
TestT2TCopy(utils::Expectation::Success, source, 0, 0, {0, 0, 0}, destination, 0, 0, {0, 0, 0},
{16, 16, 1});
// Failure when depth stencil subresource is partially copied
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 0, 0, {0, 0, 0},
{15, 15, 1});
}
TEST_F(CopyCommandTest_T2T, FormatsMismatch) {
dawn::Texture source = Create2DTexture(16, 16, 5, 2, dawn::TextureFormat::R8G8B8A8Uint,
dawn::TextureUsageBit::TransferSrc);
dawn::Texture destination = Create2DTexture(16, 16, 5, 2, dawn::TextureFormat::R8G8B8A8Unorm,
dawn::TextureUsageBit::TransferDst);
// Failure when formats don't match
TestT2TCopy(utils::Expectation::Failure, source, 0, 0, {0, 0, 0}, destination, 0, 0, {0, 0, 0},
{0, 0, 1});
}
TEST_F(CopyCommandTest_T2T, MultisampledCopies) {
dawn::Texture sourceMultiSampled1x = Create2DTexture(
16, 16, 1, 1, dawn::TextureFormat::R8G8B8A8Unorm, dawn::TextureUsageBit::TransferSrc, 1);
dawn::Texture sourceMultiSampled4x = Create2DTexture(
16, 16, 1, 1, dawn::TextureFormat::R8G8B8A8Unorm, dawn::TextureUsageBit::TransferSrc, 4);
dawn::Texture destinationMultiSampled4x = Create2DTexture(
16, 16, 1, 1, dawn::TextureFormat::R8G8B8A8Unorm, dawn::TextureUsageBit::TransferDst, 4);
// Success when entire multisampled subresource is copied
{
TestT2TCopy(utils::Expectation::Success, sourceMultiSampled4x, 0, 0, {0, 0, 0},
destinationMultiSampled4x, 0, 0, {0, 0, 0}, {16, 16, 1});
}
// Failures
{
// An empty copy with mismatched samples fails
TestT2TCopy(utils::Expectation::Failure, sourceMultiSampled1x, 0, 0, {0, 0, 0},
destinationMultiSampled4x, 0, 0, {0, 0, 0}, {0, 0, 1});
// A copy fails when samples are greater than 1, and entire subresource isn't copied
TestT2TCopy(utils::Expectation::Failure, sourceMultiSampled4x, 0, 0, {0, 0, 0},
destinationMultiSampled4x, 0, 0, {0, 0, 0}, {15, 15, 1});
}
}