D3D12: Add copy splitter unit tests

This commit is contained in:
Austin Eng 2017-07-20 15:40:12 -04:00 committed by Austin Eng
parent 0506138567
commit 04499576d9
2 changed files with 378 additions and 1 deletions

View File

@ -29,7 +29,7 @@ set(UNITTESTS_DIR ${TESTS_DIR}/unittests)
set(VALIDATION_TESTS_DIR ${UNITTESTS_DIR}/validation)
set(END2END_TESTS_DIR ${TESTS_DIR}/end2end)
add_executable(nxt_unittests
list(APPEND UNITTEST_SOURCES
${UNITTESTS_DIR}/BitSetIteratorTests.cpp
${UNITTESTS_DIR}/CommandAllocatorTests.cpp
${UNITTESTS_DIR}/EnumClassBitmasksTests.cpp
@ -54,6 +54,14 @@ add_executable(nxt_unittests
${VALIDATION_TESTS_DIR}/ValidationTest.h
${TESTS_DIR}/UnittestsMain.cpp
)
if (NXT_ENABLE_D3D12)
list(APPEND UNITTEST_SOURCES
${UNITTESTS_DIR}/d3d12/CopySplitTests.cpp
)
endif()
add_executable(nxt_unittests ${UNITTEST_SOURCES})
target_link_libraries(nxt_unittests nxt_common gtest nxt_backend mock_nxt nxt_wire utils)
NXTInternalTarget("tests" nxt_unittests)

View File

@ -0,0 +1,369 @@
// Copyright 2017 The NXT Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include "backend/d3d12/d3d12_platform.h"
#include "backend/d3d12/TextureCopySplitter.h"
#include "common/Assert.h"
#include "common/Constants.h"
#include "common/Math.h"
using namespace backend::d3d12;
namespace {
struct TextureSpec {
uint32_t x;
uint32_t y;
uint32_t z;
uint32_t width;
uint32_t height;
uint32_t depth;
uint32_t texelSize;
};
struct BufferSpec {
uint32_t offset;
uint32_t rowPitch;
};
// Check that each copy region fits inside the buffer footprint
void ValidateFootprints(const TextureCopySplit& 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.depth, copy.bufferSize.depth);
}
}
// Check that the offset is aligned
void ValidateOffset(const TextureCopySplit& copySplit) {
ASSERT_TRUE(Align(copySplit.offset, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT) == copySplit.offset);
}
bool RangesOverlap(uint32_t minA, uint32_t maxA, uint32_t minB, uint32_t maxB) {
return (minA < minB && minB <= maxA) || (minB < minA && minA <= maxB);
}
// Check that no pair of copy regions intersect each other
void ValidateDisjoint(const TextureCopySplit& copySplit) {
for (uint32_t i = 0; i < copySplit.count; ++i) {
const auto& a = copySplit.copies[i];
for (uint32_t j = i + 1; j < copySplit.count; ++j) {
const auto& b = copySplit.copies[j];
bool overlapX = RangesOverlap(a.textureOffset.x, a.textureOffset.x + a.copySize.width, b.textureOffset.x, b.textureOffset.x + b.copySize.width);
bool overlapY = RangesOverlap(a.textureOffset.y, a.textureOffset.y + a.copySize.height, b.textureOffset.y, b.textureOffset.y + b.copySize.height);
bool overlapZ = RangesOverlap(a.textureOffset.z, a.textureOffset.z + a.copySize.depth, b.textureOffset.z, b.textureOffset.z + b.copySize.depth);
ASSERT_TRUE(!overlapX || !overlapY || !overlapZ);
}
}
}
// Check that the union of the copy regions exactly covers the texture region
void ValidateTextureBounds(const TextureSpec& textureSpec, const TextureCopySplit& copySplit) {
ASSERT_TRUE(copySplit.count > 0);
uint32_t minX = copySplit.copies[0].textureOffset.x;
uint32_t minY = copySplit.copies[0].textureOffset.y;
uint32_t minZ = copySplit.copies[0].textureOffset.z;
uint32_t maxX = copySplit.copies[0].textureOffset.x + copySplit.copies[0].copySize.width;
uint32_t maxY = copySplit.copies[0].textureOffset.y + copySplit.copies[0].copySize.height;
uint32_t maxZ = copySplit.copies[0].textureOffset.z + copySplit.copies[0].copySize.depth;
for (uint32_t i = 1; i < copySplit.count; ++i) {
const auto& copy = copySplit.copies[i];
minX = std::min(minX, copy.textureOffset.x);
minY = std::min(minY, copy.textureOffset.y);
minZ = std::min(minZ, copy.textureOffset.z);
maxX = std::max(maxX, copy.textureOffset.x + copy.copySize.width);
maxY = std::max(maxY, copy.textureOffset.y + copy.copySize.height);
maxZ = std::max(maxZ, copy.textureOffset.z + copy.copySize.depth);
}
ASSERT_EQ(minX, textureSpec.x);
ASSERT_EQ(minY, textureSpec.y);
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);
}
// Validate that the number of pixels copied is exactly equal to the number of pixels in the texture region
void ValidatePixelCount(const TextureSpec& textureSpec, const TextureCopySplit& copySplit) {
uint32_t count = 0;
for (uint32_t i = 0; i < copySplit.count; ++i) {
const auto& copy = copySplit.copies[i];
count += copy.copySize.width * copy.copySize.height * copy.copySize.depth;
}
ASSERT_EQ(count, textureSpec.width * textureSpec.height * textureSpec.depth);
}
// Check that every buffer offset is at the correct pixel location
void ValidateBufferOffset(const TextureSpec& textureSpec, const BufferSpec& bufferSpec, const TextureCopySplit& copySplit) {
ASSERT_TRUE(copySplit.count > 0);
for (uint32_t i = 0; i < copySplit.count; ++i) {
const auto& copy = copySplit.copies[i];
uint32_t rowPitchInTexels = bufferSpec.rowPitch / textureSpec.texelSize;
uint32_t slicePitchInTexels = rowPitchInTexels * copy.copySize.height;
uint32_t absoluteTexelOffset = copySplit.offset / textureSpec.texelSize + copy.bufferOffset.x + copy.bufferOffset.y * rowPitchInTexels + copy.bufferOffset.z * slicePitchInTexels;
ASSERT(absoluteTexelOffset >= bufferSpec.offset / textureSpec.texelSize);
uint32_t relativeTexelOffset = absoluteTexelOffset - bufferSpec.offset / textureSpec.texelSize;
uint32_t z = relativeTexelOffset / slicePitchInTexels;
uint32_t y = (relativeTexelOffset % slicePitchInTexels) / rowPitchInTexels;
uint32_t x = relativeTexelOffset % rowPitchInTexels;
ASSERT_EQ(copy.textureOffset.x - textureSpec.x, x);
ASSERT_EQ(copy.textureOffset.y - textureSpec.y, y);
ASSERT_EQ(copy.textureOffset.z - textureSpec.z, z);
}
}
void ValidateCopySplit(const TextureSpec& textureSpec, const BufferSpec& bufferSpec, const TextureCopySplit& copySplit) {
ValidateFootprints(copySplit);
ValidateOffset(copySplit);
ValidateDisjoint(copySplit);
ValidateTextureBounds(textureSpec, copySplit);
ValidatePixelCount(textureSpec, copySplit);
ValidateBufferOffset(textureSpec, bufferSpec, copySplit);
}
std::ostream& operator<<(std::ostream& os, const TextureSpec& textureSpec) {
os << "TextureSpec("
<< "[(" << textureSpec.x << ", " << textureSpec.y << ", " << textureSpec.z << "), (" << textureSpec.width << ", " << textureSpec.height << ", " << textureSpec.depth << ")], "
<< textureSpec.texelSize
<< ")";
return os;
}
std::ostream& operator<<(std::ostream& os, const BufferSpec& bufferSpec) {
os << "BufferSpec(" << bufferSpec.offset << ", " << bufferSpec.rowPitch << ")";
return os;
}
std::ostream& operator<<(std::ostream& os, const TextureCopySplit& copySplit) {
os << "CopySplit" << std::endl;
for (uint32_t i = 0; i < copySplit.count; ++i) {
const auto& copy = copySplit.copies[i];
os << " " << i << ": Texture at (" << copy.textureOffset.x << ", " << copy.textureOffset.y << ", " << copy.textureOffset.z << "), size (" << copy.copySize.width << ", " << copy.copySize.height << ", " << copy.copySize.depth << ")" << std::endl;
os << " " << i << ": Buffer at (" << copy.bufferOffset.x << ", " << copy.bufferOffset.y << ", " << copy.bufferOffset.z << "), footprint (" << copy.bufferSize.width << ", " << copy.bufferSize.height << ", " << copy.bufferSize.depth << ")" << std::endl;
}
return os;
}
// Define base texture sizes and offsets to test with: some aligned, some unaligned
constexpr TextureSpec kBaseTextureSpecs[] = {
{ 0, 0, 0, 1, 1, 1, 4 },
{ 31, 16, 0, 1, 1, 1, 4 },
{ 64, 16, 0, 1, 1, 1, 4 },
{ 0, 0, 0, 1024, 1024, 1, 4 },
{ 256, 512, 0, 1024, 1024, 1, 4 },
{ 64, 48, 0, 1024, 1024, 1, 4 },
{ 0, 0, 0, 257, 31, 1, 4 },
{ 0, 0, 0, 17, 93, 1, 4 },
{ 59, 13, 0, 257, 31, 1, 4 },
{ 17, 73, 0, 17, 93, 1, 4 },
};
// Define base buffer sizes to work with: some offsets aligned, some unaligned. rowPitch is the minimum required
std::array<BufferSpec, 10> BaseBufferSpecs(const TextureSpec& textureSpec) {
uint32_t rowPitch = Align(textureSpec.texelSize * textureSpec.width, kTextureRowPitchAlignment);
auto alignNonPow2 = [](uint32_t value, uint32_t size) -> uint32_t {
return value == 0 ? 0 : ((value - 1) / size + 1) * size;
};
return {
alignNonPow2(0, textureSpec.texelSize), rowPitch,
alignNonPow2(512, textureSpec.texelSize), rowPitch,
alignNonPow2(1024, textureSpec.texelSize), rowPitch,
alignNonPow2(32, textureSpec.texelSize), rowPitch,
alignNonPow2(64, textureSpec.texelSize), rowPitch,
alignNonPow2(31, textureSpec.texelSize), rowPitch,
alignNonPow2(257, textureSpec.texelSize), rowPitch,
alignNonPow2(511, textureSpec.texelSize), rowPitch,
alignNonPow2(513, textureSpec.texelSize), rowPitch,
alignNonPow2(1023, textureSpec.texelSize), rowPitch,
};
}
// Define a list of values to set properties in the spec structs
constexpr uint32_t kCheckValues[] = {
1, 2, 3, 4, 5, 6, 7, 8, // small values
16, 32, 64, 128, 256, 512, 1024, 2048, // powers of 2
15, 31, 63, 127, 257, 511, 1023, 2047, // misalignments
17, 33, 65, 129, 257, 513, 1025, 2049
};
}
class CopySplitTest : public testing::Test {
protected:
TextureCopySplit DoTest(const TextureSpec& textureSpec, const BufferSpec& bufferSpec) {
TextureCopySplit copySplit = ComputeTextureCopySplit(textureSpec.x, textureSpec.y, textureSpec.z, textureSpec.width, textureSpec.height, textureSpec.depth, textureSpec.texelSize, bufferSpec.offset, bufferSpec.rowPitch);
ValidateCopySplit(textureSpec, bufferSpec, copySplit);
return copySplit;
}
};
TEST_F(CopySplitTest, General) {
for (TextureSpec textureSpec : kBaseTextureSpecs) {
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
if (HasFatalFailure()) {
std::ostringstream message;
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec << std::endl
<< copySplit << std::endl;
FAIL() << message.str();
}
}
}
}
TEST_F(CopySplitTest, TextureWidth) {
for (TextureSpec textureSpec : kBaseTextureSpecs) {
for (uint32_t val : kCheckValues) {
textureSpec.width = val;
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
if (HasFatalFailure()) {
std::ostringstream message;
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec << std::endl
<< copySplit << std::endl;
FAIL() << message.str();
}
}
}
}
}
TEST_F(CopySplitTest, TextureHeight) {
for (TextureSpec textureSpec : kBaseTextureSpecs) {
for (uint32_t val : kCheckValues) {
textureSpec.height = val;
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
if (HasFatalFailure()) {
std::ostringstream message;
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec << std::endl
<< copySplit << std::endl;
FAIL() << message.str();
}
}
}
}
}
TEST_F(CopySplitTest, TextureX) {
for (TextureSpec textureSpec : kBaseTextureSpecs) {
for (uint32_t val : kCheckValues) {
textureSpec.x = val;
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
if (HasFatalFailure()) {
std::ostringstream message;
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec << std::endl
<< copySplit << std::endl;
FAIL() << message.str();
}
}
}
}
}
TEST_F(CopySplitTest, TextureY) {
for (TextureSpec textureSpec : kBaseTextureSpecs) {
for (uint32_t val : kCheckValues) {
textureSpec.y = val;
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
if (HasFatalFailure()) {
std::ostringstream message;
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec << std::endl
<< copySplit << std::endl;
FAIL() << message.str();
}
}
}
}
}
TEST_F(CopySplitTest, TexelSize) {
for (TextureSpec textureSpec : kBaseTextureSpecs) {
for (uint32_t texelSize : {4, 8, 16, 32, 64}) {
textureSpec.texelSize = texelSize;
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
if (HasFatalFailure()) {
std::ostringstream message;
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec << std::endl
<< copySplit << std::endl;
FAIL() << message.str();
}
}
}
}
}
TEST_F(CopySplitTest, BufferOffset) {
for (TextureSpec textureSpec : kBaseTextureSpecs) {
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
for (uint32_t val : kCheckValues) {
bufferSpec.offset = textureSpec.texelSize * val;
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
if (HasFatalFailure()) {
std::ostringstream message;
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec << std::endl
<< copySplit << std::endl;
FAIL() << message.str();
}
}
}
}
}
TEST_F(CopySplitTest, RowPitch) {
for (TextureSpec textureSpec : kBaseTextureSpecs) {
for (BufferSpec bufferSpec : BaseBufferSpecs(textureSpec)) {
uint32_t baseRowPitch = bufferSpec.rowPitch;
for (uint32_t i = 0; i < 5; ++i) {
bufferSpec.rowPitch = baseRowPitch + i * 256;
TextureCopySplit copySplit = DoTest(textureSpec, bufferSpec);
if (HasFatalFailure()) {
std::ostringstream message;
message << "Failed generating splits: " << textureSpec << ", " << bufferSpec << std::endl
<< copySplit << std::endl;
FAIL() << message.str();
}
}
}
}
}