diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp index 6c1df3a3a8..c1201d2bde 100644 --- a/src/dawn_native/CommandEncoder.cpp +++ b/src/dawn_native/CommandEncoder.cpp @@ -183,6 +183,16 @@ namespace dawn_native { DAWN_TRY(ValidateEntireSubresourceCopied(src, dst, copySize)); } + if (src.texture == dst.texture && src.mipLevel == dst.mipLevel) { + ASSERT(src.texture->GetDimension() == wgpu::TextureDimension::e2D && + dst.texture->GetDimension() == wgpu::TextureDimension::e2D); + if (IsRangeOverlapped(src.arrayLayer, dst.arrayLayer, copySize.depth)) { + return DAWN_VALIDATION_ERROR( + "Copy subresources cannot be overlapped when copying within the same " + "texture."); + } + } + return {}; } diff --git a/src/dawn_native/CommandValidation.cpp b/src/dawn_native/CommandValidation.cpp index 55b6cec0af..7f8da9bcdf 100644 --- a/src/dawn_native/CommandValidation.cpp +++ b/src/dawn_native/CommandValidation.cpp @@ -339,4 +339,11 @@ namespace dawn_native { return {}; } + bool IsRangeOverlapped(uint32_t startA, uint32_t startB, uint32_t length) { + uint32_t maxStart = std::max(startA, startB); + uint32_t minStart = std::min(startA, startB); + return static_cast(minStart) + static_cast(length) > + static_cast(maxStart); + } + } // namespace dawn_native diff --git a/src/dawn_native/CommandValidation.h b/src/dawn_native/CommandValidation.h index d649ce32ee..53871ccdc5 100644 --- a/src/dawn_native/CommandValidation.h +++ b/src/dawn_native/CommandValidation.h @@ -36,6 +36,8 @@ namespace dawn_native { MaybeError ValidatePassResourceUsage(const PassResourceUsage& usage); + bool IsRangeOverlapped(uint32_t startA, uint32_t startB, uint32_t length); + } // namespace dawn_native #endif // DAWNNATIVE_COMMANDVALIDATION_H_ diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp index 6fad7c6953..c220176e2c 100644 --- a/src/dawn_native/vulkan/CommandBufferVk.cpp +++ b/src/dawn_native/vulkan/CommandBufferVk.cpp @@ -16,6 +16,7 @@ #include "dawn_native/BindGroupAndStorageBarrierTracker.h" #include "dawn_native/CommandEncoder.h" +#include "dawn_native/CommandValidation.h" #include "dawn_native/Commands.h" #include "dawn_native/RenderBundle.h" #include "dawn_native/vulkan/BindGroupVk.h" @@ -507,6 +508,15 @@ namespace dawn_native { namespace vulkan { copy->copySize.depth); } + if (src.texture.Get() == dst.texture.Get() && src.mipLevel == dst.mipLevel) { + // When there are overlapped subresources, the layout of the overlapped + // subresources should all be GENERAL instead of what we set now. Currently + // it is not allowed to copy with overlapped subresources, but we still + // add the ASSERT here as a reminder for possible changes in the future. + ASSERT(!IsRangeOverlapped(src.arrayLayer, dst.arrayLayer, + copy->copySize.depth)); + } + ToBackend(src.texture) ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc, src.mipLevel, 1, src.arrayLayer, copy->copySize.depth); diff --git a/src/tests/end2end/CopyTests.cpp b/src/tests/end2end/CopyTests.cpp index 60e76e16b7..d450fab6ed 100644 --- a/src/tests/end2end/CopyTests.cpp +++ b/src/tests/end2end/CopyTests.cpp @@ -291,7 +291,10 @@ class CopyTests_T2T : public CopyTests { }; protected: - void DoTest(const TextureSpec& srcSpec, const TextureSpec& dstSpec, const CopySize& copy) { + void DoTest(const TextureSpec& srcSpec, + const TextureSpec& dstSpec, + const CopySize& copy, + bool copyWithinSameTexture = false) { wgpu::TextureDescriptor srcDescriptor; srcDescriptor.dimension = wgpu::TextureDimension::e2D; srcDescriptor.size.width = srcSpec.width; @@ -304,17 +307,22 @@ class CopyTests_T2T : public CopyTests { srcDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; wgpu::Texture srcTexture = device.CreateTexture(&srcDescriptor); - wgpu::TextureDescriptor dstDescriptor; - dstDescriptor.dimension = wgpu::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 = wgpu::TextureFormat::RGBA8Unorm; - dstDescriptor.mipLevelCount = dstSpec.level + 1; - dstDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; - wgpu::Texture dstTexture = device.CreateTexture(&dstDescriptor); + wgpu::Texture dstTexture; + if (copyWithinSameTexture) { + dstTexture = srcTexture; + } else { + wgpu::TextureDescriptor dstDescriptor; + dstDescriptor.dimension = wgpu::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 = wgpu::TextureFormat::RGBA8Unorm; + dstDescriptor.mipLevelCount = dstSpec.level + 1; + dstDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; + dstTexture = device.CreateTexture(&dstDescriptor); + } wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); @@ -759,6 +767,42 @@ TEST_P(CopyTests_T2T, Texture2DArrayCopyMultipleSlices) { {kWidth, kHeight, kCopyArrayLayerCount}); } +// Test copying one texture slice within the same texture. +TEST_P(CopyTests_T2T, CopyWithinSameTextureOneSlice) { + // TODO(jiawei.shao@intel.com): support texture-to-texture copy within same texture on D3D12 + // after D3D12 subresource tracking is implemented. + DAWN_SKIP_TEST_IF(IsD3D12()); + + constexpr uint32_t kWidth = 256u; + constexpr uint32_t kHeight = 128u; + constexpr uint32_t kLayers = 6u; + constexpr uint32_t kSrcBaseLayer = 0u; + constexpr uint32_t kDstBaseLayer = 3u; + constexpr uint32_t kCopyArrayLayerCount = 1u; + DoTest({kWidth, kHeight, 0, 0, 0, kLayers, kSrcBaseLayer}, + {kWidth, kHeight, 0, 0, 0, kLayers, kDstBaseLayer}, + {kWidth, kHeight, kCopyArrayLayerCount}, true); +} + +// Test copying multiple contiguous texture slices within the same texture with non-overlapped +// slices. +TEST_P(CopyTests_T2T, CopyWithinSameTextureNonOverlappedSlices) { + // TODO(jiawei.shao@intel.com): investigate why this test fails with swiftshader. + // TODO(jiawei.shao@intel.com): support texture-to-texture copy within same texture on D3D12 + // after D3D12 subresource tracking is implemented. + DAWN_SKIP_TEST_IF(IsSwiftshader() || IsD3D12()); + + constexpr uint32_t kWidth = 256u; + constexpr uint32_t kHeight = 128u; + constexpr uint32_t kLayers = 6u; + constexpr uint32_t kSrcBaseLayer = 0u; + constexpr uint32_t kDstBaseLayer = 3u; + constexpr uint32_t kCopyArrayLayerCount = 3u; + DoTest({kWidth, kHeight, 0, 0, 0, kLayers, kSrcBaseLayer}, + {kWidth, kHeight, 0, 0, 0, kLayers, kDstBaseLayer}, + {kWidth, kHeight, kCopyArrayLayerCount}, true); +} + TEST_P(CopyTests_T2T, TextureMip) { constexpr uint32_t kWidth = 256; constexpr uint32_t kHeight = 128; diff --git a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp index ffade8b2c9..a4e3bf0353 100644 --- a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp +++ b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp @@ -1257,6 +1257,117 @@ TEST_F(CopyCommandTest_T2T, CopyToMipmapOfNonSquareTexture) { maxMipmapLevel - 2, 0, {0, 0, 0}, {2, 1, 1}); } +// Test copy within the same texture +TEST_F(CopyCommandTest_T2T, CopyWithinSameTexture) { + wgpu::Texture texture = + Create2DTexture(32, 32, 2, 4, wgpu::TextureFormat::RGBA8Unorm, + wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst); + + // The base array layer of the copy source being equal to that of the copy destination is not + // allowed. + { + constexpr uint32_t kBaseArrayLayer = 0; + + // copyExtent.z == 1 + { + constexpr uint32_t kCopyArrayLayerCount = 1; + TestT2TCopy(utils::Expectation::Failure, texture, 0, kBaseArrayLayer, {0, 0, 0}, + texture, 0, kBaseArrayLayer, {2, 2, 0}, {1, 1, kCopyArrayLayerCount}); + } + + // copyExtent.z > 1 + { + constexpr uint32_t kCopyArrayLayerCount = 2; + TestT2TCopy(utils::Expectation::Failure, texture, 0, kBaseArrayLayer, {0, 0, 0}, + texture, 0, kBaseArrayLayer, {2, 2, 0}, {1, 1, kCopyArrayLayerCount}); + } + } + + // The array slices of the source involved in the copy have no overlap with those of the + // destination is allowed. + { + constexpr uint32_t kCopyArrayLayerCount = 2; + + // srcBaseArrayLayer < dstBaseArrayLayer + { + constexpr uint32_t kSrcBaseArrayLayer = 0; + constexpr uint32_t kDstBaseArrayLayer = kSrcBaseArrayLayer + kCopyArrayLayerCount; + + TestT2TCopy(utils::Expectation::Success, texture, 0, kSrcBaseArrayLayer, {0, 0, 0}, + texture, 0, kDstBaseArrayLayer, {0, 0, 0}, {1, 1, kCopyArrayLayerCount}); + } + + // srcBaseArrayLayer > dstBaseArrayLayer + { + constexpr uint32_t kSrcBaseArrayLayer = 2; + constexpr uint32_t kDstBaseArrayLayer = kSrcBaseArrayLayer - kCopyArrayLayerCount; + TestT2TCopy(utils::Expectation::Success, texture, 0, kSrcBaseArrayLayer, {0, 0, 0}, + texture, 0, kDstBaseArrayLayer, {0, 0, 0}, {1, 1, kCopyArrayLayerCount}); + } + } + + // Copy between different mipmap levels is allowed. + { + constexpr uint32_t kSrcMipLevel = 0; + constexpr uint32_t kDstMipLevel = 1; + + // Copy one slice + { + constexpr uint32_t kCopyArrayLayerCount = 1; + TestT2TCopy(utils::Expectation::Success, texture, kSrcMipLevel, 0, {0, 0, 0}, texture, + kDstMipLevel, 0, {1, 1, 0}, {1, 1, kCopyArrayLayerCount}); + } + + // The base array layer of the copy source is equal to that of the copy destination. + { + constexpr uint32_t kCopyArrayLayerCount = 2; + constexpr uint32_t kBaseArrayLayer = 0; + + TestT2TCopy(utils::Expectation::Success, texture, kSrcMipLevel, kBaseArrayLayer, + {0, 0, 0}, texture, kDstMipLevel, kBaseArrayLayer, {1, 1, 0}, + {1, 1, kCopyArrayLayerCount}); + } + + // The array slices of the source involved in the copy have overlaps with those of the + // destination, and the copy areas have overlaps. + { + constexpr uint32_t kCopyArrayLayerCount = 2; + + constexpr uint32_t kSrcBaseArrayLayer = 0; + constexpr uint32_t kDstBaseArrayLayer = 1; + ASSERT(kSrcBaseArrayLayer + kCopyArrayLayerCount > kDstBaseArrayLayer); + + constexpr wgpu::Origin3D kCopyOrigin = {0, 0, 0}; + constexpr wgpu::Extent3D kCopyExtent = {1, 1, kCopyArrayLayerCount}; + + TestT2TCopy(utils::Expectation::Success, texture, kSrcMipLevel, kSrcBaseArrayLayer, + kCopyOrigin, texture, kDstMipLevel, kDstBaseArrayLayer, kCopyOrigin, + kCopyExtent); + } + } + + // The array slices of the source involved in the copy have overlaps with those of the + // destination is not allowed. + { + constexpr uint32_t kMipmapLevel = 0; + constexpr uint32_t kMinBaseArrayLayer = 0; + constexpr uint32_t kMaxBaseArrayLayer = 1; + constexpr uint32_t kCopyArrayLayerCount = 3; + ASSERT(kMinBaseArrayLayer + kCopyArrayLayerCount > kMaxBaseArrayLayer); + + constexpr wgpu::Extent3D kCopyExtent = {4, 4, kCopyArrayLayerCount}; + + const wgpu::Origin3D srcOrigin = {0, 0, 0}; + const wgpu::Origin3D dstOrigin = {4, 4, 0}; + TestT2TCopy(utils::Expectation::Failure, texture, kMipmapLevel, kMinBaseArrayLayer, + srcOrigin, texture, kMipmapLevel, kMaxBaseArrayLayer, dstOrigin, kCopyExtent); + } + + // Copy between different mipmap levels and array slices is allowed. + TestT2TCopy(utils::Expectation::Success, texture, 0, 1, {0, 0, 0}, texture, 1, 0, {1, 1, 0}, + {1, 1, 1}); +} + class CopyCommandTest_CompressedTextureFormats : public CopyCommandTest { public: CopyCommandTest_CompressedTextureFormats() : CopyCommandTest() {