blob: a81aee35070575e646ab665807a64a036b6af1fd [file] [log] [blame] [edit]
// 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