• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
GrQuadBuffer()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.
count()38     int count() const { return fCount; }
39 
40     // The most general type for the device-space quads in this buffer
deviceQuadType()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.
localQuadType()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:
Iter(const GrQuadBuffer<T> * buffer)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 
metadata()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.
deviceQuad()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().
localQuad()81         GrQuad* localQuad() {
82             this->validate();
83             return this->isLocalValid() ? &fLocalQuad : nullptr;
84         }
85 
isLocalValid()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 
SkDEBUGCODE(int fExpectedCount;)103         SkDEBUGCODE(int fExpectedCount;)
104 
105         void validate() const {
106             SkDEBUGCODE(fBuffer->validate(fCurrentEntry, fExpectedCount);)
107         }
108     };
109 
iterator()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:
MetadataIter(GrQuadBuffer<T> * list)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 
SkDEBUGCODE(int fExpectedCount;)133         SkDEBUGCODE(int fExpectedCount;)
134 
135         void validate() const {
136             SkDEBUGCODE(fBuffer->validate(fCurrentEntry, fExpectedCount);)
137         }
138     };
139 
metadata()140     MetadataIter metadata() { return MetadataIter(this); }
141 
142 private:
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     inline static constexpr unsigned kSentinel = 0xbaffe;
153     inline static constexpr int kMetaSize = sizeof(Header) + sizeof(T);
154     inline static constexpr int k2DQuadFloats = 8;
155     inline 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 
entrySize(GrQuad::Type deviceType,const GrQuad::Type * localType)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     }
entrySize(const Header * header)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
header(char * entry)198     inline Header* header(char* entry) {
199         return static_cast<Header*>(static_cast<void*>(entry));
200     }
header(const char * entry)201     inline const Header* header(const char* entry) const {
202         return static_cast<const Header*>(static_cast<const void*>(entry));
203     }
204 
metadata(char * entry)205     inline T* metadata(char* entry) {
206         return static_cast<T*>(static_cast<void*>(entry + sizeof(Header)));
207     }
metadata(const char * entry)208     inline const T* metadata(const char* entry) const {
209         return static_cast<const T*>(static_cast<const void*>(entry + sizeof(Header)));
210     }
211 
coords(char * entry)212     inline float* coords(char* entry) {
213         return static_cast<float*>(static_cast<void*>(entry + kMetaSize));
214     }
coords(const char * entry)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>
packQuad(const GrQuad & quad,float * coords)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>
unpackQuad(GrQuad::Type type,const float * coords,GrQuad * quad)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>
append(const GrQuad & deviceQuad,T && metadata,const GrQuad * localQuad)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>
concat(const GrQuadBuffer<T> & that)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>
validate(const char * entry,int expectedCount)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>
next()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>
next()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