blob: 398e090b8cfabe89d65284ec62026dec74ea5ac2 [file] [log] [blame]
John Reck013127b2020-10-29 20:53:51 -04001/*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
19#include <algorithm>
20#include <array>
21#include <cinttypes>
22#include <cstddef>
23#include <cstdlib>
24#include <type_traits>
25#include <utility>
26
27namespace android::uirenderer {
28
29template <typename T>
30struct OpBufferItemHeader {
31 T type : 8;
32 uint32_t size : 24;
33};
34
35struct OpBufferAllocationHeader {
36 // Used size, including header size
37 size_t used = 0;
38 // Capacity, including header size
39 size_t capacity = 0;
40 // Offset relative to `this` at which the first item is
41 size_t startOffset = 0;
42 // Offset relative to `this` at which the last item is
43 size_t endOffset = 0;
44};
45
46#define BE_OPBUFFERS_FRIEND() \
47 template <typename ItemTypes, template <ItemTypes> typename, typename, typename> \
48 friend class OpBuffer
49
50template <typename ItemTypes, template <ItemTypes> typename ItemContainer,
51 typename BufferHeader = OpBufferAllocationHeader,
52 typename ItemTypesSequence = std::make_index_sequence<static_cast<int>(ItemTypes::COUNT)>>
53class OpBuffer {
54 // Instead of re-aligning individual inserts, just pad the size of everything
55 // to a multiple of pointer alignment. This assumes we never work with doubles.
56 // Which we don't.
57 static constexpr size_t Alignment = alignof(void*);
58
59 static constexpr size_t PadAlign(size_t size) {
60 return (size + (Alignment - 1)) & -Alignment;
61 }
62
63 static constexpr auto STARTING_SIZE = PadAlign(sizeof(BufferHeader));
64
65public:
66 using ItemHeader = OpBufferItemHeader<ItemTypes>;
67
68 OpBuffer() = default;
69
70 // Prevent copying by default
71 OpBuffer(const OpBuffer&) = delete;
72 void operator=(const OpBuffer&) = delete;
73
74 OpBuffer(OpBuffer&& other) {
75 mBuffer = other.mBuffer;
76 other.mBuffer = nullptr;
77 }
78
79 void operator=(OpBuffer&& other) {
80 destroy();
81 mBuffer = other.mBuffer;
82 other.mBuffer = nullptr;
83 }
84
85 ~OpBuffer() {
86 destroy();
87 }
88
89 constexpr size_t capacity() const { return mBuffer ? mBuffer->capacity : 0; }
90
91 constexpr size_t size() const { return mBuffer ? mBuffer->used : 0; }
92
93 constexpr size_t remaining() const { return capacity() - size(); }
94
95 // TODO: Add less-copy'ing variants of this. emplace_back? deferred initialization?
96 template <ItemTypes T>
97 void push_container(ItemContainer<T>&& op) {
98 static_assert(alignof(ItemContainer<T>) <= Alignment);
99 static_assert(offsetof(ItemContainer<T>, header) == 0);
100
101 constexpr auto padded_size = PadAlign(sizeof(ItemContainer<T>));
102 if (remaining() < padded_size) {
103 resize(std::max(padded_size, capacity()) * 2);
104 }
105 mBuffer->endOffset = mBuffer->used;
106 mBuffer->used += padded_size;
107
108 void* allocateAt = reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->endOffset;
109 auto temp = new (allocateAt) ItemContainer<T>{std::move(op)};
110 temp->header = {.type = T, .size = padded_size};
111 }
112
113 void resize(size_t newsize) {
114 // Add the header size to newsize
115 const size_t adjustedSize = newsize + STARTING_SIZE;
116
117 if (adjustedSize < size()) {
118 // todo: throw?
119 return;
120 }
121 if (newsize == 0) {
122 free(mBuffer);
123 mBuffer = nullptr;
124 } else {
125 if (mBuffer) {
126 mBuffer = reinterpret_cast<BufferHeader*>(realloc(mBuffer, adjustedSize));
127 mBuffer->capacity = adjustedSize;
128 } else {
129 mBuffer = new (malloc(adjustedSize)) BufferHeader();
130 mBuffer->capacity = adjustedSize;
131 mBuffer->used = STARTING_SIZE;
132 mBuffer->startOffset = STARTING_SIZE;
133 }
134 }
135 }
136
137 template <typename F>
138 void for_each(F&& f) const {
139 for_each(std::forward<F>(f), ItemTypesSequence{});
140 }
141
142 void clear();
143
144 ItemHeader* first() const { return isEmpty() ? nullptr : itemAt(mBuffer->startOffset); }
145
146 ItemHeader* last() const { return isEmpty() ? nullptr : itemAt(mBuffer->endOffset); }
147
148private:
149 template <typename F, std::size_t... I>
150 void for_each(F&& f, std::index_sequence<I...>) const {
151 // Validate we're not empty
152 if (isEmpty()) return;
153
154 // Setup the jump table, mapping from each type to a springboard that invokes the template
155 // function with the appropriate concrete type
156 using F_PTR = decltype(&f);
157 using THUNK = void (*)(F_PTR, void*);
158 static constexpr auto jump = std::array<THUNK, sizeof...(I)>{[](F_PTR fp, void* t) {
159 (*fp)(reinterpret_cast<ItemContainer<static_cast<ItemTypes>(I)>*>(t));
160 }...};
161
162 // Do the actual iteration of each item
163 uint8_t* current = reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->startOffset;
164 uint8_t* end = reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->used;
165 while (current != end) {
166 auto header = reinterpret_cast<ItemHeader*>(current);
167 // `f` could be a destructor, so ensure all accesses to the OP happen prior to invoking
168 // `f`
169 auto it = (void*)current;
170 current += header->size;
171 jump[static_cast<int>(header->type)](&f, it);
172 }
173 }
174
175 void destroy() {
176 clear();
177 resize(0);
178 }
179
180 bool offsetIsValid(size_t offset) const {
181 return offset >= mBuffer->startOffset && offset < mBuffer->used;
182 }
183
184 ItemHeader* itemAt(size_t offset) const {
185 if (!offsetIsValid(offset)) return nullptr;
186 return reinterpret_cast<ItemHeader*>(reinterpret_cast<uint8_t*>(mBuffer) + offset);
187 }
188
189 bool isEmpty() const { return mBuffer == nullptr || mBuffer->used == STARTING_SIZE; }
190
191 BufferHeader* mBuffer = nullptr;
192};
193
194template <typename ItemTypes, template <ItemTypes> typename ItemContainer, typename BufferHeader,
195 typename ItemTypeSequence>
196void OpBuffer<ItemTypes, ItemContainer, BufferHeader, ItemTypeSequence>::clear() {
197
198 // Don't need to do anything if we don't have a buffer
199 if (!mBuffer) return;
200
201 for_each([](auto op) {
202 using T = std::remove_reference_t<decltype(*op)>;
203 op->~T();
204 });
205 mBuffer->used = STARTING_SIZE;
206 mBuffer->startOffset = STARTING_SIZE;
207 mBuffer->endOffset = 0;
208}
209
210} // namespace android::uirenderer