| // Copyright 2017 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include <limits> |
| #include <utility> |
| #include <vector> |
| |
| #include "dawn/native/CommandAllocator.h" |
| #include "gtest/gtest.h" |
| |
| namespace dawn::native { |
| |
| // Definition of the command types used in the tests |
| enum class CommandType { |
| Draw, |
| Pipeline, |
| PushConstants, |
| Big, |
| Small, |
| }; |
| |
| struct CommandDraw { |
| uint32_t first; |
| uint32_t count; |
| }; |
| |
| struct CommandPipeline { |
| uint64_t pipeline; |
| uint32_t attachmentPoint; |
| }; |
| |
| struct CommandPushConstants { |
| uint8_t size; |
| uint8_t offset; |
| }; |
| |
| constexpr int kBigBufferSize = 65536; |
| |
| struct CommandBig { |
| uint32_t buffer[kBigBufferSize]; |
| }; |
| |
| struct CommandSmall { |
| uint16_t data; |
| }; |
| |
| // Test allocating nothing works |
| TEST(CommandAllocator, DoNothingAllocator) { |
| CommandAllocator allocator; |
| } |
| |
| // Test iterating over nothing works |
| TEST(CommandAllocator, DoNothingAllocatorWithIterator) { |
| CommandAllocator allocator; |
| CommandIterator iterator(std::move(allocator)); |
| iterator.MakeEmptyAsDataWasDestroyed(); |
| } |
| |
| // Test basic usage of allocator + iterator |
| TEST(CommandAllocator, Basic) { |
| CommandAllocator allocator; |
| |
| uint64_t myPipeline = 0xDEADBEEFBEEFDEAD; |
| uint32_t myAttachmentPoint = 2; |
| uint32_t myFirst = 42; |
| uint32_t myCount = 16; |
| |
| { |
| CommandPipeline* pipeline = allocator.Allocate<CommandPipeline>(CommandType::Pipeline); |
| pipeline->pipeline = myPipeline; |
| pipeline->attachmentPoint = myAttachmentPoint; |
| |
| CommandDraw* draw = allocator.Allocate<CommandDraw>(CommandType::Draw); |
| draw->first = myFirst; |
| draw->count = myCount; |
| } |
| |
| { |
| CommandIterator iterator(std::move(allocator)); |
| CommandType type; |
| |
| bool hasNext = iterator.NextCommandId(&type); |
| ASSERT_TRUE(hasNext); |
| ASSERT_EQ(type, CommandType::Pipeline); |
| |
| CommandPipeline* pipeline = iterator.NextCommand<CommandPipeline>(); |
| ASSERT_EQ(pipeline->pipeline, myPipeline); |
| ASSERT_EQ(pipeline->attachmentPoint, myAttachmentPoint); |
| |
| hasNext = iterator.NextCommandId(&type); |
| ASSERT_TRUE(hasNext); |
| ASSERT_EQ(type, CommandType::Draw); |
| |
| CommandDraw* draw = iterator.NextCommand<CommandDraw>(); |
| ASSERT_EQ(draw->first, myFirst); |
| ASSERT_EQ(draw->count, myCount); |
| |
| hasNext = iterator.NextCommandId(&type); |
| ASSERT_FALSE(hasNext); |
| |
| iterator.MakeEmptyAsDataWasDestroyed(); |
| } |
| } |
| |
| // Test basic usage of allocator + iterator with data |
| TEST(CommandAllocator, BasicWithData) { |
| CommandAllocator allocator; |
| |
| uint8_t mySize = 8; |
| uint8_t myOffset = 3; |
| uint32_t myValues[5] = {6, 42, 0xFFFFFFFF, 0, 54}; |
| |
| { |
| CommandPushConstants* pushConstants = |
| allocator.Allocate<CommandPushConstants>(CommandType::PushConstants); |
| pushConstants->size = mySize; |
| pushConstants->offset = myOffset; |
| |
| uint32_t* values = allocator.AllocateData<uint32_t>(5); |
| for (size_t i = 0; i < 5; i++) { |
| values[i] = myValues[i]; |
| } |
| } |
| |
| { |
| CommandIterator iterator(std::move(allocator)); |
| CommandType type; |
| |
| bool hasNext = iterator.NextCommandId(&type); |
| ASSERT_TRUE(hasNext); |
| ASSERT_EQ(type, CommandType::PushConstants); |
| |
| CommandPushConstants* pushConstants = iterator.NextCommand<CommandPushConstants>(); |
| ASSERT_EQ(pushConstants->size, mySize); |
| ASSERT_EQ(pushConstants->offset, myOffset); |
| |
| uint32_t* values = iterator.NextData<uint32_t>(5); |
| for (size_t i = 0; i < 5; i++) { |
| ASSERT_EQ(values[i], myValues[i]); |
| } |
| |
| hasNext = iterator.NextCommandId(&type); |
| ASSERT_FALSE(hasNext); |
| |
| iterator.MakeEmptyAsDataWasDestroyed(); |
| } |
| } |
| |
| // Test basic iterating several times |
| TEST(CommandAllocator, MultipleIterations) { |
| CommandAllocator allocator; |
| |
| uint32_t myFirst = 42; |
| uint32_t myCount = 16; |
| |
| { |
| CommandDraw* draw = allocator.Allocate<CommandDraw>(CommandType::Draw); |
| draw->first = myFirst; |
| draw->count = myCount; |
| } |
| |
| { |
| CommandIterator iterator(std::move(allocator)); |
| CommandType type; |
| |
| // First iteration |
| bool hasNext = iterator.NextCommandId(&type); |
| ASSERT_TRUE(hasNext); |
| ASSERT_EQ(type, CommandType::Draw); |
| |
| CommandDraw* draw = iterator.NextCommand<CommandDraw>(); |
| ASSERT_EQ(draw->first, myFirst); |
| ASSERT_EQ(draw->count, myCount); |
| |
| hasNext = iterator.NextCommandId(&type); |
| ASSERT_FALSE(hasNext); |
| |
| // Second iteration |
| hasNext = iterator.NextCommandId(&type); |
| ASSERT_TRUE(hasNext); |
| ASSERT_EQ(type, CommandType::Draw); |
| |
| draw = iterator.NextCommand<CommandDraw>(); |
| ASSERT_EQ(draw->first, myFirst); |
| ASSERT_EQ(draw->count, myCount); |
| |
| hasNext = iterator.NextCommandId(&type); |
| ASSERT_FALSE(hasNext); |
| |
| iterator.MakeEmptyAsDataWasDestroyed(); |
| } |
| } |
| // Test large commands work |
| TEST(CommandAllocator, LargeCommands) { |
| CommandAllocator allocator; |
| |
| const int kCommandCount = 5; |
| |
| uint32_t count = 0; |
| for (int i = 0; i < kCommandCount; i++) { |
| CommandBig* big = allocator.Allocate<CommandBig>(CommandType::Big); |
| for (int j = 0; j < kBigBufferSize; j++) { |
| big->buffer[j] = count++; |
| } |
| } |
| |
| CommandIterator iterator(std::move(allocator)); |
| CommandType type; |
| count = 0; |
| int numCommands = 0; |
| while (iterator.NextCommandId(&type)) { |
| ASSERT_EQ(type, CommandType::Big); |
| |
| CommandBig* big = iterator.NextCommand<CommandBig>(); |
| for (int i = 0; i < kBigBufferSize; i++) { |
| ASSERT_EQ(big->buffer[i], count); |
| count++; |
| } |
| numCommands++; |
| } |
| ASSERT_EQ(numCommands, kCommandCount); |
| |
| iterator.MakeEmptyAsDataWasDestroyed(); |
| } |
| |
| // Test many small commands work |
| TEST(CommandAllocator, ManySmallCommands) { |
| CommandAllocator allocator; |
| |
| // Stay under max representable uint16_t |
| const int kCommandCount = 50000; |
| |
| uint16_t count = 0; |
| for (int i = 0; i < kCommandCount; i++) { |
| CommandSmall* small = allocator.Allocate<CommandSmall>(CommandType::Small); |
| small->data = count++; |
| } |
| |
| CommandIterator iterator(std::move(allocator)); |
| CommandType type; |
| count = 0; |
| int numCommands = 0; |
| while (iterator.NextCommandId(&type)) { |
| ASSERT_EQ(type, CommandType::Small); |
| |
| CommandSmall* small = iterator.NextCommand<CommandSmall>(); |
| ASSERT_EQ(small->data, count); |
| count++; |
| numCommands++; |
| } |
| ASSERT_EQ(numCommands, kCommandCount); |
| |
| iterator.MakeEmptyAsDataWasDestroyed(); |
| } |
| |
| /* ________ |
| * / \ |
| * | POUIC! | |
| * \_ ______/ |
| * v |
| * ()_() |
| * (O.o) |
| * (> <)o |
| */ |
| |
| // Test usage of iterator.Reset |
| TEST(CommandAllocator, IteratorReset) { |
| CommandAllocator allocator; |
| |
| uint64_t myPipeline = 0xDEADBEEFBEEFDEAD; |
| uint32_t myAttachmentPoint = 2; |
| uint32_t myFirst = 42; |
| uint32_t myCount = 16; |
| |
| { |
| CommandPipeline* pipeline = allocator.Allocate<CommandPipeline>(CommandType::Pipeline); |
| pipeline->pipeline = myPipeline; |
| pipeline->attachmentPoint = myAttachmentPoint; |
| |
| CommandDraw* draw = allocator.Allocate<CommandDraw>(CommandType::Draw); |
| draw->first = myFirst; |
| draw->count = myCount; |
| } |
| |
| { |
| CommandIterator iterator(std::move(allocator)); |
| CommandType type; |
| |
| bool hasNext = iterator.NextCommandId(&type); |
| ASSERT_TRUE(hasNext); |
| ASSERT_EQ(type, CommandType::Pipeline); |
| |
| CommandPipeline* pipeline = iterator.NextCommand<CommandPipeline>(); |
| ASSERT_EQ(pipeline->pipeline, myPipeline); |
| ASSERT_EQ(pipeline->attachmentPoint, myAttachmentPoint); |
| |
| iterator.Reset(); |
| |
| hasNext = iterator.NextCommandId(&type); |
| ASSERT_TRUE(hasNext); |
| ASSERT_EQ(type, CommandType::Pipeline); |
| |
| pipeline = iterator.NextCommand<CommandPipeline>(); |
| ASSERT_EQ(pipeline->pipeline, myPipeline); |
| ASSERT_EQ(pipeline->attachmentPoint, myAttachmentPoint); |
| |
| hasNext = iterator.NextCommandId(&type); |
| ASSERT_TRUE(hasNext); |
| ASSERT_EQ(type, CommandType::Draw); |
| |
| CommandDraw* draw = iterator.NextCommand<CommandDraw>(); |
| ASSERT_EQ(draw->first, myFirst); |
| ASSERT_EQ(draw->count, myCount); |
| |
| hasNext = iterator.NextCommandId(&type); |
| ASSERT_FALSE(hasNext); |
| |
| iterator.MakeEmptyAsDataWasDestroyed(); |
| } |
| } |
| |
| // Test iterating empty iterators |
| TEST(CommandAllocator, EmptyIterator) { |
| { |
| CommandAllocator allocator; |
| CommandIterator iterator(std::move(allocator)); |
| |
| CommandType type; |
| bool hasNext = iterator.NextCommandId(&type); |
| ASSERT_FALSE(hasNext); |
| |
| iterator.MakeEmptyAsDataWasDestroyed(); |
| } |
| { |
| CommandAllocator allocator; |
| CommandIterator iterator1(std::move(allocator)); |
| CommandIterator iterator2(std::move(iterator1)); |
| |
| CommandType type; |
| bool hasNext = iterator2.NextCommandId(&type); |
| ASSERT_FALSE(hasNext); |
| |
| iterator2.MakeEmptyAsDataWasDestroyed(); |
| } |
| { |
| CommandIterator iterator1; |
| CommandIterator iterator2(std::move(iterator1)); |
| |
| CommandType type; |
| bool hasNext = iterator2.NextCommandId(&type); |
| ASSERT_FALSE(hasNext); |
| |
| iterator2.MakeEmptyAsDataWasDestroyed(); |
| } |
| } |
| |
| template <size_t A> |
| struct alignas(A) AlignedStruct { |
| char placeholder; |
| }; |
| |
| // Test for overflows in Allocate's computations, size 1 variant |
| TEST(CommandAllocator, AllocationOverflow_1) { |
| CommandAllocator allocator; |
| AlignedStruct<1>* data = |
| allocator.AllocateData<AlignedStruct<1>>(std::numeric_limits<size_t>::max() / 1); |
| ASSERT_EQ(data, nullptr); |
| } |
| |
| // Test for overflows in Allocate's computations, size 2 variant |
| TEST(CommandAllocator, AllocationOverflow_2) { |
| CommandAllocator allocator; |
| AlignedStruct<2>* data = |
| allocator.AllocateData<AlignedStruct<2>>(std::numeric_limits<size_t>::max() / 2); |
| ASSERT_EQ(data, nullptr); |
| } |
| |
| // Test for overflows in Allocate's computations, size 4 variant |
| TEST(CommandAllocator, AllocationOverflow_4) { |
| CommandAllocator allocator; |
| AlignedStruct<4>* data = |
| allocator.AllocateData<AlignedStruct<4>>(std::numeric_limits<size_t>::max() / 4); |
| ASSERT_EQ(data, nullptr); |
| } |
| |
| // Test for overflows in Allocate's computations, size 8 variant |
| TEST(CommandAllocator, AllocationOverflow_8) { |
| CommandAllocator allocator; |
| AlignedStruct<8>* data = |
| allocator.AllocateData<AlignedStruct<8>>(std::numeric_limits<size_t>::max() / 8); |
| ASSERT_EQ(data, nullptr); |
| } |
| |
| template <int DefaultValue> |
| struct IntWithDefault { |
| IntWithDefault() : value(DefaultValue) {} |
| |
| int value; |
| }; |
| |
| // Test that the allcator correctly defaults initalizes data for Allocate |
| TEST(CommandAllocator, AllocateDefaultInitializes) { |
| CommandAllocator allocator; |
| |
| IntWithDefault<42>* int42 = allocator.Allocate<IntWithDefault<42>>(CommandType::Draw); |
| ASSERT_EQ(int42->value, 42); |
| |
| IntWithDefault<43>* int43 = allocator.Allocate<IntWithDefault<43>>(CommandType::Draw); |
| ASSERT_EQ(int43->value, 43); |
| |
| IntWithDefault<44>* int44 = allocator.Allocate<IntWithDefault<44>>(CommandType::Draw); |
| ASSERT_EQ(int44->value, 44); |
| |
| CommandIterator iterator(std::move(allocator)); |
| iterator.MakeEmptyAsDataWasDestroyed(); |
| } |
| |
| // Test that the allocator correctly default-initalizes data for AllocateData |
| TEST(CommandAllocator, AllocateDataDefaultInitializes) { |
| CommandAllocator allocator; |
| |
| IntWithDefault<33>* int33 = allocator.AllocateData<IntWithDefault<33>>(1); |
| ASSERT_EQ(int33[0].value, 33); |
| |
| IntWithDefault<34>* int34 = allocator.AllocateData<IntWithDefault<34>>(2); |
| ASSERT_EQ(int34[0].value, 34); |
| ASSERT_EQ(int34[0].value, 34); |
| |
| IntWithDefault<35>* int35 = allocator.AllocateData<IntWithDefault<35>>(3); |
| ASSERT_EQ(int35[0].value, 35); |
| ASSERT_EQ(int35[1].value, 35); |
| ASSERT_EQ(int35[2].value, 35); |
| |
| CommandIterator iterator(std::move(allocator)); |
| iterator.MakeEmptyAsDataWasDestroyed(); |
| } |
| |
| // Tests flattening of multiple CommandAllocators into a single CommandIterator using |
| // AcquireCommandBlocks. |
| TEST(CommandAllocator, AcquireCommandBlocks) { |
| constexpr size_t kNumAllocators = 2; |
| constexpr size_t kNumCommandsPerAllocator = 2; |
| const uint64_t pipelines[kNumAllocators][kNumCommandsPerAllocator] = { |
| {0xDEADBEEFBEEFDEAD, 0xC0FFEEF00DC0FFEE}, |
| {0x1337C0DE1337C0DE, 0xCAFEFACEFACECAFE}, |
| }; |
| const uint32_t attachmentPoints[kNumAllocators][kNumCommandsPerAllocator] = {{1, 2}, {3, 4}}; |
| const uint32_t firsts[kNumAllocators][kNumCommandsPerAllocator] = {{42, 43}, {5, 6}}; |
| const uint32_t counts[kNumAllocators][kNumCommandsPerAllocator] = {{16, 32}, {4, 8}}; |
| |
| std::vector<CommandAllocator> allocators(kNumAllocators); |
| for (size_t j = 0; j < kNumAllocators; ++j) { |
| CommandAllocator& allocator = allocators[j]; |
| for (size_t i = 0; i < kNumCommandsPerAllocator; ++i) { |
| CommandPipeline* pipeline = allocator.Allocate<CommandPipeline>(CommandType::Pipeline); |
| pipeline->pipeline = pipelines[j][i]; |
| pipeline->attachmentPoint = attachmentPoints[j][i]; |
| |
| CommandDraw* draw = allocator.Allocate<CommandDraw>(CommandType::Draw); |
| draw->first = firsts[j][i]; |
| draw->count = counts[j][i]; |
| } |
| } |
| |
| CommandIterator iterator; |
| iterator.AcquireCommandBlocks(std::move(allocators)); |
| for (size_t j = 0; j < kNumAllocators; ++j) { |
| for (size_t i = 0; i < kNumCommandsPerAllocator; ++i) { |
| CommandType type; |
| bool hasNext = iterator.NextCommandId(&type); |
| ASSERT_TRUE(hasNext); |
| ASSERT_EQ(type, CommandType::Pipeline); |
| |
| CommandPipeline* pipeline = iterator.NextCommand<CommandPipeline>(); |
| ASSERT_EQ(pipeline->pipeline, pipelines[j][i]); |
| ASSERT_EQ(pipeline->attachmentPoint, attachmentPoints[j][i]); |
| |
| hasNext = iterator.NextCommandId(&type); |
| ASSERT_TRUE(hasNext); |
| ASSERT_EQ(type, CommandType::Draw); |
| |
| CommandDraw* draw = iterator.NextCommand<CommandDraw>(); |
| ASSERT_EQ(draw->first, firsts[j][i]); |
| ASSERT_EQ(draw->count, counts[j][i]); |
| } |
| } |
| CommandType type; |
| ASSERT_FALSE(iterator.NextCommandId(&type)); |
| iterator.MakeEmptyAsDataWasDestroyed(); |
| } |
| |
| } // namespace dawn::native |