From 38246eb51ca14541623a837dfd67ae28055c9100 Mon Sep 17 00:00:00 2001 From: Corentin Wallez Date: Thu, 15 Jun 2017 13:08:25 -0400 Subject: [PATCH] Introduce SerialQueue to track in flight resources --- src/backend/CMakeLists.txt | 1 + src/backend/common/SerialQueue.h | 217 +++++++++++++++++++++++ src/tests/CMakeLists.txt | 1 + src/tests/unittests/SerialQueueTests.cpp | 119 +++++++++++++ 4 files changed, 338 insertions(+) create mode 100644 src/backend/common/SerialQueue.h create mode 100644 src/tests/unittests/SerialQueueTests.cpp diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index 07672b9ea6..fa717bef68 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -60,6 +60,7 @@ list(APPEND BACKEND_SOURCES ${COMMON_DIR}/RefCounted.h ${COMMON_DIR}/Sampler.cpp ${COMMON_DIR}/Sampler.h + ${COMMON_DIR}/SerialQueue.h ${COMMON_DIR}/ShaderModule.cpp ${COMMON_DIR}/ShaderModule.h ${COMMON_DIR}/Texture.cpp diff --git a/src/backend/common/SerialQueue.h b/src/backend/common/SerialQueue.h new file mode 100644 index 0000000000..02b5ca4cd8 --- /dev/null +++ b/src/backend/common/SerialQueue.h @@ -0,0 +1,217 @@ +// 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. + +#ifndef BACKEND_COMMON_SERIALQUEUE_H_ +#define BACKEND_COMMON_SERIALQUEUE_H_ + +#include "Forward.h" + +#include +#include + +namespace backend { + + using Serial = uint64_t; + + template + class SerialQueue { + private: + using SerialPair = std::pair>; + using Storage = std::vector; + using StorageIterator = typename Storage::const_iterator; + + public: + class Iterator { + public: + Iterator(StorageIterator start); + Iterator& operator++(); + + bool operator==(const Iterator& other) const; + bool operator!=(const Iterator& other) const; + const T& operator*() const; + + private: + StorageIterator storageIterator; + // Special case the serialIterator when it should be equal to storageIterator.begin() + // otherwise we could ask storageIterator.begin() when storageIterator is storage.end() + // which is invalid. storageIterator.begin() is tagged with a nullptr. + const T* serialIterator; + }; + + class BeginEnd { + public: + BeginEnd(StorageIterator start, StorageIterator end); + + Iterator begin() const; + Iterator end() const; + + private: + StorageIterator startIt; + StorageIterator endIt; + }; + + // The serial must be given in (not strictly) increasing order. + void Enqueue(const T& value, Serial serial); + void Enqueue(T&& value, Serial serial); + void Enqueue(const std::vector& values, Serial serial); + void Enqueue(std::vector&& values, Serial serial); + + bool Empty() const; + + // The UpTo variants of Iterate and Clear affect all values associated to a serial + // that is smaller OR EQUAL to the given serial. Iterating is done like so: + // for (const T& value : queue.IterateAll()) { stuff(T); } + BeginEnd IterateAll() const; + BeginEnd IterateUpTo(Serial serial) const; + + void Clear(); + void ClearUpTo(Serial serial); + + private: + // Returns the first StorageIterator that a serial bigger than serial. + StorageIterator FindUpTo(Serial serial) const; + Storage storage; + }; + + // SerialQueue + + template + void SerialQueue::Enqueue(const T& value, Serial serial) { + ASSERT(Empty() || storage.back().first <= serial); + + if (Empty() || storage.back().first < serial) { + storage.emplace_back(SerialPair(serial, {})); + } + storage.back().second.push_back(value); + } + + template + void SerialQueue::Enqueue(T&& value, Serial serial) { + ASSERT(Empty() || storage.back().first <= serial); + + if (Empty() || storage.back().first < serial) { + storage.emplace_back(SerialPair(serial, {})); + } + storage.back().second.push_back(value); + } + + template + void SerialQueue::Enqueue(const std::vector& values, Serial serial) { + ASSERT(Empty() || storage.back().first <= serial); + storage.emplace_back(SerialPair(serial, {values})); + } + + template + void SerialQueue::Enqueue(std::vector&& values, Serial serial) { + ASSERT(Empty() || storage.back().first <= serial); + storage.emplace_back(SerialPair(serial, {values})); + } + + template + bool SerialQueue::Empty() const { + return storage.empty(); + } + + template + typename SerialQueue::BeginEnd SerialQueue::IterateAll() const { + return {storage.begin(), storage.end()}; + } + + template + typename SerialQueue::BeginEnd SerialQueue::IterateUpTo(Serial serial) const { + return {storage.begin(), FindUpTo(serial)}; + } + + template + void SerialQueue::Clear() { + storage.clear(); + } + + template + void SerialQueue::ClearUpTo(Serial serial) { + storage.erase(storage.begin(), FindUpTo(serial)); + } + + template + typename SerialQueue::StorageIterator SerialQueue::FindUpTo(Serial serial) const { + auto it = storage.begin(); + while (it != storage.end() && it->first <= serial) { + it ++; + } + return it; + } + + // SerialQueue::BeginEnd + + template + SerialQueue::BeginEnd::BeginEnd(typename SerialQueue::StorageIterator start, typename SerialQueue::StorageIterator end) + : startIt(start), endIt(end) { + } + + template + typename SerialQueue::Iterator SerialQueue::BeginEnd::begin() const { + return {startIt}; + } + + template + typename SerialQueue::Iterator SerialQueue::BeginEnd::end() const { + return {endIt}; + } + + // SerialQueue::Iterator + + template + SerialQueue::Iterator::Iterator(typename SerialQueue::StorageIterator start) + : storageIterator(start), serialIterator(nullptr) { + } + + template + typename SerialQueue::Iterator& SerialQueue::Iterator::operator++() { + const T* vectorData = storageIterator->second.data(); + + if (serialIterator == nullptr) { + serialIterator = vectorData + 1; + } else { + serialIterator ++; + } + + if (serialIterator >= vectorData + storageIterator->second.size()) { + serialIterator = nullptr; + storageIterator ++; + } + + return *this; + } + + template + bool SerialQueue::Iterator::operator==(const typename SerialQueue::Iterator& other) const { + return other.storageIterator == storageIterator && other.serialIterator == serialIterator; + } + + template + bool SerialQueue::Iterator::operator!=(const typename SerialQueue::Iterator& other) const { + return !(*this == other); + } + + template + const T& SerialQueue::Iterator::operator*() const { + if (serialIterator == nullptr) { + return *storageIterator->second.begin(); + } + return *serialIterator; + } + +} + +#endif // BACKEND_COMMON_SERIALQUEUE_H_ diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 2baeb3268e..18e9d3e97d 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(nxt_unittests ${UNITTESTS_DIR}/ObjectBaseTests.cpp ${UNITTESTS_DIR}/PerStageTests.cpp ${UNITTESTS_DIR}/RefCountedTests.cpp + ${UNITTESTS_DIR}/SerialQueueTests.cpp ${UNITTESTS_DIR}/ToBackendTests.cpp ${UNITTESTS_DIR}/WireTests.cpp ${VALIDATION_TESTS_DIR}/BufferValidationTests.cpp diff --git a/src/tests/unittests/SerialQueueTests.cpp b/src/tests/unittests/SerialQueueTests.cpp new file mode 100644 index 0000000000..d0c1504425 --- /dev/null +++ b/src/tests/unittests/SerialQueueTests.cpp @@ -0,0 +1,119 @@ +// 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 + +#include "backend/common/SerialQueue.h" + +using SerialQueue = backend::SerialQueue; + +// A number of basic tests for SerialQueue that are difficult to split from one another +TEST(SerialQueue, BasicTest) { + SerialQueue queue; + + // Queue starts empty + ASSERT_TRUE(queue.Empty()); + + // Iterating on empty queue 1) works 2) doesn't produce any values + for (int value : queue.IterateAll()) { + ASSERT_TRUE(false); + } + + // Enqueuing values as const ref or rvalue ref + queue.Enqueue(1, 0); + queue.Enqueue(2, 0); + queue.Enqueue(std::move(3), 1); + + // Iterating over a non-empty queue produces the expected result + std::vector expectedValues = {1, 2, 3}; + for (int value : queue.IterateAll()) { + EXPECT_EQ(expectedValues.front(), value); + ASSERT_FALSE(expectedValues.empty()); + expectedValues.erase(expectedValues.begin()); + } + ASSERT_TRUE(expectedValues.empty()); + + // Clear works and makes the queue empty and iteration does nothing. + queue.Clear(); + ASSERT_TRUE(queue.Empty()); + + for (int value : queue.IterateAll()) { + ASSERT_TRUE(false); + } +} + +// Test enqueuing vectors works +TEST(SerialQueue, EnqueueVectors) { + SerialQueue queue; + + std::vector vector1 = {1, 2, 3, 4}; + std::vector vector2 = {5, 6, 7, 8}; + std::vector vector3 = {9, 0}; + + queue.Enqueue(vector1, 0); + queue.Enqueue(std::move(vector2), 0); + queue.Enqueue(vector3, 1); + + std::vector expectedValues = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + for (int value : queue.IterateAll()) { + EXPECT_EQ(expectedValues.front(), value); + ASSERT_FALSE(expectedValues.empty()); + expectedValues.erase(expectedValues.begin()); + } + ASSERT_TRUE(expectedValues.empty()); +} + +// Test IterateUpTo +TEST(SerialQueue, IterateUpTo) { + SerialQueue queue; + + std::vector vector1 = {1, 2, 3, 4}; + std::vector vector2 = {5, 6, 7, 8}; + std::vector vector3 = {9, 0}; + + queue.Enqueue(vector1, 0); + queue.Enqueue(std::move(vector2), 1); + queue.Enqueue(vector3, 2); + + std::vector expectedValues = {1, 2, 3, 4, 5, 6, 7, 8}; + for (int value : queue.IterateUpTo(1)) { + EXPECT_EQ(expectedValues.front(), value); + ASSERT_FALSE(expectedValues.empty()); + expectedValues.erase(expectedValues.begin()); + } + ASSERT_TRUE(expectedValues.empty()); +} + +// Test ClearUpTo +TEST(SerialQueue, ClearUpTo) { + SerialQueue queue; + + std::vector vector1 = {1, 2, 3, 4}; + std::vector vector2 = {5, 6, 7, 8}; + std::vector vector3 = {9, 0}; + + queue.Enqueue(vector1, 0); + queue.Enqueue(std::move(vector2), 0); + queue.Enqueue(vector3, 1); + + queue.ClearUpTo(0); + + std::vector expectedValues = {9, 0}; + for (int value : queue.IterateAll()) { + EXPECT_EQ(expectedValues.front(), value); + ASSERT_FALSE(expectedValues.empty()); + expectedValues.erase(expectedValues.begin()); + } + ASSERT_TRUE(expectedValues.empty()); +}