1 | /* |
2 | * Copyright 2019 Google LLC |
3 | * |
4 | * Use of this source code is governed by a BSD-style license that can be |
5 | * found in the LICENSE file. |
6 | */ |
7 | #ifndef GrQuadBuffer_DEFINED |
8 | #define GrQuadBuffer_DEFINED |
9 | |
10 | #include "include/private/SkTDArray.h" |
11 | #include "src/gpu/geometry/GrQuad.h" |
12 | |
13 | template<typename T> |
14 | class GrQuadBuffer { |
15 | public: |
16 | GrQuadBuffer() |
17 | : fCount(0) |
18 | , fDeviceType(GrQuad::Type::kAxisAligned) |
19 | , fLocalType(GrQuad::Type::kAxisAligned) { |
20 | // Pre-allocate space for 1 2D device-space quad, metadata, and header |
21 | fData.reserve(this->entrySize(fDeviceType, nullptr)); |
22 | } |
23 | |
24 | // Reserves space for the given number of entries; if 'needsLocals' is true, space will be |
25 | // reserved for each entry to also have a 2D local quad. The reserved space assumes 2D device |
26 | // quad for simplicity. Since this buffer has a variable bitrate encoding for quads, this may |
27 | // over or under reserve, but pre-allocating still helps when possible. |
28 | GrQuadBuffer(int count, bool needsLocals = false) |
29 | : fCount(0) |
30 | , fDeviceType(GrQuad::Type::kAxisAligned) |
31 | , fLocalType(GrQuad::Type::kAxisAligned) { |
32 | int entrySize = this->entrySize(fDeviceType, needsLocals ? &fLocalType : nullptr); |
33 | fData.reserve(count * entrySize); |
34 | } |
35 | |
36 | // The number of device-space quads (and metadata, and optional local quads) that are in the |
37 | // the buffer. |
38 | int count() const { return fCount; } |
39 | |
40 | // The most general type for the device-space quads in this buffer |
41 | GrQuad::Type deviceQuadType() const { return fDeviceType; } |
42 | |
43 | // The most general type for the local quads; if no local quads are ever added, this will |
44 | // return kAxisAligned. |
45 | GrQuad::Type localQuadType() const { return fLocalType; } |
46 | |
47 | // Append the given 'deviceQuad' to this buffer, with its associated 'metadata'. If 'localQuad' |
48 | // is not null, the local coordinates will also be attached to the entry. When an entry |
49 | // has local coordinates, during iteration, the Iter::hasLocals() will return true and its |
50 | // Iter::localQuad() will be equivalent to the provided local coordinates. If 'localQuad' is |
51 | // null then Iter::hasLocals() will report false for the added entry. |
52 | void append(const GrQuad& deviceQuad, T&& metadata, const GrQuad* localQuad = nullptr); |
53 | |
54 | // Copies all entries from 'that' to this buffer |
55 | void concat(const GrQuadBuffer<T>& that); |
56 | |
57 | // Provides a read-only iterator over a quad buffer, giving access to the device quad, metadata |
58 | // and optional local quad. |
59 | class Iter { |
60 | public: |
61 | Iter(const GrQuadBuffer<T>* buffer) |
62 | : fDeviceQuad(SkRect::MakeEmpty()) |
63 | , fLocalQuad(SkRect::MakeEmpty()) |
64 | , fBuffer(buffer) |
65 | , fCurrentEntry(nullptr) |
66 | , fNextEntry(buffer->fData.begin()) { |
67 | SkDEBUGCODE(fExpectedCount = buffer->count();) |
68 | } |
69 | |
70 | bool next(); |
71 | |
72 | const T& metadata() const { this->validate(); return *(fBuffer->metadata(fCurrentEntry)); } |
73 | |
74 | // The returned pointer is mutable so that the object can be used for scratch calculations |
75 | // during op preparation. However, any changes are not persisted in the GrQuadBuffer and |
76 | // subsequent calls to next() will overwrite the state of the GrQuad. |
77 | GrQuad* deviceQuad() { this->validate(); return &fDeviceQuad; } |
78 | |
79 | // If isLocalValid() returns false, this returns nullptr. Otherwise, the returned pointer |
80 | // is mutable in the same manner as deviceQuad(). |
81 | GrQuad* localQuad() { |
82 | this->validate(); |
83 | return this->isLocalValid() ? &fLocalQuad : nullptr; |
84 | } |
85 | |
86 | bool isLocalValid() const { |
87 | this->validate(); |
88 | return fBuffer->header(fCurrentEntry)->fHasLocals; |
89 | } |
90 | |
91 | private: |
92 | // Quads are stored locally so that calling code doesn't need to re-declare their own quads |
93 | GrQuad fDeviceQuad; |
94 | GrQuad fLocalQuad; |
95 | |
96 | const GrQuadBuffer<T>* fBuffer; |
97 | // The pointer to the current entry to read metadata/header details from |
98 | const char* fCurrentEntry; |
99 | // The pointer to replace fCurrentEntry when next() is called, cached since it is calculated |
100 | // automatically while unpacking the quad data. |
101 | const char* fNextEntry; |
102 | |
103 | SkDEBUGCODE(int fExpectedCount;) |
104 | |
105 | void validate() const { |
106 | SkDEBUGCODE(fBuffer->validate(fCurrentEntry, fExpectedCount);) |
107 | } |
108 | }; |
109 | |
110 | Iter iterator() const { return Iter(this); } |
111 | |
112 | // Provides a *mutable* iterator over just the metadata stored in the quad buffer. This skips |
113 | // unpacking the device and local quads into GrQuads and is intended for use during op |
114 | // finalization, which may require rewriting state such as color. |
115 | class MetadataIter { |
116 | public: |
117 | MetadataIter(GrQuadBuffer<T>* list) |
118 | : fBuffer(list) |
119 | , fCurrentEntry(nullptr) { |
120 | SkDEBUGCODE(fExpectedCount = list->count();) |
121 | } |
122 | |
123 | bool next(); |
124 | |
125 | T& operator*() { this->validate(); return *(fBuffer->metadata(fCurrentEntry)); } |
126 | |
127 | T* operator->() { this->validate(); return fBuffer->metadata(fCurrentEntry); } |
128 | |
129 | private: |
130 | GrQuadBuffer<T>* fBuffer; |
131 | char* fCurrentEntry; |
132 | |
133 | SkDEBUGCODE(int fExpectedCount;) |
134 | |
135 | void validate() const { |
136 | SkDEBUGCODE(fBuffer->validate(fCurrentEntry, fExpectedCount);) |
137 | } |
138 | }; |
139 | |
140 | MetadataIter metadata() { return MetadataIter(this); } |
141 | |
142 | private: |
143 | struct alignas(int32_t) { |
144 | unsigned : 2; |
145 | unsigned : 2; // Ignore if fHasLocals is false |
146 | unsigned : 1; |
147 | // Known value to detect if iteration doesn't properly advance through the buffer |
148 | SkDEBUGCODE(unsigned : 27;) |
149 | }; |
150 | static_assert(sizeof(Header) == sizeof(int32_t), "Header should be 4 bytes" ); |
151 | |
152 | static constexpr unsigned kSentinel = 0xbaffe; |
153 | static constexpr int kMetaSize = sizeof(Header) + sizeof(T); |
154 | static constexpr int k2DQuadFloats = 8; |
155 | static constexpr int k3DQuadFloats = 12; |
156 | |
157 | // Each logical entry in the buffer is a variable length tuple storing device coordinates, |
158 | // optional local coordinates, and metadata. An entry always has a header that defines the |
159 | // quad types of device and local coordinates, and always has metadata of type T. The device |
160 | // and local quads' data follows as a variable length array of floats: |
161 | // [ header ] = 4 bytes |
162 | // [ metadata ] = sizeof(T), assert alignof(T) == 4 so that pointer casts are valid |
163 | // [ device xs ] = 4 floats = 16 bytes |
164 | // [ device ys ] = 4 floats |
165 | // [ device ws ] = 4 floats or 0 floats depending on fDeviceType in header |
166 | // [ local xs ] = 4 floats or 0 floats depending on fHasLocals in header |
167 | // [ local ys ] = 4 floats or 0 floats depending on fHasLocals in header |
168 | // [ local ws ] = 4 floats or 0 floats depending on fHasLocals and fLocalType in header |
169 | // FIXME (michaelludwig) - Since this is intended only for ops, can we use the arena to |
170 | // allocate storage for the quad buffer? Since this is forward-iteration only, could also |
171 | // explore a linked-list structure for concatenating quads when batching ops |
172 | SkTDArray<char> fData; |
173 | |
174 | int fCount; // Number of (device, local, metadata) entries |
175 | GrQuad::Type fDeviceType; // Most general type of all entries |
176 | GrQuad::Type fLocalType; |
177 | |
178 | inline int entrySize(GrQuad::Type deviceType, const GrQuad::Type* localType) const { |
179 | int size = kMetaSize; |
180 | size += (deviceType == GrQuad::Type::kPerspective ? k3DQuadFloats |
181 | : k2DQuadFloats) * sizeof(float); |
182 | if (localType) { |
183 | size += (*localType == GrQuad::Type::kPerspective ? k3DQuadFloats |
184 | : k2DQuadFloats) * sizeof(float); |
185 | } |
186 | return size; |
187 | } |
188 | inline int (const Header* ) const { |
189 | if (header->fHasLocals) { |
190 | GrQuad::Type localType = static_cast<GrQuad::Type>(header->fLocalType); |
191 | return this->entrySize(static_cast<GrQuad::Type>(header->fDeviceType), &localType); |
192 | } else { |
193 | return this->entrySize(static_cast<GrQuad::Type>(header->fDeviceType), nullptr); |
194 | } |
195 | } |
196 | |
197 | // Helpers to access typed sections of the buffer, given the start of an entry |
198 | inline Header* (char* entry) { |
199 | return static_cast<Header*>(static_cast<void*>(entry)); |
200 | } |
201 | inline const Header* (const char* entry) const { |
202 | return static_cast<const Header*>(static_cast<const void*>(entry)); |
203 | } |
204 | |
205 | inline T* metadata(char* entry) { |
206 | return static_cast<T*>(static_cast<void*>(entry + sizeof(Header))); |
207 | } |
208 | inline const T* metadata(const char* entry) const { |
209 | return static_cast<const T*>(static_cast<const void*>(entry + sizeof(Header))); |
210 | } |
211 | |
212 | inline float* coords(char* entry) { |
213 | return static_cast<float*>(static_cast<void*>(entry + kMetaSize)); |
214 | } |
215 | inline const float* coords(const char* entry) const { |
216 | return static_cast<const float*>(static_cast<const void*>(entry + kMetaSize)); |
217 | } |
218 | |
219 | // Helpers to convert from coordinates to GrQuad and vice versa, returning pointer to the |
220 | // next packed quad coordinates. |
221 | float* packQuad(const GrQuad& quad, float* coords); |
222 | const float* unpackQuad(GrQuad::Type type, const float* coords, GrQuad* quad) const; |
223 | |
224 | #ifdef SK_DEBUG |
225 | void validate(const char* entry, int expectedCount) const; |
226 | #endif |
227 | }; |
228 | |
229 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
230 | // Buffer implementation |
231 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
232 | |
233 | template<typename T> |
234 | float* GrQuadBuffer<T>::packQuad(const GrQuad& quad, float* coords) { |
235 | // Copies all 12 (or 8) floats at once, so requires the 3 arrays to be contiguous |
236 | // FIXME(michaelludwig) - If this turns out not to be the case, just do 4 copies |
237 | SkASSERT(quad.xs() + 4 == quad.ys() && quad.xs() + 8 == quad.ws()); |
238 | if (quad.hasPerspective()) { |
239 | memcpy(coords, quad.xs(), k3DQuadFloats * sizeof(float)); |
240 | return coords + k3DQuadFloats; |
241 | } else { |
242 | memcpy(coords, quad.xs(), k2DQuadFloats * sizeof(float)); |
243 | return coords + k2DQuadFloats; |
244 | } |
245 | } |
246 | |
247 | template<typename T> |
248 | const float* GrQuadBuffer<T>::unpackQuad(GrQuad::Type type, const float* coords, GrQuad* quad) const { |
249 | SkASSERT(quad->xs() + 4 == quad->ys() && quad->xs() + 8 == quad->ws()); |
250 | if (type == GrQuad::Type::kPerspective) { |
251 | // Fill in X, Y, and W in one go |
252 | memcpy(quad->xs(), coords, k3DQuadFloats * sizeof(float)); |
253 | coords = coords + k3DQuadFloats; |
254 | } else { |
255 | // Fill in X and Y of the quad, the setQuadType() below will set Ws to 1 if needed |
256 | memcpy(quad->xs(), coords, k2DQuadFloats * sizeof(float)); |
257 | coords = coords + k2DQuadFloats; |
258 | } |
259 | |
260 | quad->setQuadType(type); |
261 | return coords; |
262 | } |
263 | |
264 | template<typename T> |
265 | void GrQuadBuffer<T>::append(const GrQuad& deviceQuad, T&& metadata, const GrQuad* localQuad) { |
266 | GrQuad::Type localType = localQuad ? localQuad->quadType() : GrQuad::Type::kAxisAligned; |
267 | int entrySize = this->entrySize(deviceQuad.quadType(), localQuad ? &localType : nullptr); |
268 | |
269 | // Fill in the entry, as described in fData's declaration |
270 | char* entry = fData.append(entrySize); |
271 | // First the header |
272 | Header* h = this->header(entry); |
273 | h->fDeviceType = static_cast<unsigned>(deviceQuad.quadType()); |
274 | h->fHasLocals = static_cast<unsigned>(localQuad != nullptr); |
275 | h->fLocalType = static_cast<unsigned>(localQuad ? localQuad->quadType() |
276 | : GrQuad::Type::kAxisAligned); |
277 | SkDEBUGCODE(h->fSentinel = static_cast<unsigned>(kSentinel);) |
278 | |
279 | // Second, the fixed-size metadata |
280 | static_assert(alignof(T) == 4, "Metadata must be 4 byte aligned" ); |
281 | *(this->metadata(entry)) = std::move(metadata); |
282 | |
283 | // Then the variable blocks of x, y, and w float coordinates |
284 | float* coords = this->coords(entry); |
285 | coords = this->packQuad(deviceQuad, coords); |
286 | if (localQuad) { |
287 | coords = this->packQuad(*localQuad, coords); |
288 | } |
289 | SkASSERT((char*)coords - entry == entrySize); |
290 | |
291 | // Entry complete, update buffer-level state |
292 | fCount++; |
293 | if (deviceQuad.quadType() > fDeviceType) { |
294 | fDeviceType = deviceQuad.quadType(); |
295 | } |
296 | if (localQuad && localQuad->quadType() > fLocalType) { |
297 | fLocalType = localQuad->quadType(); |
298 | } |
299 | } |
300 | |
301 | template<typename T> |
302 | void GrQuadBuffer<T>::concat(const GrQuadBuffer<T>& that) { |
303 | fData.append(that.fData.count(), that.fData.begin()); |
304 | fCount += that.fCount; |
305 | if (that.fDeviceType > fDeviceType) { |
306 | fDeviceType = that.fDeviceType; |
307 | } |
308 | if (that.fLocalType > fLocalType) { |
309 | fLocalType = that.fLocalType; |
310 | } |
311 | } |
312 | |
313 | #ifdef SK_DEBUG |
314 | template<typename T> |
315 | void GrQuadBuffer<T>::validate(const char* entry, int expectedCount) const { |
316 | // Triggers if accessing before next() is called on an iterator |
317 | SkASSERT(entry); |
318 | // Triggers if accessing after next() returns false |
319 | SkASSERT(entry < fData.end()); |
320 | // Triggers if elements have been added to the buffer while iterating entries |
321 | SkASSERT(expectedCount == fCount); |
322 | // Make sure the start of the entry looks like a header |
323 | SkASSERT(this->header(entry)->fSentinel == kSentinel); |
324 | } |
325 | #endif |
326 | |
327 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
328 | // Iterator implementations |
329 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
330 | |
331 | template<typename T> |
332 | bool GrQuadBuffer<T>::Iter::next() { |
333 | SkASSERT(fNextEntry); |
334 | if (fNextEntry >= fBuffer->fData.end()) { |
335 | return false; |
336 | } |
337 | // There is at least one more entry, so store the current start for metadata access |
338 | fCurrentEntry = fNextEntry; |
339 | |
340 | // And then unpack the device and optional local coordinates into fDeviceQuad and fLocalQuad |
341 | const Header* h = fBuffer->header(fCurrentEntry); |
342 | const float* coords = fBuffer->coords(fCurrentEntry); |
343 | coords = fBuffer->unpackQuad(static_cast<GrQuad::Type>(h->fDeviceType), coords, &fDeviceQuad); |
344 | if (h->fHasLocals) { |
345 | coords = fBuffer->unpackQuad(static_cast<GrQuad::Type>(h->fLocalType), coords, &fLocalQuad); |
346 | } // else localQuad() will return a nullptr so no need to reset fLocalQuad |
347 | |
348 | // At this point, coords points to the start of the next entry |
349 | fNextEntry = static_cast<const char*>(static_cast<const void*>(coords)); |
350 | SkASSERT((fNextEntry - fCurrentEntry) == fBuffer->entrySize(h)); |
351 | return true; |
352 | } |
353 | |
354 | template<typename T> |
355 | bool GrQuadBuffer<T>::MetadataIter::next() { |
356 | if (fCurrentEntry) { |
357 | // Advance pointer by entry size |
358 | if (fCurrentEntry < fBuffer->fData.end()) { |
359 | const Header* h = fBuffer->header(fCurrentEntry); |
360 | fCurrentEntry += fBuffer->entrySize(h); |
361 | } |
362 | } else { |
363 | // First call to next |
364 | fCurrentEntry = fBuffer->fData.begin(); |
365 | } |
366 | // Nothing else is needed to do but report whether or not the updated pointer is valid |
367 | return fCurrentEntry < fBuffer->fData.end(); |
368 | } |
369 | #endif // GrQuadBuffer_DEFINED |
370 | |