diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp index 3fa880d721..efb544589f 100644 --- a/src/dawn_native/CommandEncoder.cpp +++ b/src/dawn_native/CommandEncoder.cpp @@ -46,10 +46,22 @@ namespace dawn_native { // All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid // overflows. uint64_t level = textureCopy.level; + + uint32_t widthAtLevel = texture->GetSize().width >> level; + uint32_t heightAtLevel = texture->GetSize().height >> level; + + // Compressed Textures will have paddings if their width or height is not a multiple of + // 4 at non-zero mipmap levels. + if (Is4x4CompressedFormat(texture->GetFormat())) { + // TODO(jiawei.shao@intel.com): check if there are any overflows. + widthAtLevel = (widthAtLevel + 3) / 4 * 4; + heightAtLevel = (heightAtLevel + 3) / 4 * 4; + } + if (uint64_t(textureCopy.origin.x) + uint64_t(copySize.width) > - (static_cast(texture->GetSize().width) >> level) || + static_cast(widthAtLevel) || uint64_t(textureCopy.origin.y) + uint64_t(copySize.height) > - (static_cast(texture->GetSize().height) >> level)) { + static_cast(heightAtLevel)) { return DAWN_VALIDATION_ERROR("Copy would touch outside of the texture"); } @@ -96,10 +108,10 @@ namespace dawn_native { } MaybeError ValidateTexelBufferOffset(TextureBase* texture, const BufferCopy& bufferCopy) { - uint32_t texelSize = - static_cast(TextureFormatPixelSize(texture->GetFormat())); - if (bufferCopy.offset % texelSize != 0) { - return DAWN_VALIDATION_ERROR("Buffer offset must be a multiple of the texel size"); + uint32_t blockSize = TextureFormatTexelBlockSizeInBytes(texture->GetFormat()); + if (bufferCopy.offset % blockSize != 0) { + return DAWN_VALIDATION_ERROR( + "Buffer offset must be a multiple of the texel or block size"); } return {}; @@ -194,18 +206,24 @@ namespace dawn_native { uint32_t* bufferSize) { DAWN_TRY(ValidateImageHeight(imageHeight, copySize.height)); + uint32_t texelOrBlockSizeInBytes = TextureFormatTexelBlockSizeInBytes(textureFormat); + uint32_t blockWidthInTexels = TextureFormatBlockWidthInTexels(textureFormat); + uint32_t blockHeightInTexels = TextureFormatBlockWidthInTexels(textureFormat); + // TODO(cwallez@chromium.org): check for overflows - uint32_t slicePitch = rowPitch * imageHeight; - uint32_t sliceSize = rowPitch * (copySize.height - 1) + - copySize.width * TextureFormatPixelSize(textureFormat); + uint32_t slicePitch = rowPitch * imageHeight / blockWidthInTexels; + uint32_t sliceSize = rowPitch * (copySize.height / blockHeightInTexels - 1) + + (copySize.width / blockWidthInTexels) * texelOrBlockSizeInBytes; *bufferSize = (slicePitch * (copySize.depth - 1)) + sliceSize; return {}; } uint32_t ComputeDefaultRowPitch(TextureBase* texture, uint32_t width) { - uint32_t texelSize = TextureFormatPixelSize(texture->GetFormat()); - return texelSize * width; + const dawn::TextureFormat format = texture->GetFormat(); + uint32_t texelOrBlockSizeInBytes = TextureFormatTexelBlockSizeInBytes(format); + uint32_t blockWidthInTexels = TextureFormatBlockWidthInTexels(format); + return width / blockWidthInTexels * texelOrBlockSizeInBytes; } MaybeError ValidateRowPitch(dawn::TextureFormat format, @@ -215,8 +233,9 @@ namespace dawn_native { return DAWN_VALIDATION_ERROR("Row pitch must be a multiple of 256"); } - uint32_t texelSize = TextureFormatPixelSize(format); - if (rowPitch < copySize.width * texelSize) { + uint32_t texelOrBlockSizeInBytes = TextureFormatTexelBlockSizeInBytes(format); + uint32_t blockWidthInTexels = TextureFormatBlockWidthInTexels(format); + if (rowPitch < copySize.width / blockWidthInTexels * texelOrBlockSizeInBytes) { return DAWN_VALIDATION_ERROR( "Row pitch must not be less than the number of bytes per row"); } @@ -224,6 +243,43 @@ namespace dawn_native { return {}; } + MaybeError ValidateImageHeight(dawn::TextureFormat format, uint32_t imageHeight) { + if (imageHeight % TextureFormatBlockHeightInTexels(format) != 0) { + return DAWN_VALIDATION_ERROR( + "Image height must be a multiple of compressed texture format block width"); + } + + return {}; + } + + MaybeError ValidateImageOrigin(dawn::TextureFormat format, const Origin3D& offset) { + if (offset.x % TextureFormatBlockWidthInTexels(format) != 0) { + return DAWN_VALIDATION_ERROR( + "Offset.x must be a multiple of compressed texture format block width"); + } + + if (offset.y % TextureFormatBlockHeightInTexels(format) != 0) { + return DAWN_VALIDATION_ERROR( + "Offset.y must be a multiple of compressed texture format block height"); + } + + return {}; + } + + MaybeError ValidateImageCopySize(dawn::TextureFormat format, const Extent3D& extent) { + if (extent.width % TextureFormatBlockWidthInTexels(format) != 0) { + return DAWN_VALIDATION_ERROR( + "Extent.width must be a multiple of compressed texture format block width"); + } + + if (extent.height % TextureFormatBlockHeightInTexels(format) != 0) { + return DAWN_VALIDATION_ERROR( + "Extent.height must be a multiple of compressed texture format block height"); + } + + return {}; + } + MaybeError ValidateCanUseAs(BufferBase* buffer, dawn::BufferUsageBit usage) { ASSERT(HasZeroOrOneBits(usage)); if (!(buffer->GetUsage() & usage)) { @@ -904,6 +960,13 @@ namespace dawn_native { DAWN_TRY( ValidateTextureSampleCountInCopyCommands(copy->destination.texture.Get())); + DAWN_TRY(ValidateImageHeight(copy->destination.texture->GetFormat(), + copy->source.imageHeight)); + DAWN_TRY(ValidateImageOrigin(copy->destination.texture->GetFormat(), + copy->destination.origin)); + DAWN_TRY(ValidateImageCopySize(copy->destination.texture->GetFormat(), + copy->copySize)); + uint32_t bufferCopySize = 0; DAWN_TRY(ValidateRowPitch(copy->destination.texture->GetFormat(), copy->copySize, copy->source.rowPitch)); @@ -931,6 +994,13 @@ namespace dawn_native { DAWN_TRY(ValidateTextureSampleCountInCopyCommands(copy->source.texture.Get())); + DAWN_TRY(ValidateImageHeight(copy->source.texture->GetFormat(), + copy->destination.imageHeight)); + DAWN_TRY(ValidateImageOrigin(copy->source.texture->GetFormat(), + copy->source.origin)); + DAWN_TRY( + ValidateImageCopySize(copy->source.texture->GetFormat(), copy->copySize)); + uint32_t bufferCopySize = 0; DAWN_TRY(ValidateRowPitch(copy->source.texture->GetFormat(), copy->copySize, copy->destination.rowPitch)); @@ -960,6 +1030,15 @@ namespace dawn_native { DAWN_TRY(ValidateTextureToTextureCopyRestrictions( copy->source, copy->destination, copy->copySize)); + DAWN_TRY(ValidateImageOrigin(copy->source.texture->GetFormat(), + copy->source.origin)); + DAWN_TRY( + ValidateImageCopySize(copy->source.texture->GetFormat(), copy->copySize)); + DAWN_TRY(ValidateImageOrigin(copy->destination.texture->GetFormat(), + copy->destination.origin)); + DAWN_TRY(ValidateImageCopySize(copy->destination.texture->GetFormat(), + copy->copySize)); + DAWN_TRY(ValidateCopySizeFitsInTexture(copy->source, copy->copySize)); DAWN_TRY(ValidateCopySizeFitsInTexture(copy->destination, copy->copySize)); diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp index d94a707e1e..8e02866a6d 100644 --- a/src/dawn_native/Texture.cpp +++ b/src/dawn_native/Texture.cpp @@ -112,10 +112,6 @@ namespace dawn_native { return IsBCFormat(format); } - bool Is4x4CompressedFormat(dawn::TextureFormat format) { - return IsBCFormat(format); - } - bool IsWritableFormat(dawn::TextureFormat format) { return !IsBCFormat(format); } @@ -216,6 +212,28 @@ namespace dawn_native { } } // anonymous namespace + bool Is4x4CompressedFormat(dawn::TextureFormat format) { + return IsBCFormat(format); + } + + // We treat non-compressed texture formats as the block texture formats in 1x1 blocks. + uint32_t TextureFormatBlockWidthInTexels(dawn::TextureFormat format) { + if (Is4x4CompressedFormat(format)) { + return 4; + } + + return 1; + } + + // We treat non-compressed texture formats as the block texture formats in 1x1 blocks. + uint32_t TextureFormatBlockHeightInTexels(dawn::TextureFormat format) { + if (Is4x4CompressedFormat(format)) { + return 4; + } + + return 1; + } + MaybeError ValidateTextureUsageBit(const TextureDescriptor* descriptor) { DAWN_TRY(ValidateTextureUsageBit(descriptor->usage)); if (!IsWritableFormat(descriptor->format)) { @@ -289,8 +307,10 @@ namespace dawn_native { return {}; } - uint32_t TextureFormatPixelSize(dawn::TextureFormat format) { + // We treat non-compressed texture formats as the block texture formats in 1x1 blocks. + uint32_t TextureFormatTexelBlockSizeInBytes(dawn::TextureFormat format) { switch (format) { + // Non-compressed texture formats case dawn::TextureFormat::R8Unorm: case dawn::TextureFormat::R8Uint: return 1; @@ -303,6 +323,25 @@ namespace dawn_native { return 4; case dawn::TextureFormat::D32FloatS8Uint: return 8; + + // BC formats + case dawn::TextureFormat::BC1RGBAUnorm: + case dawn::TextureFormat::BC1RGBAUnormSrgb: + case dawn::TextureFormat::BC4RSnorm: + case dawn::TextureFormat::BC4RUnorm: + return 8; + case dawn::TextureFormat::BC2RGBAUnorm: + case dawn::TextureFormat::BC2RGBAUnormSrgb: + case dawn::TextureFormat::BC3RGBAUnorm: + case dawn::TextureFormat::BC3RGBAUnormSrgb: + case dawn::TextureFormat::BC5RGSnorm: + case dawn::TextureFormat::BC5RGUnorm: + case dawn::TextureFormat::BC6HRGBSfloat: + case dawn::TextureFormat::BC6HRGBUfloat: + case dawn::TextureFormat::BC7RGBAUnorm: + case dawn::TextureFormat::BC7RGBAUnormSrgb: + return 16; + default: UNREACHABLE(); } @@ -418,6 +457,8 @@ namespace dawn_native { ASSERT(!IsError()); return mDimension; } + + // TODO(jiawei.shao@intel.com): return more information about texture format dawn::TextureFormat TextureBase::GetFormat() const { ASSERT(!IsError()); return mFormat; diff --git a/src/dawn_native/Texture.h b/src/dawn_native/Texture.h index dc3771eb0a..db6a3e4f52 100644 --- a/src/dawn_native/Texture.h +++ b/src/dawn_native/Texture.h @@ -29,7 +29,7 @@ namespace dawn_native { const TextureBase* texture, const TextureViewDescriptor* descriptor); - uint32_t TextureFormatPixelSize(dawn::TextureFormat format); + uint32_t TextureFormatTexelBlockSizeInBytes(dawn::TextureFormat format); bool TextureFormatHasDepth(dawn::TextureFormat format); bool TextureFormatHasStencil(dawn::TextureFormat format); bool TextureFormatHasDepthOrStencil(dawn::TextureFormat format); @@ -37,6 +37,10 @@ namespace dawn_native { bool IsDepthStencilRenderableTextureFormat(dawn::TextureFormat format); bool IsValidSampleCount(uint32_t sampleCount); + bool Is4x4CompressedFormat(dawn::TextureFormat format); + uint32_t TextureFormatBlockWidthInTexels(dawn::TextureFormat format); + uint32_t TextureFormatBlockHeightInTexels(dawn::TextureFormat format); + static constexpr dawn::TextureUsageBit kReadOnlyTextureUsages = dawn::TextureUsageBit::TransferSrc | dawn::TextureUsageBit::Sampled | dawn::TextureUsageBit::Present; diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp index fd2b4e6808..bfdc84aba4 100644 --- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp +++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp @@ -505,7 +505,8 @@ namespace dawn_native { namespace d3d12 { auto copySplit = ComputeTextureCopySplit( copy->destination.origin, copy->copySize, - static_cast(TextureFormatPixelSize(texture->GetFormat())), + static_cast( + TextureFormatTexelBlockSizeInBytes(texture->GetFormat())), copy->source.offset, copy->source.rowPitch, copy->source.imageHeight); D3D12_TEXTURE_COPY_LOCATION textureLocation = @@ -549,7 +550,8 @@ namespace dawn_native { namespace d3d12 { auto copySplit = ComputeTextureCopySplit( copy->source.origin, copy->copySize, - static_cast(TextureFormatPixelSize(texture->GetFormat())), + static_cast( + TextureFormatTexelBlockSizeInBytes(texture->GetFormat())), copy->destination.offset, copy->destination.rowPitch, copy->destination.imageHeight); diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm index 65cf7264fc..20ff0a96f9 100644 --- a/src/dawn_native/metal/CommandBufferMTL.mm +++ b/src/dawn_native/metal/CommandBufferMTL.mm @@ -454,7 +454,7 @@ namespace dawn_native { namespace metal { // Doing the last row copy with the exact number of bytes in last row. // Like copy to a 1D texture to workaround the issue. uint32_t lastRowDataSize = - copySize.width * TextureFormatPixelSize(texture->GetFormat()); + copySize.width * TextureFormatTexelBlockSizeInBytes(texture->GetFormat()); [encoders.blit copyFromBuffer:buffer->GetMTLBuffer() @@ -568,7 +568,7 @@ namespace dawn_native { namespace metal { // Doing the last row copy with the exact number of bytes in last row. // Like copy from a 1D texture to workaround the issue. uint32_t lastRowDataSize = - copySize.width * TextureFormatPixelSize(texture->GetFormat()); + copySize.width * TextureFormatTexelBlockSizeInBytes(texture->GetFormat()); [encoders.blit copyFromTexture:texture->GetMTLTexture() diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp index 13673d78fb..602f93e494 100644 --- a/src/dawn_native/opengl/CommandBufferGL.cpp +++ b/src/dawn_native/opengl/CommandBufferGL.cpp @@ -389,8 +389,9 @@ namespace dawn_native { namespace opengl { gl.ActiveTexture(GL_TEXTURE0); gl.BindTexture(target, texture->GetHandle()); - gl.PixelStorei(GL_UNPACK_ROW_LENGTH, - src.rowPitch / TextureFormatPixelSize(texture->GetFormat())); + gl.PixelStorei( + GL_UNPACK_ROW_LENGTH, + src.rowPitch / TextureFormatTexelBlockSizeInBytes(texture->GetFormat())); gl.PixelStorei(GL_UNPACK_IMAGE_HEIGHT, src.imageHeight); switch (texture->GetDimension()) { case dawn::TextureDimension::e2D: @@ -451,8 +452,9 @@ namespace dawn_native { namespace opengl { } gl.BindBuffer(GL_PIXEL_PACK_BUFFER, buffer->GetHandle()); - gl.PixelStorei(GL_PACK_ROW_LENGTH, - dst.rowPitch / TextureFormatPixelSize(texture->GetFormat())); + gl.PixelStorei( + GL_PACK_ROW_LENGTH, + dst.rowPitch / TextureFormatTexelBlockSizeInBytes(texture->GetFormat())); gl.PixelStorei(GL_PACK_IMAGE_HEIGHT, dst.imageHeight); ASSERT(copySize.depth == 1 && src.origin.z == 0); void* offset = reinterpret_cast(static_cast(dst.offset)); diff --git a/src/dawn_native/opengl/TextureGL.cpp b/src/dawn_native/opengl/TextureGL.cpp index 1ffcacbc78..8726118594 100644 --- a/src/dawn_native/opengl/TextureGL.cpp +++ b/src/dawn_native/opengl/TextureGL.cpp @@ -171,7 +171,7 @@ namespace dawn_native { namespace opengl { if (GetDevice()->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) { static constexpr uint32_t MAX_TEXEL_SIZE = 16; - ASSERT(TextureFormatPixelSize(GetFormat()) <= MAX_TEXEL_SIZE); + ASSERT(TextureFormatTexelBlockSizeInBytes(GetFormat()) <= MAX_TEXEL_SIZE); GLubyte clearColor[MAX_TEXEL_SIZE]; std::fill(clearColor, clearColor + MAX_TEXEL_SIZE, 255); diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp index ddf489357d..e36676f713 100644 --- a/src/dawn_native/vulkan/CommandBufferVk.cpp +++ b/src/dawn_native/vulkan/CommandBufferVk.cpp @@ -51,7 +51,7 @@ namespace dawn_native { namespace vulkan { region.bufferOffset = bufferCopy.offset; // In Vulkan the row length is in texels while it is in bytes for Dawn region.bufferRowLength = - bufferCopy.rowPitch / TextureFormatPixelSize(texture->GetFormat()); + bufferCopy.rowPitch / TextureFormatTexelBlockSizeInBytes(texture->GetFormat()); region.bufferImageHeight = bufferCopy.imageHeight; region.imageSubresource.aspectMask = texture->GetVkAspectMask(); diff --git a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp index afad75c513..f913029eb0 100644 --- a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp +++ b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp @@ -1080,4 +1080,313 @@ TEST_F(CopyCommandTest_T2T, MultisampledCopies) { TestT2TCopy(utils::Expectation::Failure, sourceMultiSampled4x, 0, 0, {0, 0, 0}, destinationMultiSampled4x, 0, 0, {0, 0, 0}, {15, 15, 1}); } -} \ No newline at end of file +} + +class CopyCommandTest_CompressedTextureFormats : public CopyCommandTest { + protected: + dawn::Texture Create2DTexture(dawn::TextureFormat format, + uint32_t mipmapLevels = 1, + uint32_t width = kWidth, + uint32_t height = kHeight) { + constexpr dawn::TextureUsageBit kUsage = dawn::TextureUsageBit::TransferDst | + dawn::TextureUsageBit::TransferSrc | + dawn::TextureUsageBit::Sampled; + constexpr uint32_t kArrayLayers = 1; + return CopyCommandTest::Create2DTexture(width, height, mipmapLevels, kArrayLayers, format, + kUsage, 1); + } + + static uint32_t CompressedFormatBlockSizeInBytes(dawn::TextureFormat format) { + switch (format) { + case dawn::TextureFormat::BC1RGBAUnorm: + case dawn::TextureFormat::BC1RGBAUnormSrgb: + case dawn::TextureFormat::BC4RSnorm: + case dawn::TextureFormat::BC4RUnorm: + return 8; + case dawn::TextureFormat::BC2RGBAUnorm: + case dawn::TextureFormat::BC2RGBAUnormSrgb: + case dawn::TextureFormat::BC3RGBAUnorm: + case dawn::TextureFormat::BC3RGBAUnormSrgb: + case dawn::TextureFormat::BC5RGSnorm: + case dawn::TextureFormat::BC5RGUnorm: + case dawn::TextureFormat::BC6HRGBSfloat: + case dawn::TextureFormat::BC6HRGBUfloat: + case dawn::TextureFormat::BC7RGBAUnorm: + case dawn::TextureFormat::BC7RGBAUnormSrgb: + return 16; + default: + UNREACHABLE(); + return 0; + } + } + + void TestBothTBCopies(utils::Expectation expectation, + dawn::Buffer buffer, + uint64_t bufferOffset, + uint32_t bufferRowPitch, + uint32_t imageHeight, + dawn::Texture texture, + uint32_t level, + uint32_t arraySlice, + dawn::Origin3D origin, + dawn::Extent3D extent3D) { + TestB2TCopy(expectation, buffer, bufferOffset, bufferRowPitch, imageHeight, texture, level, + arraySlice, origin, extent3D); + TestT2BCopy(expectation, texture, level, arraySlice, origin, buffer, bufferOffset, + bufferRowPitch, imageHeight, extent3D); + } + + void TestBothT2TCopies(utils::Expectation expectation, + dawn::Texture texture1, + uint32_t level1, + uint32_t slice1, + dawn::Origin3D origin1, + dawn::Texture texture2, + uint32_t level2, + uint32_t slice2, + dawn::Origin3D origin2, + dawn::Extent3D extent3D) { + TestT2TCopy(expectation, texture1, level1, slice1, origin1, texture2, level2, slice2, + origin2, extent3D); + TestT2TCopy(expectation, texture2, level2, slice2, origin2, texture1, level1, slice1, + origin1, extent3D); + } + + static constexpr uint32_t kWidth = 16; + static constexpr uint32_t kHeight = 16; + + const std::array kBCFormats = { + dawn::TextureFormat::BC1RGBAUnorm, dawn::TextureFormat::BC1RGBAUnormSrgb, + dawn::TextureFormat::BC2RGBAUnorm, dawn::TextureFormat::BC2RGBAUnormSrgb, + dawn::TextureFormat::BC3RGBAUnorm, dawn::TextureFormat::BC3RGBAUnormSrgb, + dawn::TextureFormat::BC4RUnorm, dawn::TextureFormat::BC4RSnorm, + dawn::TextureFormat::BC5RGUnorm, dawn::TextureFormat::BC5RGSnorm, + dawn::TextureFormat::BC6HRGBUfloat, dawn::TextureFormat::BC6HRGBSfloat, + dawn::TextureFormat::BC7RGBAUnorm, dawn::TextureFormat::BC7RGBAUnormSrgb}; +}; + +// Tests to verify that bufferOffset must be a multiple of the compressed texture blocks in bytes +// in buffer-to-texture or texture-to-buffer copies with compressed texture formats. +TEST_F(CopyCommandTest_CompressedTextureFormats, BufferOffset) { + dawn::Buffer buffer = + CreateBuffer(512, dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst); + + for (dawn::TextureFormat bcFormat : kBCFormats) { + dawn::Texture texture = Create2DTexture(bcFormat); + + // Valid usages of BufferOffset in B2T and T2B copies with compressed texture formats. + { + uint32_t validBufferOffset = CompressedFormatBlockSizeInBytes(bcFormat); + TestBothTBCopies(utils::Expectation::Success, buffer, validBufferOffset, 256, 4, + texture, 0, 0, {0, 0, 0}, {4, 4, 1}); + } + + // Failures on invalid bufferOffset. + { + uint32_t kInvalidBufferOffset = CompressedFormatBlockSizeInBytes(bcFormat) / 2; + TestBothTBCopies(utils::Expectation::Failure, buffer, kInvalidBufferOffset, 256, 4, + texture, 0, 0, {0, 0, 0}, {4, 4, 1}); + } + } +} + +// Tests to verify that RowPitch must not be smaller than (width / blockWidth) * blockSizeInBytes +// and it is valid to use 0 as RowPitch in buffer-to-texture or texture-to-buffer copies with +// compressed texture formats. +// Note that in Dawn we require RowPitch be a multiple of 256, which ensures RowPitch will always be +// the multiple of compressed texture block width in bytes. +TEST_F(CopyCommandTest_CompressedTextureFormats, RowPitch) { + dawn::Buffer buffer = + CreateBuffer(1024, dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst); + + { + constexpr uint32_t kTestWidth = 160; + constexpr uint32_t kTestHeight = 160; + + // Failures on the RowPitch that is not large enough. + { + constexpr uint32_t kSmallRowPitch = 256; + for (dawn::TextureFormat bcFormat : kBCFormats) { + dawn::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight); + TestBothTBCopies(utils::Expectation::Failure, buffer, 0, kSmallRowPitch, 4, texture, + 0, 0, {0, 0, 0}, {kTestWidth, 4, 1}); + } + } + + // Test it is not valid to use a RowPitch that is not a multiple of 256. + { + for (dawn::TextureFormat bcFormat : kBCFormats) { + dawn::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight); + uint32_t inValidRowPitch = + kTestWidth / 4 * CompressedFormatBlockSizeInBytes(bcFormat); + ASSERT_NE(0u, inValidRowPitch % 256); + TestBothTBCopies(utils::Expectation::Failure, buffer, 0, inValidRowPitch, 4, + texture, 0, 0, {0, 0, 0}, {kTestWidth, 4, 1}); + } + } + + // Test the smallest valid RowPitch should work. + { + for (dawn::TextureFormat bcFormat : kBCFormats) { + dawn::Texture texture = Create2DTexture(bcFormat, 1, kTestWidth, kTestHeight); + uint32_t smallestValidRowPitch = + Align(kTestWidth / 4 * CompressedFormatBlockSizeInBytes(bcFormat), 256); + TestBothTBCopies(utils::Expectation::Success, buffer, 0, smallestValidRowPitch, 4, + texture, 0, 0, {0, 0, 0}, {kTestWidth, 4, 1}); + } + } + } + + // Test RowPitch == 0. + { + constexpr uint32_t kZeroRowPitch = 0; + constexpr uint32_t kTestHeight = 128; + + { + constexpr uint32_t kValidWidth = 128; + for (dawn::TextureFormat bcFormat : kBCFormats) { + dawn::Texture texture = Create2DTexture(bcFormat, 1, kValidWidth, kTestHeight); + TestBothTBCopies(utils::Expectation::Success, buffer, 0, kZeroRowPitch, 4, texture, + 0, 0, {0, 0, 0}, {kValidWidth, 4, 1}); + } + } + + { + constexpr uint32_t kInValidWidth = 16; + for (dawn::TextureFormat bcFormat : kBCFormats) { + dawn::Texture texture = Create2DTexture(bcFormat, 1, kInValidWidth, kTestHeight); + TestBothTBCopies(utils::Expectation::Failure, buffer, 0, kZeroRowPitch, 4, texture, + 0, 0, {0, 0, 0}, {kInValidWidth, 4, 1}); + } + } + } +} + +// Tests to verify that imageHeight must be a multiple of the compressed texture block height in +// buffer-to-texture or texture-to-buffer copies with compressed texture formats. +TEST_F(CopyCommandTest_CompressedTextureFormats, ImageHeight) { + dawn::Buffer buffer = + CreateBuffer(512, dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst); + + for (dawn::TextureFormat bcFormat : kBCFormats) { + dawn::Texture texture = Create2DTexture(bcFormat); + + // Valid usages of imageHeight in B2T and T2B copies with compressed texture formats. + { + constexpr uint32_t kValidImageHeight = 8; + TestBothTBCopies(utils::Expectation::Success, buffer, 0, 256, kValidImageHeight, + texture, 0, 0, {0, 0, 0}, {4, 4, 1}); + } + + // Failures on invalid imageHeight. + { + constexpr uint32_t kInvalidImageHeight = 3; + TestBothTBCopies(utils::Expectation::Failure, buffer, 0, 256, kInvalidImageHeight, + texture, 0, 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 in buffer-to-texture, +// texture-to-buffer or texture-to-texture copies with compressed texture formats. +TEST_F(CopyCommandTest_CompressedTextureFormats, ImageOffset) { + dawn::Buffer buffer = + CreateBuffer(512, dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst); + + for (dawn::TextureFormat bcFormat : kBCFormats) { + dawn::Texture texture = Create2DTexture(bcFormat); + dawn::Texture texture2 = Create2DTexture(bcFormat); + + constexpr dawn::Origin3D kSmallestValidOrigin3D = {4, 4, 0}; + + // Valid usages of ImageOffset in B2T, T2B and T2T copies with compressed texture formats. + { + TestBothTBCopies(utils::Expectation::Success, buffer, 0, 256, 4, texture, 0, 0, + kSmallestValidOrigin3D, {4, 4, 1}); + TestBothT2TCopies(utils::Expectation::Success, texture, 0, 0, {0, 0, 0}, texture2, 0, 0, + kSmallestValidOrigin3D, {4, 4, 1}); + } + + // Failures on invalid ImageOffset.x. + { + constexpr dawn::Origin3D kInvalidOrigin3D = {kSmallestValidOrigin3D.x - 1, + kSmallestValidOrigin3D.y, 0}; + TestBothTBCopies(utils::Expectation::Failure, buffer, 0, 256, 4, texture, 0, 0, + kInvalidOrigin3D, {4, 4, 1}); + TestBothT2TCopies(utils::Expectation::Failure, texture, 0, 0, kInvalidOrigin3D, + texture2, 0, 0, {0, 0, 0}, {4, 4, 1}); + } + + // Failures on invalid ImageOffset.y. + { + constexpr dawn::Origin3D kInvalidOrigin3D = {kSmallestValidOrigin3D.x, + kSmallestValidOrigin3D.y - 1, 0}; + TestBothTBCopies(utils::Expectation::Failure, buffer, 0, 256, 4, texture, 0, 0, + kInvalidOrigin3D, {4, 4, 1}); + TestBothT2TCopies(utils::Expectation::Failure, texture, 0, 0, kInvalidOrigin3D, + texture2, 0, 0, {0, 0, 0}, {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 in buffer-to-texture, +// texture-to-buffer or texture-to-texture copies with compressed texture formats. +TEST_F(CopyCommandTest_CompressedTextureFormats, ImageExtent) { + dawn::Buffer buffer = + CreateBuffer(512, dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst); + + constexpr uint32_t kMipmapLevels = 3; + constexpr uint32_t kTestWidth = 60; + constexpr uint32_t kTestHeight = 60; + + for (dawn::TextureFormat bcFormat : kBCFormats) { + dawn::Texture texture = Create2DTexture(bcFormat, kMipmapLevels, kTestWidth, kTestHeight); + dawn::Texture texture2 = Create2DTexture(bcFormat, kMipmapLevels, kTestWidth, kTestHeight); + + constexpr dawn::Extent3D kSmallestValidExtent3D = {4, 4, 1}; + + // Valid usages of ImageExtent in B2T, T2B and T2T copies with compressed texture formats. + { + TestBothTBCopies(utils::Expectation::Success, buffer, 0, 256, 8, texture, 0, 0, + {0, 0, 0}, kSmallestValidExtent3D); + TestBothT2TCopies(utils::Expectation::Success, texture, 0, 0, {0, 0, 0}, texture2, 0, 0, + {0, 0, 0}, kSmallestValidExtent3D); + } + + // Valid usages of ImageExtent in B2T, T2B and T2T copies with compressed texture formats + // and non-zero mipmap levels. + { + constexpr uint32_t kTestMipmapLevel = 2; + constexpr dawn::Origin3D kTestOrigin = { + (kTestWidth >> kTestMipmapLevel) - kSmallestValidExtent3D.width + 1, + (kTestHeight >> kTestMipmapLevel) - kSmallestValidExtent3D.height + 1, 0}; + + TestBothTBCopies(utils::Expectation::Success, buffer, 0, 256, 4, texture, + kTestMipmapLevel, 0, kTestOrigin, kSmallestValidExtent3D); + TestBothT2TCopies(utils::Expectation::Success, texture, kTestMipmapLevel, 0, + kTestOrigin, texture2, 0, 0, {0, 0, 0}, kSmallestValidExtent3D); + } + + // Failures on invalid ImageExtent.x. + { + constexpr dawn::Extent3D kInValidExtent3D = {kSmallestValidExtent3D.width - 1, + kSmallestValidExtent3D.height, 1}; + TestBothTBCopies(utils::Expectation::Failure, buffer, 0, 256, 4, texture, 0, 0, + {0, 0, 0}, kInValidExtent3D); + TestBothT2TCopies(utils::Expectation::Failure, texture, 0, 0, {0, 0, 0}, texture2, 0, 0, + {0, 0, 0}, kInValidExtent3D); + } + + // Failures on invalid ImageExtent.y. + { + constexpr dawn::Extent3D kInValidExtent3D = {kSmallestValidExtent3D.width, + kSmallestValidExtent3D.height - 1, 1}; + TestBothTBCopies(utils::Expectation::Failure, buffer, 0, 256, 4, texture, 0, 0, + {0, 0, 0}, kInValidExtent3D); + TestBothT2TCopies(utils::Expectation::Failure, texture, 0, 0, {0, 0, 0}, texture2, 0, 0, + {0, 0, 0}, kInValidExtent3D); + } + } +}