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