|  | /* | 
|  | * Copyright (C) 2020 The Android Open Source Project | 
|  | * | 
|  | * 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. | 
|  | */ | 
|  |  | 
|  | #pragma once | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <array> | 
|  | #include <cinttypes> | 
|  | #include <cstddef> | 
|  | #include <cstdlib> | 
|  | #include <type_traits> | 
|  | #include <utility> | 
|  |  | 
|  | namespace android::uirenderer { | 
|  |  | 
|  | template <typename T> | 
|  | struct OpBufferItemHeader { | 
|  | T type : 8; | 
|  | uint32_t size : 24; | 
|  | }; | 
|  |  | 
|  | struct OpBufferAllocationHeader { | 
|  | // Used size, including header size | 
|  | size_t used = 0; | 
|  | // Capacity, including header size | 
|  | size_t capacity = 0; | 
|  | // Offset relative to `this` at which the first item is | 
|  | size_t startOffset = 0; | 
|  | // Offset relative to `this` at which the last item is | 
|  | size_t endOffset = 0; | 
|  | }; | 
|  |  | 
|  | #define BE_OPBUFFERS_FRIEND()                                      \ | 
|  | template <typename ItemTypes, template <ItemTypes> typename, typename, typename> \ | 
|  | friend class OpBuffer | 
|  |  | 
|  | template <typename ItemTypes, template <ItemTypes> typename ItemContainer, | 
|  | typename BufferHeader = OpBufferAllocationHeader, | 
|  | typename ItemTypesSequence = std::make_index_sequence<static_cast<int>(ItemTypes::COUNT)>> | 
|  | class OpBuffer { | 
|  | // Instead of re-aligning individual inserts, just pad the size of everything | 
|  | // to a multiple of pointer alignment. This assumes we never work with doubles. | 
|  | // Which we don't. | 
|  | static constexpr size_t Alignment = alignof(void*); | 
|  |  | 
|  | static constexpr size_t PadAlign(size_t size) { | 
|  | return (size + (Alignment - 1)) & -Alignment; | 
|  | } | 
|  |  | 
|  | public: | 
|  | static constexpr auto STARTING_SIZE = PadAlign(sizeof(BufferHeader)); | 
|  | using ItemHeader = OpBufferItemHeader<ItemTypes>; | 
|  |  | 
|  | explicit OpBuffer() = default; | 
|  |  | 
|  | // Prevent copying by default | 
|  | OpBuffer(const OpBuffer&) = delete; | 
|  | void operator=(const OpBuffer&) = delete; | 
|  |  | 
|  | OpBuffer(OpBuffer&& other) { | 
|  | mBuffer = other.mBuffer; | 
|  | other.mBuffer = nullptr; | 
|  | } | 
|  |  | 
|  | void operator=(OpBuffer&& other) { | 
|  | destroy(); | 
|  | mBuffer = other.mBuffer; | 
|  | other.mBuffer = nullptr; | 
|  | } | 
|  |  | 
|  | ~OpBuffer() { | 
|  | destroy(); | 
|  | } | 
|  |  | 
|  | constexpr size_t capacity() const { return mBuffer ? mBuffer->capacity : 0; } | 
|  |  | 
|  | constexpr size_t size() const { return mBuffer ? mBuffer->used : 0; } | 
|  |  | 
|  | constexpr size_t remaining() const { return capacity() - size(); } | 
|  |  | 
|  | // TODO: Add less-copy'ing variants of this. emplace_back? deferred initialization? | 
|  | template <ItemTypes T> | 
|  | void push_container(ItemContainer<T>&& op) { | 
|  | static_assert(alignof(ItemContainer<T>) <= Alignment); | 
|  | static_assert(offsetof(ItemContainer<T>, header) == 0); | 
|  |  | 
|  | constexpr auto padded_size = PadAlign(sizeof(ItemContainer<T>)); | 
|  | if (remaining() < padded_size) { | 
|  | resize(std::max(padded_size, capacity()) * 2); | 
|  | } | 
|  | mBuffer->endOffset = mBuffer->used; | 
|  | mBuffer->used += padded_size; | 
|  |  | 
|  | void* allocateAt = reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->endOffset; | 
|  | auto temp = new (allocateAt) ItemContainer<T>{std::move(op)}; | 
|  | temp->header = {.type = T, .size = padded_size}; | 
|  | } | 
|  |  | 
|  | void resize(size_t newsize) { | 
|  | // Add the header size to newsize | 
|  | const size_t adjustedSize = newsize + STARTING_SIZE; | 
|  |  | 
|  | if (adjustedSize < size()) { | 
|  | // todo: throw? | 
|  | return; | 
|  | } | 
|  | if (newsize == 0) { | 
|  | free(mBuffer); | 
|  | mBuffer = nullptr; | 
|  | } else { | 
|  | if (mBuffer) { | 
|  | mBuffer = reinterpret_cast<BufferHeader*>(realloc(mBuffer, adjustedSize)); | 
|  | mBuffer->capacity = adjustedSize; | 
|  | } else { | 
|  | mBuffer = new (malloc(adjustedSize)) BufferHeader(); | 
|  | mBuffer->capacity = adjustedSize; | 
|  | mBuffer->used = STARTING_SIZE; | 
|  | mBuffer->startOffset = STARTING_SIZE; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename F> | 
|  | void for_each(F&& f) const { | 
|  | do_for_each(std::forward<F>(f), ItemTypesSequence{}); | 
|  | } | 
|  |  | 
|  | void clear(); | 
|  |  | 
|  | ItemHeader* first() const { return isEmpty() ? nullptr : itemAt(mBuffer->startOffset); } | 
|  |  | 
|  | ItemHeader* last() const { return isEmpty() ? nullptr : itemAt(mBuffer->endOffset); } | 
|  |  | 
|  | class sentinal { | 
|  | public: | 
|  | explicit sentinal(const uint8_t* end) : end(end) {} | 
|  | private: | 
|  | const uint8_t* const end; | 
|  | }; | 
|  |  | 
|  | sentinal end() const { | 
|  | return sentinal{end_ptr()}; | 
|  | } | 
|  |  | 
|  | template <ItemTypes T> | 
|  | class filtered_iterator { | 
|  | public: | 
|  | explicit filtered_iterator(uint8_t* start, const uint8_t* end) | 
|  | : mCurrent(start), mEnd(end) { | 
|  | ItemHeader* header = reinterpret_cast<ItemHeader*>(mCurrent); | 
|  | if (header->type != T) { | 
|  | advance(); | 
|  | } | 
|  | } | 
|  |  | 
|  | filtered_iterator& operator++() { | 
|  | advance(); | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | // Although this iterator self-terminates, we need a placeholder to compare against | 
|  | // to make for-each loops happy | 
|  | bool operator!=(const sentinal& other) const { | 
|  | return mCurrent != mEnd; | 
|  | } | 
|  |  | 
|  | ItemContainer<T>& operator*() { | 
|  | return *reinterpret_cast<ItemContainer<T>*>(mCurrent); | 
|  | } | 
|  | private: | 
|  | void advance() { | 
|  | ItemHeader* header = reinterpret_cast<ItemHeader*>(mCurrent); | 
|  | do { | 
|  | mCurrent += header->size; | 
|  | header = reinterpret_cast<ItemHeader*>(mCurrent); | 
|  | } while (mCurrent != mEnd && header->type != T); | 
|  | } | 
|  | uint8_t* mCurrent; | 
|  | const uint8_t* const mEnd; | 
|  | }; | 
|  |  | 
|  | template <ItemTypes T> | 
|  | class filtered_view { | 
|  | public: | 
|  | explicit filtered_view(uint8_t* start, const uint8_t* end) : mStart(start), mEnd(end) {} | 
|  |  | 
|  | filtered_iterator<T> begin() const { | 
|  | return filtered_iterator<T>{mStart, mEnd}; | 
|  | } | 
|  |  | 
|  | sentinal end() const { | 
|  | return sentinal{mEnd}; | 
|  | } | 
|  | private: | 
|  | uint8_t* mStart; | 
|  | const uint8_t* const mEnd; | 
|  | }; | 
|  |  | 
|  | template <ItemTypes T> | 
|  | filtered_view<T> filter() const { | 
|  | return filtered_view<T>{start_ptr(), end_ptr()}; | 
|  | } | 
|  |  | 
|  | private: | 
|  |  | 
|  | uint8_t* start_ptr() const { | 
|  | return reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->startOffset; | 
|  | } | 
|  |  | 
|  | const uint8_t* end_ptr() const { | 
|  | return reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->used; | 
|  | } | 
|  |  | 
|  | template <typename F, std::size_t... I> | 
|  | void do_for_each(F&& f, std::index_sequence<I...>) const { | 
|  | // Validate we're not empty | 
|  | if (isEmpty()) return; | 
|  |  | 
|  | // Setup the jump table, mapping from each type to a springboard that invokes the template | 
|  | // function with the appropriate concrete type | 
|  | using F_PTR = decltype(&f); | 
|  | using THUNK = void (*)(F_PTR, void*); | 
|  | static constexpr auto jump = std::array<THUNK, sizeof...(I)>{[](F_PTR fp, void* t) { | 
|  | (*fp)(reinterpret_cast<const ItemContainer<static_cast<ItemTypes>(I)>*>(t)); | 
|  | }...}; | 
|  |  | 
|  | // Do the actual iteration of each item | 
|  | uint8_t* current = start_ptr(); | 
|  | const uint8_t* end = end_ptr(); | 
|  | while (current != end) { | 
|  | auto header = reinterpret_cast<ItemHeader*>(current); | 
|  | // `f` could be a destructor, so ensure all accesses to the OP happen prior to invoking | 
|  | // `f` | 
|  | auto it = (void*)current; | 
|  | current += header->size; | 
|  | jump[static_cast<int>(header->type)](&f, it); | 
|  | } | 
|  | } | 
|  |  | 
|  | void destroy() { | 
|  | clear(); | 
|  | resize(0); | 
|  | } | 
|  |  | 
|  | bool offsetIsValid(size_t offset) const { | 
|  | return offset >= mBuffer->startOffset && offset < mBuffer->used; | 
|  | } | 
|  |  | 
|  | ItemHeader* itemAt(size_t offset) const { | 
|  | if (!offsetIsValid(offset)) return nullptr; | 
|  | return reinterpret_cast<ItemHeader*>(reinterpret_cast<uint8_t*>(mBuffer) + offset); | 
|  | } | 
|  |  | 
|  | bool isEmpty() const { return mBuffer == nullptr || mBuffer->used == STARTING_SIZE; } | 
|  |  | 
|  | BufferHeader* mBuffer = nullptr; | 
|  | }; | 
|  |  | 
|  | template <typename ItemTypes, template <ItemTypes> typename ItemContainer, typename BufferHeader, | 
|  | typename ItemTypeSequence> | 
|  | void OpBuffer<ItemTypes, ItemContainer, BufferHeader, ItemTypeSequence>::clear() { | 
|  |  | 
|  | // Don't need to do anything if we don't have a buffer | 
|  | if (!mBuffer) return; | 
|  |  | 
|  | for_each([](auto op) { | 
|  | using T = std::remove_reference_t<decltype(*op)>; | 
|  | op->~T(); | 
|  | }); | 
|  | mBuffer->used = STARTING_SIZE; | 
|  | mBuffer->startOffset = STARTING_SIZE; | 
|  | mBuffer->endOffset = 0; | 
|  | } | 
|  |  | 
|  | }  // namespace android::uirenderer |