1 /*
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
27 namespace android::uirenderer {
28
29 template <typename T>
30 struct OpBufferItemHeader {
31 T type : 8;
32 uint32_t size : 24;
33 };
34
35 struct 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
50 template <typename ItemTypes, template <ItemTypes> typename ItemContainer,
51 typename BufferHeader = OpBufferAllocationHeader,
52 typename ItemTypesSequence = std::make_index_sequence<static_cast<int>(ItemTypes::COUNT)>>
53 class 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
PadAlign(size_t size)59 static constexpr size_t PadAlign(size_t size) {
60 return (size + (Alignment - 1)) & -Alignment;
61 }
62
63 public:
64 static constexpr auto STARTING_SIZE = PadAlign(sizeof(BufferHeader));
65 using ItemHeader = OpBufferItemHeader<ItemTypes>;
66
67 explicit OpBuffer() = default;
68
69 // Prevent copying by default
70 OpBuffer(const OpBuffer&) = delete;
71 void operator=(const OpBuffer&) = delete;
72
OpBuffer(OpBuffer && other)73 OpBuffer(OpBuffer&& other) {
74 mBuffer = other.mBuffer;
75 other.mBuffer = nullptr;
76 }
77
78 void operator=(OpBuffer&& other) {
79 destroy();
80 mBuffer = other.mBuffer;
81 other.mBuffer = nullptr;
82 }
83
~OpBuffer()84 ~OpBuffer() {
85 destroy();
86 }
87
capacity()88 constexpr size_t capacity() const { return mBuffer ? mBuffer->capacity : 0; }
89
size()90 constexpr size_t size() const { return mBuffer ? mBuffer->used : 0; }
91
remaining()92 constexpr size_t remaining() const { return capacity() - size(); }
93
94 // TODO: Add less-copy'ing variants of this. emplace_back? deferred initialization?
95 template <ItemTypes T>
push_container(ItemContainer<T> && op)96 void push_container(ItemContainer<T>&& op) {
97 static_assert(alignof(ItemContainer<T>) <= Alignment);
98 static_assert(offsetof(ItemContainer<T>, header) == 0);
99
100 constexpr auto padded_size = PadAlign(sizeof(ItemContainer<T>));
101 if (remaining() < padded_size) {
102 resize(std::max(padded_size, capacity()) * 2);
103 }
104 mBuffer->endOffset = mBuffer->used;
105 mBuffer->used += padded_size;
106
107 void* allocateAt = reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->endOffset;
108 auto temp = new (allocateAt) ItemContainer<T>{std::move(op)};
109 temp->header = {.type = T, .size = padded_size};
110 }
111
resize(size_t newsize)112 void resize(size_t newsize) {
113 // Add the header size to newsize
114 const size_t adjustedSize = newsize + STARTING_SIZE;
115
116 if (adjustedSize < size()) {
117 // todo: throw?
118 return;
119 }
120 if (newsize == 0) {
121 free(mBuffer);
122 mBuffer = nullptr;
123 } else {
124 if (mBuffer) {
125 mBuffer = reinterpret_cast<BufferHeader*>(realloc(mBuffer, adjustedSize));
126 mBuffer->capacity = adjustedSize;
127 } else {
128 mBuffer = new (malloc(adjustedSize)) BufferHeader();
129 mBuffer->capacity = adjustedSize;
130 mBuffer->used = STARTING_SIZE;
131 mBuffer->startOffset = STARTING_SIZE;
132 }
133 }
134 }
135
136 template <typename F>
for_each(F && f)137 void for_each(F&& f) const {
138 do_for_each(std::forward<F>(f), ItemTypesSequence{});
139 }
140
141 void clear();
142
first()143 ItemHeader* first() const { return isEmpty() ? nullptr : itemAt(mBuffer->startOffset); }
144
last()145 ItemHeader* last() const { return isEmpty() ? nullptr : itemAt(mBuffer->endOffset); }
146
147 class sentinal {
148 public:
sentinal(const uint8_t * end)149 explicit sentinal(const uint8_t* end) : end(end) {}
150 private:
151 const uint8_t* const end;
152 };
153
end()154 sentinal end() const {
155 return sentinal{end_ptr()};
156 }
157
158 template <ItemTypes T>
159 class filtered_iterator {
160 public:
filtered_iterator(uint8_t * start,const uint8_t * end)161 explicit filtered_iterator(uint8_t* start, const uint8_t* end)
162 : mCurrent(start), mEnd(end) {
163 ItemHeader* header = reinterpret_cast<ItemHeader*>(mCurrent);
164 if (header->type != T) {
165 advance();
166 }
167 }
168
169 filtered_iterator& operator++() {
170 advance();
171 return *this;
172 }
173
174 // Although this iterator self-terminates, we need a placeholder to compare against
175 // to make for-each loops happy
176 bool operator!=(const sentinal& other) const {
177 return mCurrent != mEnd;
178 }
179
180 ItemContainer<T>& operator*() {
181 return *reinterpret_cast<ItemContainer<T>*>(mCurrent);
182 }
183 private:
advance()184 void advance() {
185 ItemHeader* header = reinterpret_cast<ItemHeader*>(mCurrent);
186 do {
187 mCurrent += header->size;
188 header = reinterpret_cast<ItemHeader*>(mCurrent);
189 } while (mCurrent != mEnd && header->type != T);
190 }
191 uint8_t* mCurrent;
192 const uint8_t* const mEnd;
193 };
194
195 template <ItemTypes T>
196 class filtered_view {
197 public:
filtered_view(uint8_t * start,const uint8_t * end)198 explicit filtered_view(uint8_t* start, const uint8_t* end) : mStart(start), mEnd(end) {}
199
begin()200 filtered_iterator<T> begin() const {
201 return filtered_iterator<T>{mStart, mEnd};
202 }
203
end()204 sentinal end() const {
205 return sentinal{mEnd};
206 }
207 private:
208 uint8_t* mStart;
209 const uint8_t* const mEnd;
210 };
211
212 template <ItemTypes T>
filter()213 filtered_view<T> filter() const {
214 return filtered_view<T>{start_ptr(), end_ptr()};
215 }
216
217 private:
218
start_ptr()219 uint8_t* start_ptr() const {
220 return reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->startOffset;
221 }
222
end_ptr()223 const uint8_t* end_ptr() const {
224 return reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->used;
225 }
226
227 template <typename F, std::size_t... I>
do_for_each(F && f,std::index_sequence<I...>)228 void do_for_each(F&& f, std::index_sequence<I...>) const {
229 // Validate we're not empty
230 if (isEmpty()) return;
231
232 // Setup the jump table, mapping from each type to a springboard that invokes the template
233 // function with the appropriate concrete type
234 using F_PTR = decltype(&f);
235 using THUNK = void (*)(F_PTR, void*);
236 static constexpr auto jump = std::array<THUNK, sizeof...(I)>{[](F_PTR fp, void* t) {
237 (*fp)(reinterpret_cast<const ItemContainer<static_cast<ItemTypes>(I)>*>(t));
238 }...};
239
240 // Do the actual iteration of each item
241 uint8_t* current = start_ptr();
242 const uint8_t* end = end_ptr();
243 while (current != end) {
244 auto header = reinterpret_cast<ItemHeader*>(current);
245 // `f` could be a destructor, so ensure all accesses to the OP happen prior to invoking
246 // `f`
247 auto it = (void*)current;
248 current += header->size;
249 jump[static_cast<int>(header->type)](&f, it);
250 }
251 }
252
destroy()253 void destroy() {
254 clear();
255 resize(0);
256 }
257
offsetIsValid(size_t offset)258 bool offsetIsValid(size_t offset) const {
259 return offset >= mBuffer->startOffset && offset < mBuffer->used;
260 }
261
itemAt(size_t offset)262 ItemHeader* itemAt(size_t offset) const {
263 if (!offsetIsValid(offset)) return nullptr;
264 return reinterpret_cast<ItemHeader*>(reinterpret_cast<uint8_t*>(mBuffer) + offset);
265 }
266
isEmpty()267 bool isEmpty() const { return mBuffer == nullptr || mBuffer->used == STARTING_SIZE; }
268
269 BufferHeader* mBuffer = nullptr;
270 };
271
272 template <typename ItemTypes, template <ItemTypes> typename ItemContainer, typename BufferHeader,
273 typename ItemTypeSequence>
clear()274 void OpBuffer<ItemTypes, ItemContainer, BufferHeader, ItemTypeSequence>::clear() {
275
276 // Don't need to do anything if we don't have a buffer
277 if (!mBuffer) return;
278
279 for_each([](auto op) {
280 using T = std::remove_reference_t<decltype(*op)>;
281 op->~T();
282 });
283 mBuffer->used = STARTING_SIZE;
284 mBuffer->startOffset = STARTING_SIZE;
285 mBuffer->endOffset = 0;
286 }
287
288 } // namespace android::uirenderer