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