dawn-cmake/src/tests/unittests/validation/QueueWriteTextureValidationTests.cpp
Austin Eng f114a68b8f Update depth-stencil copy validation
Depth/stencil copies of partial subresources is disallowed in
WebGPU because this is a D3D12 restriction. This restriction
need also to be enforced on B2T, T2B and WriteTexture.

This CL also fixes the subresource whole size calucation to use
the mip level. Previously, the 0th level size was always used.

This CL updates the validation to be correct and adds tests.
The DepthStencilCopy tests are factored into smaller helpers to
reduce code duplication.

Bug: dawn:439
Change-Id: I45d4836f6be1707c5171bddef875e535e935f7f4
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/26660
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
2020-09-01 18:40:18 +00:00

755 lines
36 KiB
C++

// Copyright 2020 The Dawn 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 "tests/unittests/validation/ValidationTest.h"
#include "common/Math.h"
#include "utils/TestUtils.h"
#include "utils/TextureFormatUtils.h"
#include "utils/WGPUHelpers.h"
namespace {
class QueueWriteTextureValidationTest : public ValidationTest {
private:
void SetUp() override {
ValidationTest::SetUp();
queue = device.GetDefaultQueue();
}
protected:
wgpu::Texture Create2DTexture(wgpu::Extent3D size,
uint32_t mipLevelCount,
wgpu::TextureFormat format,
wgpu::TextureUsage usage,
uint32_t sampleCount = 1) {
wgpu::TextureDescriptor descriptor;
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.size.width = size.width;
descriptor.size.height = size.height;
descriptor.size.depth = size.depth;
descriptor.sampleCount = sampleCount;
descriptor.format = format;
descriptor.mipLevelCount = mipLevelCount;
descriptor.usage = usage;
wgpu::Texture tex = device.CreateTexture(&descriptor);
return tex;
}
void TestWriteTexture(size_t dataSize,
uint32_t dataOffset,
uint32_t dataBytesPerRow,
uint32_t dataRowsPerImage,
wgpu::Texture texture,
uint32_t texLevel,
wgpu::Origin3D texOrigin,
wgpu::Extent3D size,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All) {
std::vector<uint8_t> data(dataSize);
wgpu::TextureDataLayout textureDataLayout;
textureDataLayout.offset = dataOffset;
textureDataLayout.bytesPerRow = dataBytesPerRow;
textureDataLayout.rowsPerImage = dataRowsPerImage;
wgpu::TextureCopyView textureCopyView =
utils::CreateTextureCopyView(texture, texLevel, texOrigin, aspect);
queue.WriteTexture(&textureCopyView, data.data(), dataSize, &textureDataLayout, &size);
}
void TestWriteTextureExactDataSize(uint32_t bytesPerRow,
uint32_t rowsPerImage,
wgpu::Texture texture,
wgpu::TextureFormat textureFormat,
wgpu::Origin3D origin,
wgpu::Extent3D extent3D) {
// Check the minimal valid dataSize.
uint64_t dataSize =
utils::RequiredBytesInCopy(bytesPerRow, rowsPerImage, extent3D, textureFormat);
TestWriteTexture(dataSize, 0, bytesPerRow, rowsPerImage, texture, 0, origin, extent3D);
// Check dataSize was indeed minimal.
uint64_t invalidSize = dataSize - 1;
ASSERT_DEVICE_ERROR(TestWriteTexture(invalidSize, 0, bytesPerRow, rowsPerImage, texture,
0, origin, extent3D));
}
wgpu::Queue queue;
};
// Test the success case for WriteTexture
TEST_F(QueueWriteTextureValidationTest, Success) {
const uint64_t dataSize =
utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
wgpu::Texture destination = Create2DTexture({16, 16, 4}, 5, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst);
// Different copies, including some that touch the OOB condition
{
// Copy 4x4 block in corner of first mip.
TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {4, 4, 1});
// Copy 4x4 block in opposite corner of first mip.
TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {12, 12, 0}, {4, 4, 1});
// Copy 4x4 block in the 4x4 mip.
TestWriteTexture(dataSize, 0, 256, 0, destination, 2, {0, 0, 0}, {4, 4, 1});
// Copy with a data offset
TestWriteTexture(dataSize, dataSize - 4, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1});
}
// Copies with a 256-byte aligned bytes per row but unaligned texture region
{
// Unaligned region
TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {3, 4, 1});
// Unaligned region with texture offset
TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {5, 7, 0}, {2, 3, 1});
// Unaligned region, with data offset
TestWriteTexture(dataSize, 31 * 4, 256, 0, destination, 0, {0, 0, 0}, {3, 3, 1});
}
// Empty copies are valid
{
// An empty copy
TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {0, 0, 0}, {0, 0, 1});
// An empty copy with depth = 0
TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {0, 0, 0}, {0, 0, 0});
// An empty copy touching the end of the data
TestWriteTexture(dataSize, dataSize, 0, 0, destination, 0, {0, 0, 0}, {0, 0, 1});
// An empty copy touching the side of the texture
TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {16, 16, 0}, {0, 0, 1});
// An empty copy with depth = 1 and bytesPerRow > 0
TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {0, 0, 1});
// An empty copy with height > 0, depth = 0, bytesPerRow > 0 and rowsPerImage > 0
TestWriteTexture(dataSize, 0, 256, 16, destination, 0, {0, 0, 0}, {0, 1, 0});
}
}
// Test OOB conditions on the data
TEST_F(QueueWriteTextureValidationTest, OutOfBoundsOnData) {
const uint64_t dataSize =
utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
wgpu::Texture destination = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst);
// OOB on the data because we copy too many pixels
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {4, 5, 1}));
// OOB on the data because of the offset
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 4, 256, 0, destination, 0, {0, 0, 0}, {4, 4, 1}));
// OOB on the data because utils::RequiredBytesInCopy overflows
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 0, 512, 0, destination, 0, {0, 0, 0}, {4, 3, 1}));
// Not OOB on the data although bytes per row * height overflows
// but utils::RequiredBytesInCopy * depth does not overflow
{
uint32_t sourceDataSize =
utils::RequiredBytesInCopy(256, 0, {7, 3, 1}, wgpu::TextureFormat::RGBA8Unorm);
ASSERT_TRUE(256 * 3 > sourceDataSize) << "bytes per row * height should overflow data";
TestWriteTexture(sourceDataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {7, 3, 1});
}
}
// Test OOB conditions on the texture
TEST_F(QueueWriteTextureValidationTest, OutOfBoundsOnTexture) {
const uint64_t dataSize =
utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
wgpu::Texture destination = Create2DTexture({16, 16, 2}, 5, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst);
// OOB on the texture because x + width overflows
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {13, 12, 0}, {4, 4, 1}));
// OOB on the texture because y + width overflows
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {12, 13, 0}, {4, 4, 1}));
// OOB on the texture because we overflow a non-zero mip
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 0, 256, 0, destination, 2, {1, 0, 0}, {4, 4, 1}));
// OOB on the texture even on an empty copy when we copy to a non-existent mip.
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 0, 0, 0, destination, 5, {0, 0, 0}, {0, 0, 1}));
// OOB on the texture because slice overflows
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {0, 0, 2}, {0, 0, 1}));
}
// Test that we force Depth=1 on writes to 2D textures
TEST_F(QueueWriteTextureValidationTest, DepthConstraintFor2DTextures) {
const uint64_t dataSize =
utils::RequiredBytesInCopy(0, 0, {0, 0, 2}, wgpu::TextureFormat::RGBA8Unorm);
wgpu::Texture destination = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst);
// Depth > 1 on an empty copy still errors
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 0, 0, 0, destination, 0, {0, 0, 0}, {0, 0, 2}));
}
// Test WriteTexture with incorrect texture usage
TEST_F(QueueWriteTextureValidationTest, IncorrectUsage) {
const uint64_t dataSize =
utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
wgpu::Texture sampled = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::Sampled);
// Incorrect destination usage
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 0, 256, 0, sampled, 0, {0, 0, 0}, {4, 4, 1}));
}
// Test incorrect values of bytesPerRow and that values not divisible by 256 are allowed.
TEST_F(QueueWriteTextureValidationTest, BytesPerRowLimitations) {
wgpu::Texture destination = Create2DTexture({3, 7, 1}, 1, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst);
// bytesPerRow = 0
{
// copyHeight > 1
ASSERT_DEVICE_ERROR(
TestWriteTexture(128, 0, 0, 0, destination, 0, {0, 0, 0}, {3, 7, 1}));
// copyDepth > 1
ASSERT_DEVICE_ERROR(
TestWriteTexture(128, 0, 0, 1, destination, 0, {0, 0, 0}, {3, 1, 2}));
// copyHeight = 1 and copyDepth = 1
TestWriteTexture(128, 0, 0, 0, destination, 0, {0, 0, 0}, {3, 1, 1});
}
// bytesPerRow = 11 is invalid since a row takes 12 bytes.
{
// copyHeight > 1
ASSERT_DEVICE_ERROR(
TestWriteTexture(128, 0, 11, 0, destination, 0, {0, 0, 0}, {3, 7, 1}));
// copyDepth > 1
ASSERT_DEVICE_ERROR(
TestWriteTexture(128, 0, 11, 1, destination, 0, {0, 0, 0}, {3, 1, 2}));
// copyHeight = 1 and copyDepth = 1
TestWriteTexture(128, 0, 11, 0, destination, 0, {0, 0, 0}, {3, 1, 1});
}
// bytesPerRow = 12 is valid since a row takes 12 bytes.
TestWriteTexture(128, 0, 12, 0, destination, 0, {0, 0, 0}, {3, 7, 1});
// bytesPerRow = 13 is valid since a row takes 12 bytes.
TestWriteTexture(128, 0, 13, 0, destination, 0, {0, 0, 0}, {3, 7, 1});
}
// Test that if rowsPerImage is greater than 0, it must be at least copy height.
TEST_F(QueueWriteTextureValidationTest, ImageHeightConstraint) {
uint64_t dataSize =
utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
wgpu::Texture destination = Create2DTexture({16, 16, 1}, 1, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst);
// Image height is zero (Valid)
TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {4, 4, 1});
// Image height is equal to copy height (Valid)
TestWriteTexture(dataSize, 0, 256, 4, destination, 0, {0, 0, 0}, {4, 4, 1});
// Image height is larger than copy height (Valid)
TestWriteTexture(dataSize, 0, 256, 5, destination, 0, {0, 0, 0}, {4, 4, 1});
// Image height is less than copy height (Invalid)
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 0, 256, 3, destination, 0, {0, 0, 0}, {4, 4, 1}));
}
// Test WriteTexture with incorrect data offset usage
TEST_F(QueueWriteTextureValidationTest, IncorrectDataOffset) {
uint64_t dataSize =
utils::RequiredBytesInCopy(256, 0, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
wgpu::Texture destination = Create2DTexture({16, 16, 1}, 5, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst);
// Correct usage
TestWriteTexture(dataSize, dataSize - 4, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1});
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, dataSize - 6, 256, 0, destination, 0, {0, 0, 0}, {1, 1, 1}));
}
// Test multisampled textures can be used in WriteTexture.
TEST_F(QueueWriteTextureValidationTest, WriteToMultisampledTexture) {
uint64_t dataSize =
utils::RequiredBytesInCopy(256, 0, {2, 2, 1}, wgpu::TextureFormat::RGBA8Unorm);
wgpu::Texture destination = Create2DTexture({2, 2, 1}, 1, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst, 4);
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 0, 256, 0, destination, 0, {0, 0, 0}, {2, 2, 1}));
}
// Test that WriteTexture cannot be run with a destroyed texture.
TEST_F(QueueWriteTextureValidationTest, DestroyedTexture) {
const uint64_t dataSize =
utils::RequiredBytesInCopy(256, 4, {4, 4, 1}, wgpu::TextureFormat::RGBA8Unorm);
wgpu::Texture destination = Create2DTexture({16, 16, 4}, 5, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst);
destination.Destroy();
ASSERT_DEVICE_ERROR(
TestWriteTexture(dataSize, 0, 256, 4, destination, 0, {0, 0, 0}, {4, 4, 1}));
}
// Test WriteTexture with texture in error state causes errors.
TEST_F(QueueWriteTextureValidationTest, TextureInErrorState) {
wgpu::TextureDescriptor errorTextureDescriptor;
errorTextureDescriptor.size.depth = 0;
ASSERT_DEVICE_ERROR(wgpu::Texture errorTexture =
device.CreateTexture(&errorTextureDescriptor));
wgpu::TextureCopyView errorTextureCopyView =
utils::CreateTextureCopyView(errorTexture, 0, {0, 0, 0});
wgpu::Extent3D extent3D = {0, 0, 0};
{
std::vector<uint8_t> data(4);
wgpu::TextureDataLayout textureDataLayout = utils::CreateTextureDataLayout(0, 0, 0);
ASSERT_DEVICE_ERROR(queue.WriteTexture(&errorTextureCopyView, data.data(), 4,
&textureDataLayout, &extent3D));
}
}
// Test that WriteTexture throws an error when requiredBytesInCopy overflows uint64_t
TEST_F(QueueWriteTextureValidationTest, RequiredBytesInCopyOverflow) {
wgpu::Texture destination = Create2DTexture({1, 1, 16}, 1, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst);
// success because depth = 1.
TestWriteTexture(10000, 0, (1 << 31), (1 << 31), destination, 0, {0, 0, 0}, {1, 1, 1});
// failure because bytesPerImage * (depth - 1) overflows.
ASSERT_DEVICE_ERROR(TestWriteTexture(10000, 0, (1 << 31), (1 << 31), destination, 0,
{0, 0, 0}, {1, 1, 16}));
}
// Regression tests for a bug in the computation of texture data size in Dawn.
TEST_F(QueueWriteTextureValidationTest, TextureWriteDataSizeLastRowComputation) {
constexpr uint32_t kBytesPerRow = 256;
constexpr uint32_t kWidth = 4;
constexpr uint32_t kHeight = 4;
constexpr std::array<wgpu::TextureFormat, 2> kFormats = {wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureFormat::RG8Unorm};
{
// kBytesPerRow * (kHeight - 1) + kWidth is not large enough to be the valid data size
// in this test because the data sizes in WriteTexture are not in texels but in bytes.
constexpr uint32_t kInvalidDataSize = kBytesPerRow * (kHeight - 1) + kWidth;
for (wgpu::TextureFormat format : kFormats) {
wgpu::Texture destination =
Create2DTexture({kWidth, kHeight, 1}, 1, format, wgpu::TextureUsage::CopyDst);
ASSERT_DEVICE_ERROR(TestWriteTexture(kInvalidDataSize, 0, kBytesPerRow, 0,
destination, 0, {0, 0, 0},
{kWidth, kHeight, 1}));
}
}
{
for (wgpu::TextureFormat format : kFormats) {
uint32_t validDataSize =
utils::RequiredBytesInCopy(kBytesPerRow, 0, {kWidth, kHeight, 1}, format);
wgpu::Texture destination =
Create2DTexture({kWidth, kHeight, 1}, 1, format, wgpu::TextureUsage::CopyDst);
// Verify the return value of RequiredBytesInCopy() is exactly the minimum valid
// data size in this test.
{
uint32_t invalidDataSize = validDataSize - 1;
ASSERT_DEVICE_ERROR(TestWriteTexture(invalidDataSize, 0, kBytesPerRow, 0,
destination, 0, {0, 0, 0},
{kWidth, kHeight, 1}));
}
{
TestWriteTexture(validDataSize, 0, kBytesPerRow, 0, destination, 0, {0, 0, 0},
{kWidth, kHeight, 1});
}
}
}
}
// Test write from data to mip map of non square texture
TEST_F(QueueWriteTextureValidationTest, WriteToMipmapOfNonSquareTexture) {
uint64_t dataSize =
utils::RequiredBytesInCopy(256, 0, {4, 2, 1}, wgpu::TextureFormat::RGBA8Unorm);
uint32_t maxMipmapLevel = 3;
wgpu::Texture destination =
Create2DTexture({4, 2, 1}, maxMipmapLevel, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst);
// Copy to top level mip map
TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel - 1, {0, 0, 0},
{1, 1, 1});
// Copy to high level mip map
TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel - 2, {0, 0, 0},
{2, 1, 1});
// Mip level out of range
ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel,
{0, 0, 0}, {1, 1, 1}));
// Copy origin out of range
ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel - 2,
{1, 0, 0}, {2, 1, 1}));
// Copy size out of range
ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, 256, 0, destination, maxMipmapLevel - 2,
{0, 0, 0}, {2, 2, 1}));
}
// Test writes to multiple array layers of an uncompressed texture
TEST_F(QueueWriteTextureValidationTest, WriteToMultipleArrayLayers) {
wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
{4, 2, 5}, 1, wgpu::TextureFormat::RGBA8Unorm,
wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);
// Write to all array layers
TestWriteTextureExactDataSize(256, 2, destination, wgpu::TextureFormat::RGBA8Unorm,
{0, 0, 0}, {4, 2, 5});
// Write to the highest array layer
TestWriteTextureExactDataSize(256, 2, destination, wgpu::TextureFormat::RGBA8Unorm,
{0, 0, 4}, {4, 2, 1});
// Write to array layers in the middle
TestWriteTextureExactDataSize(256, 2, destination, wgpu::TextureFormat::RGBA8Unorm,
{0, 0, 1}, {4, 2, 3});
// Copy with a non-packed rowsPerImage
TestWriteTextureExactDataSize(256, 3, destination, wgpu::TextureFormat::RGBA8Unorm,
{0, 0, 0}, {4, 2, 5});
// Copy with bytesPerRow = 500
TestWriteTextureExactDataSize(500, 2, destination, wgpu::TextureFormat::RGBA8Unorm,
{0, 0, 1}, {4, 2, 3});
}
// Test it is invalid to write into a depth texture.
TEST_F(QueueWriteTextureValidationTest, WriteToDepthAspect) {
uint32_t bytesPerRow = sizeof(float) * 4;
const uint64_t dataSize = utils::RequiredBytesInCopy(bytesPerRow, 0, {4, 4, 1},
wgpu::TextureFormat::Depth32Float);
// Invalid to write into depth32float
{
wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
{4, 4, 1}, 1, wgpu::TextureFormat::Depth32Float, wgpu::TextureUsage::CopyDst);
ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All));
ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
{0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::DepthOnly));
}
// Invalid to write into depth24plus
{
wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
{4, 4, 1}, 1, wgpu::TextureFormat::Depth24Plus, wgpu::TextureUsage::CopyDst);
ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
{0, 0, 0}, {4, 4, 1}, wgpu::TextureAspect::All));
ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
{0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::DepthOnly));
}
}
// Test write texture to the stencil aspect
TEST_F(QueueWriteTextureValidationTest, WriteToStencilAspect) {
uint32_t bytesPerRow = 4;
const uint64_t dataSize =
utils::RequiredBytesInCopy(bytesPerRow, 0, {4, 4, 1}, wgpu::TextureFormat::R8Uint);
// It is valid to write into the stencil aspect of depth24plus-stencil8
{
wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
{4, 4, 1}, 1, wgpu::TextureFormat::Depth24PlusStencil8,
wgpu::TextureUsage::CopyDst);
TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0, {0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::StencilOnly);
// And that it fails if the buffer is one byte too small
ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize - 1, 0, bytesPerRow, 0, destination, 0,
{0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::StencilOnly));
// It is invalid to write just part of the subresource size
ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
{0, 0, 0}, {3, 3, 1},
wgpu::TextureAspect::StencilOnly));
}
// It is invalid to write into the stencil aspect of depth24plus (no stencil)
{
wgpu::Texture destination = QueueWriteTextureValidationTest::Create2DTexture(
{4, 4, 1}, 1, wgpu::TextureFormat::Depth24Plus, wgpu::TextureUsage::CopyDst);
ASSERT_DEVICE_ERROR(TestWriteTexture(dataSize, 0, bytesPerRow, 0, destination, 0,
{0, 0, 0}, {4, 4, 1},
wgpu::TextureAspect::StencilOnly));
}
}
class WriteTextureTest_CompressedTextureFormats : public QueueWriteTextureValidationTest {
public:
WriteTextureTest_CompressedTextureFormats() : QueueWriteTextureValidationTest() {
device = CreateDeviceFromAdapter(adapter, {"texture_compression_bc"});
}
protected:
wgpu::Texture Create2DTexture(wgpu::TextureFormat format,
uint32_t mipmapLevels = 1,
uint32_t width = kWidth,
uint32_t height = kHeight) {
constexpr wgpu::TextureUsage kUsage = wgpu::TextureUsage::CopyDst;
constexpr uint32_t kArrayLayers = 1;
return QueueWriteTextureValidationTest::Create2DTexture(
{width, height, kArrayLayers}, mipmapLevels, format, kUsage, 1);
}
void TestWriteTexture(size_t dataSize,
uint32_t dataOffset,
uint32_t dataBytesPerRow,
uint32_t dataRowsPerImage,
wgpu::Texture texture,
uint32_t textLevel,
wgpu::Origin3D textOrigin,
wgpu::Extent3D size) {
QueueWriteTextureValidationTest::TestWriteTexture(dataSize, dataOffset, dataBytesPerRow,
dataRowsPerImage, texture, textLevel,
textOrigin, size);
}
static constexpr uint32_t kWidth = 16;
static constexpr uint32_t kHeight = 16;
};
// Tests to verify that data offset must be a multiple of the compressed texture blocks in bytes
TEST_F(WriteTextureTest_CompressedTextureFormats, DataOffset) {
for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
wgpu::Texture texture = Create2DTexture(bcFormat);
// Valid usages of data offset.
{
uint32_t validDataOffset = utils::GetTexelBlockSizeInBytes(bcFormat);
QueueWriteTextureValidationTest::TestWriteTexture(512, validDataOffset, 256, 4,
texture, 0, {0, 0, 0}, {4, 4, 1});
}
// Failures on invalid data offset.
{
uint32_t kInvalidDataOffset = utils::GetTexelBlockSizeInBytes(bcFormat) / 2;
ASSERT_DEVICE_ERROR(TestWriteTexture(512, kInvalidDataOffset, 256, 4, texture, 0,
{0, 0, 0}, {4, 4, 1}));
}
}
}
// Tests to verify that bytesPerRow must not be less than (width / blockWidth) *
// blockSizeInBytes and that it doesn't have to be a multiple of the compressed
// texture block width.
TEST_F(WriteTextureTest_CompressedTextureFormats, BytesPerRow) {
constexpr uint32_t kTestWidth = 160;
constexpr uint32_t kTestHeight = 160;
// Failures on the BytesPerRow that is not large enough.
{
constexpr uint32_t kSmallBytesPerRow = 256;
for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
wgpu::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
ASSERT_DEVICE_ERROR(TestWriteTexture(1024, 0, kSmallBytesPerRow, 4, texture, 0,
{0, 0, 0}, {kTestWidth, 4, 1}));
}
}
// Test it is valid to use a BytesPerRow that is not a multiple of 256.
{
for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
wgpu::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight);
uint32_t ValidBytesPerRow =
kTestWidth / 4 * utils::GetTexelBlockSizeInBytes(bcFormat);
ASSERT_NE(0u, ValidBytesPerRow % 256);
TestWriteTexture(1024, 0, ValidBytesPerRow, 4, texture, 0, {0, 0, 0},
{kTestWidth, 4, 1});
}
}
for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
wgpu::Texture texture = Create2DTexture(bcFormat);
// Valid usage of bytesPerRow in WriteTexture with compressed texture formats.
{
constexpr uint32_t kValidBytesPerRow = 20;
TestWriteTexture(512, 0, kValidBytesPerRow, 0, texture, 0, {0, 0, 0}, {4, 4, 1});
}
// Valid bytesPerRow.
// Note that image width is not a multiple of blockWidth.
{
constexpr uint32_t kValidBytesPerRow = 17;
TestWriteTexture(512, 0, kValidBytesPerRow, 0, texture, 0, {0, 0, 0}, {4, 4, 1});
}
}
}
// Tests to verify that rowsPerImage must be a multiple of the compressed texture block height
TEST_F(WriteTextureTest_CompressedTextureFormats, RowsPerImage) {
for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
wgpu::Texture texture = Create2DTexture(bcFormat);
// Valid usages of rowsPerImage in WriteTexture with compressed texture formats.
{
constexpr uint32_t kValidRowsPerImage = 8;
TestWriteTexture(512, 0, 256, kValidRowsPerImage, texture, 0, {0, 0, 0}, {4, 4, 1});
}
// 4 is the exact limit for rowsPerImage here.
{
constexpr uint32_t kValidRowsPerImage = 4;
TestWriteTexture(512, 0, 256, kValidRowsPerImage, texture, 0, {0, 0, 0}, {4, 4, 1});
}
// Failure on invalid rowsPerImage.
{
constexpr uint32_t kInvalidRowsPerImage = 2;
ASSERT_DEVICE_ERROR(TestWriteTexture(512, 0, 256, kInvalidRowsPerImage, texture, 0,
{0, 0, 0}, {4, 4, 1}));
}
}
}
// Tests to verify that ImageOffset.x must be a multiple of the compressed texture block width
// and ImageOffset.y must be a multiple of the compressed texture block height
TEST_F(WriteTextureTest_CompressedTextureFormats, ImageOffset) {
for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
wgpu::Texture texture = Create2DTexture(bcFormat);
wgpu::Texture texture2 = Create2DTexture(bcFormat);
constexpr wgpu::Origin3D kSmallestValidOrigin3D = {4, 4, 0};
// Valid usages of ImageOffset in WriteTexture with compressed texture formats.
{ TestWriteTexture(512, 0, 256, 4, texture, 0, kSmallestValidOrigin3D, {4, 4, 1}); }
// Failures on invalid ImageOffset.x.
{
constexpr wgpu::Origin3D kInvalidOrigin3D = {kSmallestValidOrigin3D.x - 1,
kSmallestValidOrigin3D.y, 0};
ASSERT_DEVICE_ERROR(
TestWriteTexture(512, 0, 256, 4, texture, 0, kInvalidOrigin3D, {4, 4, 1}));
}
// Failures on invalid ImageOffset.y.
{
constexpr wgpu::Origin3D kInvalidOrigin3D = {kSmallestValidOrigin3D.x,
kSmallestValidOrigin3D.y - 1, 0};
ASSERT_DEVICE_ERROR(
TestWriteTexture(512, 0, 256, 4, texture, 0, kInvalidOrigin3D, {4, 4, 1}));
}
}
}
// Tests to verify that ImageExtent.x must be a multiple of the compressed texture block width
// and ImageExtent.y must be a multiple of the compressed texture block height
TEST_F(WriteTextureTest_CompressedTextureFormats, ImageExtent) {
constexpr uint32_t kMipmapLevels = 3;
constexpr uint32_t kTestWidth = 60;
constexpr uint32_t kTestHeight = 60;
for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
wgpu::Texture texture =
Create2DTexture(bcFormat, kMipmapLevels, kTestWidth, kTestHeight);
wgpu::Texture texture2 =
Create2DTexture(bcFormat, kMipmapLevels, kTestWidth, kTestHeight);
constexpr wgpu::Extent3D kSmallestValidExtent3D = {4, 4, 1};
// Valid usages of ImageExtent in WriteTexture with compressed texture formats.
{ TestWriteTexture(512, 0, 256, 8, texture, 0, {0, 0, 0}, kSmallestValidExtent3D); }
// Valid usages of ImageExtent in WriteTexture with compressed texture formats
// and non-zero mipmap levels.
{
constexpr uint32_t kTestMipmapLevel = 2;
constexpr wgpu::Origin3D kTestOrigin = {
(kTestWidth >> kTestMipmapLevel) - kSmallestValidExtent3D.width + 1,
(kTestHeight >> kTestMipmapLevel) - kSmallestValidExtent3D.height + 1, 0};
TestWriteTexture(512, 0, 256, 4, texture, kTestMipmapLevel, kTestOrigin,
kSmallestValidExtent3D);
}
// Failures on invalid ImageExtent.x.
{
constexpr wgpu::Extent3D kInValidExtent3D = {kSmallestValidExtent3D.width - 1,
kSmallestValidExtent3D.height, 1};
ASSERT_DEVICE_ERROR(
TestWriteTexture(512, 0, 256, 4, texture, 0, {0, 0, 0}, kInValidExtent3D));
}
// Failures on invalid ImageExtent.y.
{
constexpr wgpu::Extent3D kInValidExtent3D = {kSmallestValidExtent3D.width,
kSmallestValidExtent3D.height - 1, 1};
ASSERT_DEVICE_ERROR(
TestWriteTexture(512, 0, 256, 4, texture, 0, {0, 0, 0}, kInValidExtent3D));
}
}
}
// Test writes to multiple array layers of a compressed texture
TEST_F(WriteTextureTest_CompressedTextureFormats, WriteToMultipleArrayLayers) {
for (wgpu::TextureFormat bcFormat : utils::kBCFormats) {
wgpu::Texture texture = QueueWriteTextureValidationTest::Create2DTexture(
{12, 16, 20}, 1, bcFormat,
wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc);
// Write to all array layers
TestWriteTextureExactDataSize(256, 16, texture, bcFormat, {0, 0, 0}, {12, 16, 20});
// Write to the highest array layer
TestWriteTextureExactDataSize(256, 16, texture, bcFormat, {0, 0, 19}, {12, 16, 1});
// Write to array layers in the middle
TestWriteTextureExactDataSize(256, 16, texture, bcFormat, {0, 0, 1}, {12, 16, 18});
// Write touching the texture corners with a non-packed rowsPerImage
TestWriteTextureExactDataSize(256, 24, texture, bcFormat, {4, 4, 4}, {8, 12, 16});
// rowsPerImage needs to be a multiple of blockHeight
ASSERT_DEVICE_ERROR(
TestWriteTexture(8192, 0, 256, 6, texture, 0, {0, 0, 0}, {4, 4, 1}));
// rowsPerImage must be a multiple of blockHeight even with an empty write
ASSERT_DEVICE_ERROR(TestWriteTexture(0, 0, 256, 2, texture, 0, {0, 0, 0}, {0, 0, 0}));
}
}
} // anonymous namespace