diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 935f5ff00c..252a7781a5 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -60,6 +60,7 @@ add_executable(nxt_end2end_tests ${END2END_TESTS_DIR}/BasicTests.cpp ${END2END_TESTS_DIR}/BufferTests.cpp ${END2END_TESTS_DIR}/InputStateTests.cpp + ${END2END_TESTS_DIR}/CopyTests.cpp ${TESTS_DIR}/End2EndTestsMain.cpp ${TESTS_DIR}/NXTTest.cpp ${TESTS_DIR}/NXTTest.h diff --git a/src/tests/end2end/BasicTests.cpp b/src/tests/end2end/BasicTests.cpp index d0d03b05e5..399b10b641 100644 --- a/src/tests/end2end/BasicTests.cpp +++ b/src/tests/end2end/BasicTests.cpp @@ -34,66 +34,4 @@ TEST_P(BasicTests, BufferSetSubData) { EXPECT_BUFFER_U32_EQ(value, buffer, 0); } -TEST_P(BasicTests, ReadPixelsTest) { - RGBA8 red(255, 0, 0, 255); - nxt::Buffer buffer = utils::CreateFrozenBufferFromData(device, &red, sizeof(red), nxt::BufferUsageBit::TransferSrc); - - nxt::Texture texture = device.CreateTextureBuilder() - .SetDimension(nxt::TextureDimension::e2D) - .SetExtent(1, 1, 1) - .SetMipLevels(1) - .SetAllowedUsage(nxt::TextureUsageBit::TransferSrc | nxt::TextureUsageBit::TransferDst) - .SetFormat(nxt::TextureFormat::R8G8B8A8Unorm) - .GetResult(); - - nxt::CommandBuffer commands = device.CreateCommandBufferBuilder() - .TransitionTextureUsage(texture, nxt::TextureUsageBit::TransferDst) - .CopyBufferToTexture(buffer, 0, 256, texture, 0, 0, 0, 1, 1, 1, 0) - .GetResult(); - - queue.Submit(1, &commands); - - EXPECT_PIXEL_RGBA8_EQ(red, texture, 0, 0); -} - -TEST_P(BasicTests, Buffer2Texture2Buffer) { - static constexpr unsigned int kSize = 64; - - uint8_t data[4 * kSize * kSize] = {}; - for (unsigned int i = 0; i < 4 * kSize * kSize; ++i) { - data[i] = i % 256; - } - - nxt::Buffer srcBuffer = utils::CreateFrozenBufferFromData(device, data, 4 * kSize * kSize, nxt::BufferUsageBit::TransferSrc); - - nxt::Texture texture = device.CreateTextureBuilder() - .SetDimension(nxt::TextureDimension::e2D) - .SetExtent(kSize, kSize, 1) - .SetFormat(nxt::TextureFormat::R8G8B8A8Unorm) - .SetMipLevels(1) - .SetAllowedUsage(nxt::TextureUsageBit::TransferDst | nxt::TextureUsageBit::TransferSrc) - .GetResult(); - - nxt::Buffer dstBuffer = device.CreateBufferBuilder() - .SetAllowedUsage(nxt::BufferUsageBit::TransferDst | nxt::BufferUsageBit::TransferSrc) - .SetInitialUsage(nxt::BufferUsageBit::TransferDst) - .SetSize(4 * kSize * kSize) - .GetResult(); - - nxt::CommandBuffer commands = device.CreateCommandBufferBuilder() - .TransitionTextureUsage(texture, nxt::TextureUsageBit::TransferDst) - .CopyBufferToTexture(srcBuffer, 0, 0, texture, 0, 0, 0, kSize, kSize, 1, 0) - - .TransitionTextureUsage(texture, nxt::TextureUsageBit::TransferSrc) - .TransitionBufferUsage(dstBuffer, nxt::BufferUsageBit::TransferDst) - .CopyTextureToBuffer(texture, 0, 0, 0, kSize, kSize, 1, 0, dstBuffer, 0, 0) - - .GetResult(); - - queue.Submit(1, &commands); - - const uint32_t* dataView = reinterpret_cast(data); - EXPECT_BUFFER_U32_RANGE_EQ(dataView, dstBuffer, 0, kSize * kSize); -} - NXT_INSTANTIATE_TEST(BasicTests, D3D12Backend, MetalBackend, OpenGLBackend) diff --git a/src/tests/end2end/CopyTests.cpp b/src/tests/end2end/CopyTests.cpp new file mode 100644 index 0000000000..d7ab9b5bea --- /dev/null +++ b/src/tests/end2end/CopyTests.cpp @@ -0,0 +1,517 @@ +// 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 "tests/NXTTest.h" + +#include +#include "common/Constants.h" +#include "common/Math.h" +#include "utils/NXTHelpers.h" + +class CopyTests : public NXTTest { + protected: + static constexpr unsigned int kBytesPerTexel = 4; + + struct TextureSpec { + uint32_t width; + uint32_t height; + uint32_t x; + uint32_t y; + uint32_t copyWidth; + uint32_t copyHeight; + uint32_t level; + }; + + struct BufferSpec { + uint32_t size; + uint32_t offset; + uint32_t rowPitch; + }; + + BufferSpec MinimumBufferSpec(uint32_t width, uint32_t height) { + uint32_t rowPitch = Align(width * kBytesPerTexel, kTextureRowPitchAlignment); + return { rowPitch * (height - 1) + width * kBytesPerTexel, 0, rowPitch }; + } + + static void PackTextureData(const RGBA8* srcData, uint32_t width, uint32_t height, uint32_t srcTexelsPerRow, RGBA8* dstData, uint32_t dstTexelsPerRow) { + for (unsigned int y = 0; y < height; ++y) { + for (unsigned int x = 0; x < width; ++x) { + unsigned int src = x + y * srcTexelsPerRow; + unsigned int dst = x + y * dstTexelsPerRow; + dstData[dst] = srcData[src]; + } + } + } +}; + +class CopyTests_T2B : public CopyTests { + protected: + static void FillTextureData(uint32_t width, uint32_t height, uint32_t texelsPerRow, RGBA8* data) { + for (unsigned int y = 0; y < height; ++y) { + for (unsigned int x = 0; x < width; ++x) { + unsigned int i = x + y * texelsPerRow; + data[i] = RGBA8( + static_cast(x % 256), + static_cast(y % 256), + static_cast(x / 256), + static_cast(y / 256)); + } + } + } + + void DoTest(const TextureSpec& textureSpec, const BufferSpec& bufferSpec) { + // Create a texture that is `width` x `height` with (`level` + 1) mip levels. + nxt::Texture texture = device.CreateTextureBuilder() + .SetDimension(nxt::TextureDimension::e2D) + .SetExtent(textureSpec.width, textureSpec.height, 1) + .SetFormat(nxt::TextureFormat::R8G8B8A8Unorm) + .SetMipLevels(textureSpec.level + 1) + .SetAllowedUsage(nxt::TextureUsageBit::TransferDst | nxt::TextureUsageBit::TransferSrc) + .GetResult(); + + uint32_t width = textureSpec.width >> textureSpec.level; + uint32_t height = textureSpec.height >> textureSpec.level; + uint32_t rowPitch = Align(kBytesPerTexel * width, kTextureRowPitchAlignment); + uint32_t texelsPerRow = rowPitch / kBytesPerTexel; + uint32_t texelCount = texelsPerRow * (height - 1) + width; + + // Create an upload buffer and use it to populate the `level` mip of the texture + std::vector textureData(texelCount); + FillTextureData(width, height, rowPitch / kBytesPerTexel, textureData.data()); + nxt::Buffer uploadBuffer = utils::CreateFrozenBufferFromData(device, textureData.data(), static_cast(sizeof(RGBA8) * textureData.size()), nxt::BufferUsageBit::TransferSrc); + + nxt::CommandBuffer commands[2]; + + commands[0] = device.CreateCommandBufferBuilder() + .TransitionTextureUsage(texture, nxt::TextureUsageBit::TransferDst) + .CopyBufferToTexture(uploadBuffer, 0, rowPitch, texture, 0, 0, 0, width, height, 1, textureSpec.level) + .GetResult(); + + // Create a buffer of size `size` and populate it with empty data (0,0,0,0) + // Note: Prepopulating the buffer with empty data ensures that there is not random data in the expectation + // and helps ensure that the padding due to the row pitch is not modified by the copy + nxt::Buffer buffer = device.CreateBufferBuilder() + .SetSize(bufferSpec.size) + .SetAllowedUsage(nxt::BufferUsageBit::TransferSrc | nxt::BufferUsageBit::TransferDst) + .SetInitialUsage(nxt::BufferUsageBit::TransferDst) + .GetResult(); + std::vector emptyData(bufferSpec.size / kBytesPerTexel); + buffer.SetSubData(0, static_cast(emptyData.size()), reinterpret_cast(emptyData.data())); + + // Copy the region [(`x`, `y`), (`x + copyWidth, `y + copyWidth`)] from the `level` mip into the buffer at the specified `offset` and `rowPitch` + commands[1] = device.CreateCommandBufferBuilder() + .TransitionTextureUsage(texture, nxt::TextureUsageBit::TransferSrc) + .TransitionBufferUsage(buffer, nxt::BufferUsageBit::TransferDst) + .CopyTextureToBuffer(texture, textureSpec.x, textureSpec.y, 0, textureSpec.copyWidth, textureSpec.copyHeight, 1, textureSpec.level, buffer, bufferSpec.offset, bufferSpec.rowPitch) + .GetResult(); + + queue.Submit(2, commands); + + // Pack the data used to create the upload buffer in the specified copy region to have the same format as the expected buffer data. + std::vector expected(bufferSpec.rowPitch / kBytesPerTexel * (textureSpec.copyHeight - 1) + textureSpec.copyWidth); + PackTextureData( + &textureData[textureSpec.x + textureSpec.y * (rowPitch / kBytesPerTexel)], + textureSpec.copyWidth, + textureSpec.copyHeight, + rowPitch / kBytesPerTexel, + expected.data(), + bufferSpec.rowPitch / kBytesPerTexel); + + EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast(expected.data()), buffer, bufferSpec.offset, static_cast(expected.size())) << + "Texture to Buffer copy failed copying region [(" << textureSpec.x << ", " << textureSpec.y << "), (" << textureSpec.x + textureSpec.copyWidth << ", " << textureSpec.y + textureSpec.copyHeight << + ")) from " << textureSpec.width << " x " << textureSpec.height << " texture at mip level " << textureSpec.level << + " to " << bufferSpec.size << "-byte buffer with offset " << bufferSpec.offset << " and row pitch " << bufferSpec.rowPitch << std::endl; + } + + +}; + +class CopyTests_B2T : public CopyTests { +protected: + static void FillBufferData(RGBA8* data, size_t count) { + for (size_t i = 0; i < count; ++i) { + data[i] = RGBA8( + static_cast(i % 256), + static_cast((i / 256) % 256), + static_cast((i / 256 / 256) % 255), + 255); + } + } + + void DoTest(const TextureSpec& textureSpec, const BufferSpec& bufferSpec) { + // Create a buffer of size `size` and populate it with data + nxt::Buffer buffer = device.CreateBufferBuilder() + .SetSize(bufferSpec.size) + .SetAllowedUsage(nxt::BufferUsageBit::TransferSrc | nxt::BufferUsageBit::TransferDst) + .SetInitialUsage(nxt::BufferUsageBit::TransferDst) + .GetResult(); + std::vector bufferData(bufferSpec.size / kBytesPerTexel); + FillBufferData(bufferData.data(), bufferData.size()); + buffer.SetSubData(0, static_cast(bufferData.size()), reinterpret_cast(bufferData.data())); + + // Create a texture that is `width` x `height` with (`level` + 1) mip levels. + nxt::Texture texture = device.CreateTextureBuilder() + .SetDimension(nxt::TextureDimension::e2D) + .SetExtent(textureSpec.width, textureSpec.height, 1) + .SetFormat(nxt::TextureFormat::R8G8B8A8Unorm) + .SetMipLevels(textureSpec.level + 1) + .SetAllowedUsage(nxt::TextureUsageBit::TransferDst | nxt::TextureUsageBit::TransferSrc) + .GetResult(); + + nxt::CommandBuffer commands[2]; + + // Create an upload buffer filled with empty data and use it to populate the `level` mip of the texture + // Note: Prepopulating the texture with empty data ensures that there is not random data in the expectation + // and helps ensure that the padding due to the row pitch is not modified by the copy + { + uint32_t width = textureSpec.width >> textureSpec.level; + uint32_t height = textureSpec.height >> textureSpec.level; + uint32_t rowPitch = Align(kBytesPerTexel * width, kTextureRowPitchAlignment); + uint32_t texelsPerRow = rowPitch / kBytesPerTexel; + uint32_t texelCount = texelsPerRow * (height - 1) + width; + + std::vector emptyData(texelCount); + nxt::Buffer uploadBuffer = utils::CreateFrozenBufferFromData(device, emptyData.data(), static_cast(sizeof(RGBA8) * emptyData.size()), nxt::BufferUsageBit::TransferSrc); + + commands[0] = device.CreateCommandBufferBuilder() + .TransitionTextureUsage(texture, nxt::TextureUsageBit::TransferDst) + .CopyBufferToTexture(uploadBuffer, 0, rowPitch, texture, 0, 0, 0, width, height, 1, textureSpec.level) + .GetResult(); + } + + // Copy to the region [(`x`, `y`), (`x + copyWidth, `y + copyWidth`)] at the `level` mip from the buffer at the specified `offset` and `rowPitch` + commands[1] = device.CreateCommandBufferBuilder() + .TransitionBufferUsage(buffer, nxt::BufferUsageBit::TransferSrc) + .TransitionTextureUsage(texture, nxt::TextureUsageBit::TransferDst) + .CopyBufferToTexture(buffer, bufferSpec.offset, bufferSpec.rowPitch, texture, textureSpec.x, textureSpec.y, 0, textureSpec.copyWidth, textureSpec.copyHeight, 1, textureSpec.level) + .GetResult(); + + queue.Submit(2, commands); + + // Pack the data used to create the buffer in the specified copy region to have the same format as the expected texture data. + uint32_t rowPitch = Align(kBytesPerTexel * textureSpec.copyWidth, kTextureRowPitchAlignment); + std::vector expected(rowPitch / kBytesPerTexel * (textureSpec.copyHeight - 1) + textureSpec.copyWidth); + PackTextureData(&bufferData[bufferSpec.offset / kBytesPerTexel], textureSpec.copyWidth, textureSpec.copyHeight, bufferSpec.rowPitch / kBytesPerTexel, expected.data(), textureSpec.copyWidth); + + EXPECT_TEXTURE_RGBA8_EQ(expected.data(), texture, textureSpec.x, textureSpec.y, textureSpec.copyWidth, textureSpec.copyHeight, textureSpec.level) << + "Buffer to Texture copy failed copying " + << bufferSpec.size << "-byte buffer with offset " << bufferSpec.offset << " and row pitch " << bufferSpec.rowPitch << " to [(" + << textureSpec.x << ", " << textureSpec.y << "), (" << textureSpec.x + textureSpec.copyWidth << ", " << textureSpec.y + textureSpec.copyHeight << + ")) region of " << textureSpec.width << " x " << textureSpec.height << " texture at mip level " << textureSpec.level << std::endl; + } + + +}; + +// Test that copying an entire texture with 256-byte aligned dimensions works +TEST_P(CopyTests_T2B, FullTextureAligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, MinimumBufferSpec(kWidth, kHeight)); +} + +// Test that copying an entire texture without 256-byte aligned dimensions works +TEST_P(CopyTests_T2B, FullTextureUnaligned) { + constexpr uint32_t kWidth = 259; + constexpr uint32_t kHeight = 127; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, MinimumBufferSpec(kWidth, kHeight)); +} + +// Test that reading pixels from a 256-byte aligned texture works +TEST_P(CopyTests_T2B, PixelReadAligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + BufferSpec pixelBuffer = MinimumBufferSpec(1, 1); + DoTest({ kWidth, kHeight, 0, 0, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth - 1, 0, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, 0, kHeight - 1, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth - 1, kHeight - 1, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth / 3, kHeight / 7, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth / 7, kHeight / 3, 1, 1, 0 }, pixelBuffer); +} + +// Test that copying pixels from a texture that is not 256-byte aligned works +TEST_P(CopyTests_T2B, PixelReadUnaligned) { + constexpr uint32_t kWidth = 259; + constexpr uint32_t kHeight = 127; + BufferSpec pixelBuffer = MinimumBufferSpec(1, 1); + DoTest({ kWidth, kHeight, 0, 0, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth - 1, 0, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, 0, kHeight - 1, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth - 1, kHeight - 1, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth / 3, kHeight / 7, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth / 7, kHeight / 3, 1, 1, 0 }, pixelBuffer); +} + +// Test that copying regions with 256-byte aligned sizes works +TEST_P(CopyTests_T2B, TextureRegionAligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + for (unsigned int w : {64, 128, 256}) { + for (unsigned int h : { 16, 32, 48 }) { + DoTest({ kWidth, kHeight, 0, 0, w, h, 0 }, MinimumBufferSpec(w, h)); + } + } +} + +// Test that copying regions without 256-byte aligned sizes works +TEST_P(CopyTests_T2B, TextureRegionUnaligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + for (unsigned int w : {13, 63, 65}) { + for (unsigned int h : { 17, 19, 63 }) { + DoTest({ kWidth, kHeight, 0, 0, w, h, 0 }, MinimumBufferSpec(w, h)); + } + } +} + +// Test that copying mips with 256-byte aligned sizes works +TEST_P(CopyTests_T2B, TextureMipAligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + for (unsigned int i = 1; i < 4; ++i) { + DoTest({ kWidth, kHeight, 0, 0, kWidth >> i, kHeight >> i, i }, MinimumBufferSpec(kWidth >> i, kHeight >> i)); + } +} + +// Test that copying mips without 256-byte aligned sizes works +TEST_P(CopyTests_T2B, TextureMipUnaligned) { + constexpr uint32_t kWidth = 259; + constexpr uint32_t kHeight = 127; + for (unsigned int i = 1; i < 4; ++i) { + DoTest({ kWidth, kHeight, 0, 0, kWidth >> i, kHeight >> i, i }, MinimumBufferSpec(kWidth >> i, kHeight >> i)); + } +} + +// Test that copying with a 512-byte aligned buffer offset works +TEST_P(CopyTests_T2B, OffsetBufferAligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + for (unsigned int i = 0; i < 3; ++i) { + BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight); + uint32_t offset = 512 * i; + bufferSpec.size += offset; + bufferSpec.offset += offset; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec); + } +} + +// Test that copying without a 512-byte aligned buffer offset works +TEST_P(CopyTests_T2B, OffsetBufferUnaligned) { + if (IsD3D12()) { + printf("TODO(enga@google.com): Test skipped because unaligned buffer offsets not supported in D3D12\n"); + return; + } + constexpr uint32_t kWidth = 128; + constexpr uint32_t kHeight = 128; + for (uint32_t i = kBytesPerTexel; i < 512; i += kBytesPerTexel * 9) { + BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight); + bufferSpec.size += i; + bufferSpec.offset += i; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec); + } +} + +// Test that copying without a 512-byte aligned buffer offset that is greater than the row pitch works +TEST_P(CopyTests_T2B, OffsetBufferUnalignedSmallRowPitch) { + if (IsD3D12()) { + printf("TODO(enga@google.com): Test skipped because unaligned buffer offsets not supported in D3D12\n"); + return; + } + constexpr uint32_t kWidth = 32; + constexpr uint32_t kHeight = 128; + for (uint32_t i = 256 + kBytesPerTexel; i < 512; i += kBytesPerTexel * 9) { + BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight); + bufferSpec.size += i; + bufferSpec.offset += i; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec); + } +} + +// Test that copying with a greater row pitch than needed on a 256-byte aligned texture works +TEST_P(CopyTests_T2B, RowPitchAligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight); + for (unsigned int i = 1; i < 4; ++i) { + bufferSpec.rowPitch += 256; + bufferSpec.size += 256 * kHeight; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec); + } +} + +// Test that copying with a greater row pitch than needed on a texture that is not 256-byte aligned works +TEST_P(CopyTests_T2B, RowPitchUnaligned) { + constexpr uint32_t kWidth = 259; + constexpr uint32_t kHeight = 127; + BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight); + for (unsigned int i = 1; i < 4; ++i) { + bufferSpec.rowPitch += 256; + bufferSpec.size += 256 * kHeight; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec); + } +} + +NXT_INSTANTIATE_TEST(CopyTests_T2B, D3D12Backend, MetalBackend, OpenGLBackend) + +// Test that copying an entire texture with 256-byte aligned dimensions works +TEST_P(CopyTests_B2T, FullTextureAligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, MinimumBufferSpec(kWidth, kHeight)); +} + +// Test that copying an entire texture without 256-byte aligned dimensions works +TEST_P(CopyTests_B2T, FullTextureUnaligned) { + constexpr uint32_t kWidth = 259; + constexpr uint32_t kHeight = 127; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, MinimumBufferSpec(kWidth, kHeight)); +} + +// Test that reading pixels from a 256-byte aligned texture works +TEST_P(CopyTests_B2T, PixelReadAligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + BufferSpec pixelBuffer = MinimumBufferSpec(1, 1); + DoTest({ kWidth, kHeight, 0, 0, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth - 1, 0, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, 0, kHeight - 1, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth - 1, kHeight - 1, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth / 3, kHeight / 7, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth / 7, kHeight / 3, 1, 1, 0 }, pixelBuffer); +} + +// Test that copying pixels from a texture that is not 256-byte aligned works +TEST_P(CopyTests_B2T, PixelReadUnaligned) { + constexpr uint32_t kWidth = 259; + constexpr uint32_t kHeight = 127; + BufferSpec pixelBuffer = MinimumBufferSpec(1, 1); + DoTest({ kWidth, kHeight, 0, 0, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth - 1, 0, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, 0, kHeight - 1, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth - 1, kHeight - 1, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth / 3, kHeight / 7, 1, 1, 0 }, pixelBuffer); + DoTest({ kWidth, kHeight, kWidth / 7, kHeight / 3, 1, 1, 0 }, pixelBuffer); +} + +// Test that copying regions with 256-byte aligned sizes works +TEST_P(CopyTests_B2T, TextureRegionAligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + for (unsigned int w : {64, 128, 256}) { + for (unsigned int h : { 16, 32, 48 }) { + DoTest({ kWidth, kHeight, 0, 0, w, h, 0 }, MinimumBufferSpec(w, h)); + } + } +} + +// Test that copying regions without 256-byte aligned sizes works +TEST_P(CopyTests_B2T, TextureRegionUnaligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + for (unsigned int w : {13, 63, 65}) { + for (unsigned int h : { 17, 19, 63 }) { + DoTest({ kWidth, kHeight, 0, 0, w, h, 0 }, MinimumBufferSpec(w, h)); + } + } +} + +// Test that copying mips with 256-byte aligned sizes works +TEST_P(CopyTests_B2T, TextureMipAligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + for (unsigned int i = 1; i < 4; ++i) { + DoTest({ kWidth, kHeight, 0, 0, kWidth >> i, kHeight >> i, i }, MinimumBufferSpec(kWidth >> i, kHeight >> i)); + } +} + +// Test that copying mips without 256-byte aligned sizes works +TEST_P(CopyTests_B2T, TextureMipUnaligned) { + constexpr uint32_t kWidth = 259; + constexpr uint32_t kHeight = 127; + for (unsigned int i = 1; i < 4; ++i) { + DoTest({ kWidth, kHeight, 0, 0, kWidth >> i, kHeight >> i, i }, MinimumBufferSpec(kWidth >> i, kHeight >> i)); + } +} + +// Test that copying with a 512-byte aligned buffer offset works +TEST_P(CopyTests_B2T, OffsetBufferAligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + for (unsigned int i = 0; i < 3; ++i) { + BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight); + uint32_t offset = 512 * i; + bufferSpec.size += offset; + bufferSpec.offset += offset; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec); + } +} + +// Test that copying without a 512-byte aligned buffer offset works +TEST_P(CopyTests_B2T, OffsetBufferUnaligned) { + if (IsD3D12()) { + printf("TODO(enga@google.com): Test skipped because unaligned buffer offsets not supported in D3D12\n"); + return; + } + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + for (uint32_t i = kBytesPerTexel; i < 512; i += kBytesPerTexel * 9) { + BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight); + bufferSpec.size += i; + bufferSpec.offset += i; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec); + } +} + +// Test that copying without a 512-byte aligned buffer offset that is greater than the row pitch works +TEST_P(CopyTests_B2T, OffsetBufferUnalignedSmallRowPitch) { + if (IsD3D12()) { + printf("TODO(enga@google.com): Test skipped because unaligned buffer offsets not supported in D3D12\n"); + return; + } + constexpr uint32_t kWidth = 32; + constexpr uint32_t kHeight = 128; + for (uint32_t i = 256 + kBytesPerTexel; i < 512; i += kBytesPerTexel * 9) { + BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight); + bufferSpec.size += i; + bufferSpec.offset += i; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec); + } +} + +// Test that copying with a greater row pitch than needed on a 256-byte aligned texture works +TEST_P(CopyTests_B2T, RowPitchAligned) { + constexpr uint32_t kWidth = 256; + constexpr uint32_t kHeight = 128; + BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight); + for (unsigned int i = 1; i < 4; ++i) { + bufferSpec.rowPitch += 256; + bufferSpec.size += 256 * kHeight; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec); + } +} + +// Test that copying with a greater row pitch than needed on a texture that is not 256-byte aligned works +TEST_P(CopyTests_B2T, RowPitchUnaligned) { + constexpr uint32_t kWidth = 259; + constexpr uint32_t kHeight = 127; + BufferSpec bufferSpec = MinimumBufferSpec(kWidth, kHeight); + for (unsigned int i = 1; i < 4; ++i) { + bufferSpec.rowPitch += 256; + bufferSpec.size += 256 * kHeight; + DoTest({ kWidth, kHeight, 0, 0, kWidth, kHeight, 0 }, bufferSpec); + } +} + +NXT_INSTANTIATE_TEST(CopyTests_B2T, D3D12Backend, MetalBackend, OpenGLBackend)