Polish 2D texture copy splitter on D3D12

In every copy region, its bufferSize.height minus rowsPerImage
can be texture format's blockInfo.height (it is 1 for uncompressed
formats) at most, and it appears only if bytesPerRow is 256 and
copySize on height is a full copy.

This change will benefit 3D texture copy splitter via removing
unwanted empty rows issues. Because empty rows in 3D copy splitter
may lead to recompute/modify copy regions and there might be new
copy regions added.

The removed empty row situations are:
  1) Partial copy on height: copySize.height < rowsPerImage *
  blockInfo.height
  2) bytesPerRow is greater than 512. For example, if bytesPerRow
  is 512 and data in one row straddles two rows and there is no
  empty row at the first part. The second part will have a fake
  empty row if we don't recompute its alignedOffset.
  3) There are two empty rows in a copy region. For example:
  if data in one row straddles two rows and there is an empty row
  in the first copy region. Then there will be two empty rows in
  the copy region of the second part if we don't recompute the
  alignedOffset.

This change also fixes an issue found by Corentin that copy related
argument "rowsPerImage" should not take effect when we are copying
one single depth or array slice.

Bug: dawn:547

Change-Id: I603291d559de1d05e420e5ed1f4cabf53de5a93f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/52680
Reviewed-by: Corentin Wallez <cwallez@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Yunchao He <yunchao.he@intel.com>
This commit is contained in:
Yunchao He
2021-06-03 18:19:06 +00:00
committed by Dawn LUCI CQ
parent fec575ca7b
commit 9680c9f874
5 changed files with 144 additions and 35 deletions

View File

