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
13template<typename T>
14class GrQuadBuffer {
15public:
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
142private:
143 struct alignas(int32_t) Header {
144 unsigned fDeviceType : 2;
145 unsigned fLocalType : 2; // Ignore if fHasLocals is false
146 unsigned fHasLocals : 1;
147 // Known value to detect if iteration doesn't properly advance through the buffer
148 SkDEBUGCODE(unsigned fSentinel : 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 entrySize(const Header* 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* header(char* entry) {
199 return static_cast<Header*>(static_cast<void*>(entry));
200 }
201 inline const Header* 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
233template<typename T>
234float* 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
247template<typename T>
248const 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
264template<typename T>
265void 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
301template<typename T>
302void 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
314template<typename T>
315void 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
331template<typename T>
332bool 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
354template<typename T>
355bool 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