@@ -863,6 +863,47 @@ TEST_P(CopyTests_T2B, StrideSpecialCases) {
}
}
// Test copying a single slice with rowsPerImage larger than copy height and rowsPerImage will not
// take effect. If rowsPerImage takes effect, it looks like the copy may go past the end of the
// buffer.
TEST_P(CopyTests_T2B, RowsPerImageShouldNotCauseBufferOOBIfDepthOrArrayLayersIsOne) {
// Check various offsets to cover each code path in the 2D split code in TextureCopySplitter.
for (uint32_t offset : {0, 4, 64}) {
constexpr uint32_t kWidth = 250;
constexpr uint32_t kHeight = 3;
TextureSpec textureSpec;
textureSpec.textureSize = {kWidth, kHeight, 1};
BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight);
bufferSpec.rowsPerImage = 2 * kHeight;
bufferSpec.offset = offset;
bufferSpec.size += offset;
DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1});
DoTest(textureSpec, bufferSpec, {kWidth, kHeight, 1}, wgpu::TextureDimension::e3D);
}
}
// Test copying a single row with bytesPerRow larger than copy width and bytesPerRow will not
// take effect. If bytesPerRow takes effect, it looks like the copy may go past the end of the
// buffer.
TEST_P(CopyTests_T2B, BytesPerRowShouldNotCauseBufferOOBIfCopyHeightIsOne) {
// Check various offsets to cover each code path in the 2D split code in TextureCopySplitter.
for (uint32_t offset : {0, 4, 100}) {
constexpr uint32_t kWidth = 250;
TextureSpec textureSpec;
textureSpec.textureSize = {kWidth, 1, 1};
BufferSpec bufferSpec = MinimumBufferSpec(kWidth, 1);
bufferSpec.bytesPerRow = 1280; // the default bytesPerRow is 1024.
bufferSpec.offset = offset;
bufferSpec.size += offset;
DoTest(textureSpec, bufferSpec, {kWidth, 1, 1});
DoTest(textureSpec, bufferSpec, {kWidth, 1, 1}, wgpu::TextureDimension::e3D);
}
}
// Test that copying whole texture 2D array layers in one texture-to-buffer-copy works.
TEST_P(CopyTests_T2B, Texture2DArrayFull) {
constexpr uint32_t kWidth = 256;

View File

@@ -20,6 +20,7 @@
#include "dawn_native/Format.h"
#include "dawn_native/d3d12/TextureCopySplitter.h"
#include "dawn_native/d3d12/d3d12_platform.h"
#include "utils/TestUtils.h"
using namespace dawn_native::d3d12;
@@ -31,7 +32,7 @@ namespace {
uint32_t z;
uint32_t width;
uint32_t height;
uint32_t depth;
uint32_t depthOrArrayLayers;
uint32_t texelBlockSizeInBytes;
uint32_t blockWidth = 1;
uint32_t blockHeight = 1;
@@ -44,13 +45,50 @@ namespace {
};
// Check that each copy region fits inside the buffer footprint
void ValidateFootprints(const TextureCopySubresource& copySplit) {
void ValidateFootprints(const TextureSpec& textureSpec,
const BufferSpec& bufferSpec,
const TextureCopySubresource& copySplit) {
for (uint32_t i = 0; i < copySplit.count; ++i) {
const auto& copy = copySplit.copies[i];
ASSERT_LE(copy.bufferOffset.x + copy.copySize.width, copy.bufferSize.width);
ASSERT_LE(copy.bufferOffset.y + copy.copySize.height, copy.bufferSize.height);
ASSERT_LE(copy.bufferOffset.z + copy.copySize.depthOrArrayLayers,
copy.bufferSize.depthOrArrayLayers);
// If there are multiple layers, 2D texture splitter actually splits each layer
// independently. See the details in Compute2DTextureCopySplits(). As a result,
// if we simply expand a copy region generated by 2D texture splitter to all
// layers, the copy region might be OOB. But that is not the approach that the current
// 2D texture splitter is doing, although Compute2DTextureCopySubresource forwards
// "copySize.depthOrArrayLayers" to the copy region it generated. So skip the test
// below for 2D textures with multiple layers.
// TODO(yunchao.he@intel.com): add the test below for 3D texture with multiple slices.
if (textureSpec.depthOrArrayLayers <= 1) {
uint32_t widthInBlocks = textureSpec.width / textureSpec.blockWidth;
uint32_t heightInBlocks = textureSpec.height / textureSpec.blockHeight;
uint64_t minimumRequiredBufferSize =
bufferSpec.offset +
utils::RequiredBytesInCopy(bufferSpec.bytesPerRow, bufferSpec.rowsPerImage,
widthInBlocks, heightInBlocks,
textureSpec.depthOrArrayLayers,
textureSpec.texelBlockSizeInBytes);
ASSERT(copy.bufferSize.width % textureSpec.blockWidth == 0);
uint32_t widthInBlocksForFootprint = copy.bufferSize.width / textureSpec.blockWidth;
ASSERT(copy.bufferSize.height % textureSpec.blockHeight == 0);
uint32_t heightInBlocksForFootprint =
copy.bufferSize.height / textureSpec.blockHeight;
uint64_t bufferSizeForFootprint =
copy.alignedOffset + utils::RequiredBytesInCopy(
bufferSpec.bytesPerRow, bufferSpec.rowsPerImage,
widthInBlocksForFootprint, heightInBlocksForFootprint,
copy.bufferSize.depthOrArrayLayers,
textureSpec.texelBlockSizeInBytes);
// The buffer footprint for copy should not exceed the minimum required buffer
// size. Otherwise, pixels accessed by copy may be OOB.
ASSERT_LE(bufferSizeForFootprint, minimumRequiredBufferSize);
}
}
}
@@ -115,7 +153,7 @@ namespace {
ASSERT_EQ(minZ, textureSpec.z);
ASSERT_EQ(maxX, textureSpec.x + textureSpec.width);
ASSERT_EQ(maxY, textureSpec.y + textureSpec.height);
ASSERT_EQ(maxZ, textureSpec.z + textureSpec.depth);
ASSERT_EQ(maxZ, textureSpec.z + textureSpec.depthOrArrayLayers);
}
// Validate that the number of pixels copied is exactly equal to the number of pixels in the
@@ -127,7 +165,7 @@ namespace {
const auto& copy = copySplit.copies[i];
count += copy.copySize.width * copy.copySize.height * copy.copySize.depthOrArrayLayers;
}
ASSERT_EQ(count, textureSpec.width * textureSpec.height * textureSpec.depth);
ASSERT_EQ(count, textureSpec.width * textureSpec.height * textureSpec.depthOrArrayLayers);
}
// Check that every buffer offset is at the correct pixel location
@@ -149,6 +187,7 @@ namespace {
copy.bufferOffset.x / textureSpec.blockWidth * texelsPerBlock +
copy.bufferOffset.y / textureSpec.blockHeight * bytesPerRowInTexels;
ASSERT_LE(copy.bufferOffset.y, textureSpec.blockHeight);
ASSERT_EQ(copy.bufferOffset.z, 0u);
ASSERT(absoluteTexelOffset >=
@@ -170,7 +209,7 @@ namespace {
void ValidateCopySplit(const TextureSpec& textureSpec,
const BufferSpec& bufferSpec,
const TextureCopySubresource& copySplit) {
ValidateFootprints(copySplit);
ValidateFootprints(textureSpec, bufferSpec, copySplit);
ValidateOffset(copySplit);
ValidateDisjoint(copySplit);
ValidateTextureBounds(textureSpec, copySplit);
@@ -181,8 +220,8 @@ namespace {
std::ostream& operator<<(std::ostream& os, const TextureSpec& textureSpec) {
os << "TextureSpec("
<< "[(" << textureSpec.x << ", " << textureSpec.y << ", " << textureSpec.z << "), ("
<< textureSpec.width << ", " << textureSpec.height << ", " << textureSpec.depth << ")], "
<< textureSpec.texelBlockSizeInBytes << ")";
<< textureSpec.width << ", " << textureSpec.height << ", "
<< textureSpec.depthOrArrayLayers << ")], " << textureSpec.texelBlockSizeInBytes << ")";
return os;
}
@@ -212,10 +251,19 @@ namespace {
constexpr TextureSpec kBaseTextureSpecs[] = {
{0, 0, 0, 1, 1, 1, 4},
{0, 0, 0, 64, 1, 1, 4},
{0, 0, 0, 128, 1, 1, 4},
{0, 0, 0, 192, 1, 1, 4},
{31, 16, 0, 1, 1, 1, 4},
{64, 16, 0, 1, 1, 1, 4},
{64, 16, 8, 1, 1, 1, 4},
{0, 0, 0, 64, 2, 1, 4},
{0, 0, 0, 64, 2, 2, 4},
{0, 0, 0, 128, 2, 1, 4},
{0, 0, 0, 128, 2, 2, 4},
{0, 0, 0, 192, 2, 1, 4},
{0, 0, 0, 192, 2, 2, 4},
{0, 0, 0, 1024, 1024, 1, 4},
{256, 512, 0, 1024, 1024, 1, 4},
{64, 48, 0, 1024, 1024, 1, 4},
@@ -246,7 +294,7 @@ namespace {
// Define base buffer sizes to work with: some offsets aligned, some unaligned. bytesPerRow is
// the minimum required
std::array<BufferSpec, 14> BaseBufferSpecs(const TextureSpec& textureSpec) {
std::array<BufferSpec, 15> BaseBufferSpecs(const TextureSpec& textureSpec) {
uint32_t bytesPerRow = Align(textureSpec.texelBlockSizeInBytes * textureSpec.width,
kTextureBytesPerRowAlignment);
@@ -257,6 +305,8 @@ namespace {
return {
BufferSpec{alignNonPow2(0, textureSpec.texelBlockSizeInBytes), bytesPerRow,
textureSpec.height},
BufferSpec{alignNonPow2(256, textureSpec.texelBlockSizeInBytes), bytesPerRow,
textureSpec.height},
BufferSpec{alignNonPow2(512, textureSpec.texelBlockSizeInBytes), bytesPerRow,
textureSpec.height},
BufferSpec{alignNonPow2(1024, textureSpec.texelBlockSizeInBytes), bytesPerRow,
@@ -307,8 +357,8 @@ class CopySplitTest : public testing::Test {
blockInfo.byteSize = textureSpec.texelBlockSizeInBytes;
TextureCopySubresource copySplit = Compute2DTextureCopySubresource(
{textureSpec.x, textureSpec.y, textureSpec.z},
{textureSpec.width, textureSpec.height, textureSpec.depth}, blockInfo,
bufferSpec.offset, bufferSpec.bytesPerRow, bufferSpec.rowsPerImage);
{textureSpec.width, textureSpec.height, textureSpec.depthOrArrayLayers}, blockInfo,
bufferSpec.offset, bufferSpec.bytesPerRow);
ValidateCopySplit(textureSpec, bufferSpec, copySplit);
return copySplit;
}