Reland "Replace GrQuadList with variable-length quad buffer"
This reverts commit19628ec144
. Reason for revert: fixed struct size on 32-bit iOS Original change's description: > Revert "Replace GrQuadList with variable-length quad buffer" > > This reverts commitf281604429
. > > Reason for revert: Breaking G3 and iOS Build > > Original change's description: > > Replace GrQuadList with variable-length quad buffer > > > > Change-Id: I5cc391e8d143032893511695961f5251f40e8291 > > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/223803 > > Reviewed-by: Brian Salomon <bsalomon@google.com> > > Commit-Queue: Michael Ludwig <michaelludwig@google.com> > > TBR=bsalomon@google.com,michaelludwig@google.com > > Change-Id: I55947c068c6472c301952e33cbc36d04505f9800 > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/223993 > Reviewed-by: Brian Salomon <bsalomon@google.com> > Commit-Queue: Brian Salomon <bsalomon@google.com> TBR=bsalomon@google.com,michaelludwig@google.com Change-Id: I7522270d467faf0f4e777831e9186bad010409ab No-Presubmit: true No-Tree-Checks: true No-Try: true Reviewed-on: https://skia-review.googlesource.com/c/skia/+/224184 Reviewed-by: Michael Ludwig <michaelludwig@google.com> Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
parent
1958fbbcff
commit
425eb45435
@ -346,7 +346,7 @@ skia_gpu_sources = [
|
||||
"$_src/gpu/geometry/GrPathUtils.h",
|
||||
"$_src/gpu/geometry/GrQuad.cpp",
|
||||
"$_src/gpu/geometry/GrQuad.h",
|
||||
"$_src/gpu/geometry/GrQuadList.h",
|
||||
"$_src/gpu/geometry/GrQuadBuffer.h",
|
||||
"$_src/gpu/geometry/GrQuadUtils.cpp",
|
||||
"$_src/gpu/geometry/GrQuadUtils.h",
|
||||
"$_src/gpu/geometry/GrRect.h",
|
||||
|
@ -106,8 +106,8 @@ tests_sources = [
|
||||
"$_tests/GrOpListFlushTest.cpp",
|
||||
"$_tests/GrPipelineDynamicStateTest.cpp",
|
||||
"$_tests/GrPorterDuffTest.cpp",
|
||||
"$_tests/GrQuadBufferTest.cpp",
|
||||
"$_tests/GrQuadCropTest.cpp",
|
||||
"$_tests/GrQuadListTest.cpp",
|
||||
"$_tests/GrShapeTest.cpp",
|
||||
"$_tests/GrSurfaceTest.cpp",
|
||||
"$_tests/GrTRecorderTest.cpp",
|
||||
|
@ -132,9 +132,11 @@ public:
|
||||
// The non-const pointers are provided to support modifying a GrQuad in-place, but care must be
|
||||
// taken to keep its quad type aligned with the geometric nature of the new coordinates. This is
|
||||
// no different than using the constructors that accept a quad type.
|
||||
|
||||
const float* xs() const { return fX; }
|
||||
float* xs() { return fX; }
|
||||
const float* ys() const { return fY; }
|
||||
float* ys() { return fY; }
|
||||
const float* ws() const { return fW; }
|
||||
float* ws() { return fW; }
|
||||
|
||||
void setQuadType(Type newType) { fType = newType; }
|
||||
|
375
src/gpu/geometry/GrQuadBuffer.h
Normal file
375
src/gpu/geometry/GrQuadBuffer.h
Normal file
@ -0,0 +1,375 @@
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "include/private/SkTDArray.h"
|
||||
#include "src/gpu/geometry/GrQuad.h"
|
||||
|
||||
template<typename T>
|
||||
class GrQuadBuffer {
|
||||
public:
|
||||
GrQuadBuffer()
|
||||
: fCount(0)
|
||||
, fDeviceType(GrQuad::Type::kAxisAligned)
|
||||
, fLocalType(GrQuad::Type::kAxisAligned) {
|
||||
// Pre-allocate space for 1 2D device-space quad, metadata, and header
|
||||
fData.reserve(this->entrySize(fDeviceType, nullptr));
|
||||
}
|
||||
|
||||
// Reserves space for the given number of entries; if 'needsLocals' is true, space will be
|
||||
// reserved for each entry to also have a 2D local quad. The reserved space assumes 2D device
|
||||
// quad for simplicity. Since this buffer has a variable bitrate encoding for quads, this may
|
||||
// over or under reserve, but pre-allocating still helps when possible.
|
||||
GrQuadBuffer(int count, bool needsLocals = false)
|
||||
: fCount(0)
|
||||
, fDeviceType(GrQuad::Type::kAxisAligned)
|
||||
, fLocalType(GrQuad::Type::kAxisAligned) {
|
||||
int entrySize = this->entrySize(fDeviceType, needsLocals ? &fLocalType : nullptr);
|
||||
fData.reserve(count * entrySize);
|
||||
}
|
||||
|
||||
// The number of device-space quads (and metadata, and optional local quads) that are in the
|
||||
// the buffer.
|
||||
int count() const { return fCount; }
|
||||
|
||||
// The most general type for the device-space quads in this buffer
|
||||
GrQuad::Type deviceQuadType() const { return fDeviceType; }
|
||||
|
||||
// The most general type for the local quads; if no local quads are ever added, this will
|
||||
// return kAxisAligned.
|
||||
GrQuad::Type localQuadType() const { return fLocalType; }
|
||||
|
||||
// Append the given 'deviceQuad' to this buffer, with its associated 'metadata'. If 'localQuad'
|
||||
// is not null, the local coordinates will also be attached to the entry. When an entry
|
||||
// has local coordinates, during iteration, the Iter::hasLocals() will return true and its
|
||||
// Iter::localQuad() will be equivalent to the provided local coordinates. If 'localQuad' is
|
||||
// null then Iter::hasLocals() will report false for the added entry.
|
||||
void append(const GrQuad& deviceQuad, T&& metadata, const GrQuad* localQuad = nullptr);
|
||||
|
||||
// Copies all entries from 'that' to this buffer
|
||||
void concat(const GrQuadBuffer<T>& that);
|
||||
|
||||
// Provides a read-only iterator over a quad buffer, giving access to the device quad, metadata
|
||||
// and optional local quad.
|
||||
class Iter {
|
||||
public:
|
||||
Iter(const GrQuadBuffer<T>* buffer)
|
||||
: fDeviceQuad(SkRect::MakeEmpty())
|
||||
, fLocalQuad(SkRect::MakeEmpty())
|
||||
, fBuffer(buffer)
|
||||
, fCurrentEntry(nullptr)
|
||||
, fNextEntry(buffer->fData.begin()) {
|
||||
SkDEBUGCODE(fExpectedCount = buffer->count();)
|
||||
}
|
||||
|
||||
bool next();
|
||||
|
||||
const T& metadata() const { this->validate(); return *(fBuffer->metadata(fCurrentEntry)); }
|
||||
|
||||
const GrQuad& deviceQuad() const { this->validate(); return fDeviceQuad; }
|
||||
|
||||
// If isLocalValid() returns false, this returns an empty quad (all 0s) so that localQuad()
|
||||
// can be called without triggering any sanitizers, for convenience when some other state
|
||||
// ensures that the quad will eventually not be used.
|
||||
const GrQuad& localQuad() const {
|
||||
this->validate();
|
||||
return fLocalQuad;
|
||||
}
|
||||
|
||||
bool isLocalValid() const {
|
||||
this->validate();
|
||||
return fBuffer->header(fCurrentEntry)->fHasLocals;
|
||||
}
|
||||
|
||||
private:
|
||||
// Quads are stored locally so that calling code doesn't need to re-declare their own quads
|
||||
GrQuad fDeviceQuad;
|
||||
GrQuad fLocalQuad;
|
||||
|
||||
const GrQuadBuffer<T>* fBuffer;
|
||||
// The pointer to the current entry to read metadata/header details from
|
||||
const char* fCurrentEntry;
|
||||
// The pointer to replace fCurrentEntry when next() is called, cached since it is calculated
|
||||
// automatically while unpacking the quad data.
|
||||
const char* fNextEntry;
|
||||
|
||||
SkDEBUGCODE(int fExpectedCount;)
|
||||
|
||||
void validate() const {
|
||||
SkDEBUGCODE(fBuffer->validate(fCurrentEntry, fExpectedCount);)
|
||||
}
|
||||
};
|
||||
|
||||
Iter iterator() const { return Iter(this); }
|
||||
|
||||
// Provides a *mutable* iterator over just the metadata stored in the quad buffer. This skips
|
||||
// unpacking the device and local quads into GrQuads and is intended for use during op
|
||||
// finalization, which may require rewriting state such as color.
|
||||
class MetadataIter {
|
||||
public:
|
||||
MetadataIter(GrQuadBuffer<T>* list)
|
||||
: fBuffer(list)
|
||||
, fCurrentEntry(nullptr) {
|
||||
SkDEBUGCODE(fExpectedCount = list->count();)
|
||||
}
|
||||
|
||||
bool next();
|
||||
|
||||
T& operator*() { this->validate(); return *(fBuffer->metadata(fCurrentEntry)); }
|
||||
|
||||
T* operator->() { this->validate(); return fBuffer->metadata(fCurrentEntry); }
|
||||
|
||||
private:
|
||||
GrQuadBuffer<T>* fBuffer;
|
||||
char* fCurrentEntry;
|
||||
|
||||
SkDEBUGCODE(int fExpectedCount;)
|
||||
|
||||
void validate() const {
|
||||
SkDEBUGCODE(fBuffer->validate(fCurrentEntry, fExpectedCount);)
|
||||
}
|
||||
};
|
||||
|
||||
MetadataIter metadata() { return MetadataIter(this); }
|
||||
|
||||
private:
|
||||
struct alignas(int32_t) Header {
|
||||
unsigned fDeviceType : 2;
|
||||
unsigned fLocalType : 2; // Ignore if fHasLocals is false
|
||||
unsigned fHasLocals : 1;
|
||||
// Known value to detect if iteration doesn't properly advance through the buffer
|
||||
SkDEBUGCODE(unsigned fSentinel : 27;)
|
||||
};
|
||||
static_assert(sizeof(Header) == sizeof(int32_t), "Header should be 4 bytes");
|
||||
|
||||
static constexpr unsigned kSentinel = 0xbaffe;
|
||||
static constexpr int kMetaSize = sizeof(Header) + sizeof(T);
|
||||
static constexpr int k2DQuadFloats = 8;
|
||||
static constexpr int k3DQuadFloats = 12;
|
||||
|
||||
// Each logical entry in the buffer is a variable length tuple storing device coordinates,
|
||||
// optional local coordinates, and metadata. An entry always has a header that defines the
|
||||
// quad types of device and local coordinates, and always has metadata of type T. The device
|
||||
// and local quads' data follows as a variable length array of floats:
|
||||
// [ header ] = 4 bytes
|
||||
// [ metadata ] = sizeof(T), assert alignof(T) == 4 so that pointer casts are valid
|
||||
// [ device xs ] = 4 floats = 16 bytes
|
||||
// [ device ys ] = 4 floats
|
||||
// [ device ws ] = 4 floats or 0 floats depending on fDeviceType in header
|
||||
// [ local xs ] = 4 floats or 0 floats depending on fHasLocals in header
|
||||
// [ local ys ] = 4 floats or 0 floats depending on fHasLocals in header
|
||||
// [ local ws ] = 4 floats or 0 floats depending on fHasLocals and fLocalType in header
|
||||
// FIXME (michaelludwig) - Since this is intended only for ops, can we use the arena to
|
||||
// allocate storage for the quad buffer? Since this is forward-iteration only, could also
|
||||
// explore a linked-list structure for concatenating quads when batching ops
|
||||
SkTDArray<char> fData;
|
||||
|
||||
int fCount; // Number of (device, local, metadata) entries
|
||||
GrQuad::Type fDeviceType; // Most general type of all entries
|
||||
GrQuad::Type fLocalType;
|
||||
|
||||
inline int entrySize(GrQuad::Type deviceType, const GrQuad::Type* localType) const {
|
||||
int size = kMetaSize;
|
||||
size += (deviceType == GrQuad::Type::kPerspective ? k3DQuadFloats
|
||||
: k2DQuadFloats) * sizeof(float);
|
||||
if (localType) {
|
||||
size += (*localType == GrQuad::Type::kPerspective ? k3DQuadFloats
|
||||
: k2DQuadFloats) * sizeof(float);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
inline int entrySize(const Header* header) const {
|
||||
if (header->fHasLocals) {
|
||||
GrQuad::Type localType = static_cast<GrQuad::Type>(header->fLocalType);
|
||||
return this->entrySize(static_cast<GrQuad::Type>(header->fDeviceType), &localType);
|
||||
} else {
|
||||
return this->entrySize(static_cast<GrQuad::Type>(header->fDeviceType), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers to access typed sections of the buffer, given the start of an entry
|
||||
inline Header* header(char* entry) {
|
||||
return static_cast<Header*>(static_cast<void*>(entry));
|
||||
}
|
||||
inline const Header* header(const char* entry) const {
|
||||
return static_cast<const Header*>(static_cast<const void*>(entry));
|
||||
}
|
||||
|
||||
inline T* metadata(char* entry) {
|
||||
return static_cast<T*>(static_cast<void*>(entry + sizeof(Header)));
|
||||
}
|
||||
inline const T* metadata(const char* entry) const {
|
||||
return static_cast<const T*>(static_cast<const void*>(entry + sizeof(Header)));
|
||||
}
|
||||
|
||||
inline float* coords(char* entry) {
|
||||
return static_cast<float*>(static_cast<void*>(entry + kMetaSize));
|
||||
}
|
||||
inline const float* coords(const char* entry) const {
|
||||
return static_cast<const float*>(static_cast<const void*>(entry + kMetaSize));
|
||||
}
|
||||
|
||||
// Helpers to convert from coordinates to GrQuad and vice versa, returning pointer to the
|
||||
// next packed quad coordinates.
|
||||
float* packQuad(const GrQuad& quad, float* coords);
|
||||
const float* unpackQuad(GrQuad::Type type, const float* coords, GrQuad* quad) const;
|
||||
|
||||
#ifdef SK_DEBUG
|
||||
void validate(const char* entry, int expectedCount) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Buffer implementation
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<typename T>
|
||||
float* GrQuadBuffer<T>::packQuad(const GrQuad& quad, float* coords) {
|
||||
// Copies all 12 (or 8) floats at once, so requires the 3 arrays to be contiguous
|
||||
// FIXME(michaelludwig) - If this turns out not to be the case, just do 4 copies
|
||||
SkASSERT(quad.xs() + 4 == quad.ys() && quad.xs() + 8 == quad.ws());
|
||||
if (quad.hasPerspective()) {
|
||||
memcpy(coords, quad.xs(), k3DQuadFloats * sizeof(float));
|
||||
return coords + k3DQuadFloats;
|
||||
} else {
|
||||
memcpy(coords, quad.xs(), k2DQuadFloats * sizeof(float));
|
||||
return coords + k2DQuadFloats;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const float* GrQuadBuffer<T>::unpackQuad(GrQuad::Type type, const float* coords, GrQuad* quad) const {
|
||||
SkASSERT(quad->xs() + 4 == quad->ys() && quad->xs() + 8 == quad->ws());
|
||||
if (type == GrQuad::Type::kPerspective) {
|
||||
// Fill in X, Y, and W in one go
|
||||
memcpy(quad->xs(), coords, k3DQuadFloats * sizeof(float));
|
||||
coords = coords + k3DQuadFloats;
|
||||
} else {
|
||||
// Fill in X and Y of the quad, and set W to 1s if needed
|
||||
memcpy(quad->xs(), coords, k2DQuadFloats * sizeof(float));
|
||||
coords = coords + k2DQuadFloats;
|
||||
|
||||
if (quad->quadType() == GrQuad::Type::kPerspective) {
|
||||
// The output quad was previously perspective, so its ws are not 1s
|
||||
static constexpr float kNoPerspectiveWs[4] = {1.f, 1.f, 1.f, 1.f};
|
||||
memcpy(quad->ws(), kNoPerspectiveWs, 4 * sizeof(float));
|
||||
}
|
||||
// Else the quad should already have 1s in w
|
||||
SkASSERT(quad->w(0) == 1.f && quad->w(1) == 1.f &&
|
||||
quad->w(2) == 1.f && quad->w(3) == 1.f);
|
||||
}
|
||||
|
||||
quad->setQuadType(type);
|
||||
return coords;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void GrQuadBuffer<T>::append(const GrQuad& deviceQuad, T&& metadata, const GrQuad* localQuad) {
|
||||
GrQuad::Type localType = localQuad ? localQuad->quadType() : GrQuad::Type::kAxisAligned;
|
||||
int entrySize = this->entrySize(deviceQuad.quadType(), localQuad ? &localType : nullptr);
|
||||
|
||||
// Fill in the entry, as described in fData's declaration
|
||||
char* entry = fData.append(entrySize);
|
||||
// First the header
|
||||
Header* h = this->header(entry);
|
||||
h->fDeviceType = static_cast<unsigned>(deviceQuad.quadType());
|
||||
h->fHasLocals = static_cast<unsigned>(localQuad != nullptr);
|
||||
h->fLocalType = static_cast<unsigned>(localQuad ? localQuad->quadType()
|
||||
: GrQuad::Type::kAxisAligned);
|
||||
SkDEBUGCODE(h->fSentinel = static_cast<unsigned>(kSentinel);)
|
||||
|
||||
// Second, the fixed-size metadata
|
||||
static_assert(alignof(T) == 4, "Metadata must be 4 byte aligned");
|
||||
*(this->metadata(entry)) = std::move(metadata);
|
||||
|
||||
// Then the variable blocks of x, y, and w float coordinates
|
||||
float* coords = this->coords(entry);
|
||||
coords = this->packQuad(deviceQuad, coords);
|
||||
if (localQuad) {
|
||||
coords = this->packQuad(*localQuad, coords);
|
||||
}
|
||||
SkASSERT((char*)coords - entry == entrySize);
|
||||
|
||||
// Entry complete, update buffer-level state
|
||||
fCount++;
|
||||
if (deviceQuad.quadType() > fDeviceType) {
|
||||
fDeviceType = deviceQuad.quadType();
|
||||
}
|
||||
if (localQuad && localQuad->quadType() > fLocalType) {
|
||||
fLocalType = localQuad->quadType();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void GrQuadBuffer<T>::concat(const GrQuadBuffer<T>& that) {
|
||||
fData.append(that.fData.count(), that.fData.begin());
|
||||
fCount += that.fCount;
|
||||
if (that.fDeviceType > fDeviceType) {
|
||||
fDeviceType = that.fDeviceType;
|
||||
}
|
||||
if (that.fLocalType > fLocalType) {
|
||||
fLocalType = that.fLocalType;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SK_DEBUG
|
||||
template<typename T>
|
||||
void GrQuadBuffer<T>::validate(const char* entry, int expectedCount) const {
|
||||
// Triggers if accessing before next() is called on an iterator
|
||||
SkASSERT(entry);
|
||||
// Triggers if accessing after next() returns false
|
||||
SkASSERT(entry < fData.end());
|
||||
// Triggers if elements have been added to the buffer while iterating entries
|
||||
SkASSERT(expectedCount == fCount);
|
||||
// Make sure the start of the entry looks like a header
|
||||
SkASSERT(this->header(entry)->fSentinel == kSentinel);
|
||||
}
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Iterator implementations
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<typename T>
|
||||
bool GrQuadBuffer<T>::Iter::next() {
|
||||
SkASSERT(fNextEntry);
|
||||
if (fNextEntry >= fBuffer->fData.end()) {
|
||||
return false;
|
||||
}
|
||||
// There is at least one more entry, so store the current start for metadata access
|
||||
fCurrentEntry = fNextEntry;
|
||||
|
||||
// And then unpack the device and optional local coordinates into fDeviceQuad and fLocalQuad
|
||||
const Header* h = fBuffer->header(fCurrentEntry);
|
||||
const float* coords = fBuffer->coords(fCurrentEntry);
|
||||
coords = fBuffer->unpackQuad(static_cast<GrQuad::Type>(h->fDeviceType), coords, &fDeviceQuad);
|
||||
if (h->fHasLocals) {
|
||||
coords = fBuffer->unpackQuad(static_cast<GrQuad::Type>(h->fLocalType), coords, &fLocalQuad);
|
||||
} else {
|
||||
static const GrQuad kEmptyLocal(SkRect::MakeEmpty());
|
||||
fLocalQuad = kEmptyLocal;
|
||||
}
|
||||
// At this point, coords points to the start of the next entry
|
||||
fNextEntry = static_cast<const char*>(static_cast<const void*>(coords));
|
||||
SkASSERT((fNextEntry - fCurrentEntry) == fBuffer->entrySize(h));
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool GrQuadBuffer<T>::MetadataIter::next() {
|
||||
if (fCurrentEntry) {
|
||||
// Advance pointer by entry size
|
||||
if (fCurrentEntry < fBuffer->fData.end()) {
|
||||
const Header* h = fBuffer->header(fCurrentEntry);
|
||||
fCurrentEntry += fBuffer->entrySize(h);
|
||||
}
|
||||
} else {
|
||||
// First call to next
|
||||
fCurrentEntry = fBuffer->fData.begin();
|
||||
}
|
||||
// Nothing else is needed to do but report whether or not the updated pointer is valid
|
||||
return fCurrentEntry < fBuffer->fData.end();
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef GrQuadList_DEFINED
|
||||
#define GrQuadList_DEFINED
|
||||
|
||||
#include "include/private/SkTArray.h"
|
||||
#include "src/gpu/geometry/GrQuad.h"
|
||||
|
||||
// Underlying data used by GrQuadListBase. It is defined outside of GrQuadListBase due to compiler
|
||||
// issues related to specializing member types.
|
||||
template<typename T>
|
||||
struct QuadData {
|
||||
float fX[4];
|
||||
float fY[4];
|
||||
T fMetadata;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct QuadData<void> {
|
||||
float fX[4];
|
||||
float fY[4];
|
||||
};
|
||||
|
||||
// A dynamic list of (possibly) perspective quads that tracks the most general quad type of all
|
||||
// added quads. It avoids storing the 3rd component if the quad type never becomes perspective.
|
||||
// Use GrQuadList subclass when only storing quads. Use GrTQuadList subclass when storing quads
|
||||
// and per-quad templated metadata (such as color or domain).
|
||||
template<typename T>
|
||||
class GrQuadListBase {
|
||||
public:
|
||||
|
||||
int count() const { return fXYs.count(); }
|
||||
|
||||
GrQuad::Type quadType() const { return fType; }
|
||||
|
||||
void reserve(int count, bool needsPerspective) {
|
||||
fXYs.reserve(count);
|
||||
if (needsPerspective || fType == GrQuad::Type::kPerspective) {
|
||||
fWs.reserve(4 * count);
|
||||
}
|
||||
}
|
||||
|
||||
GrQuad operator[] (int i) const {
|
||||
SkASSERT(i < this->count());
|
||||
SkASSERT(i >= 0);
|
||||
|
||||
const QuadData<T>& item = fXYs[i];
|
||||
if (fType == GrQuad::Type::kPerspective) {
|
||||
// Read the explicit ws
|
||||
return GrQuad(item.fX, item.fY, fWs.begin() + 4 * i, fType);
|
||||
} else {
|
||||
// Ws are implicitly 1s.
|
||||
static constexpr float kNoPerspectiveWs[4] = {1.f, 1.f, 1.f, 1.f};
|
||||
return GrQuad(item.fX, item.fY, kNoPerspectiveWs, fType);
|
||||
}
|
||||
}
|
||||
|
||||
// Subclasses expose push_back(const GrQuad|GrPerspQuad&, GrQuadType, [const T&]), where
|
||||
// the metadata argument is only present in GrTQuadList's push_back definition.
|
||||
|
||||
protected:
|
||||
GrQuadListBase() : fType(GrQuad::Type::kAxisAligned) {}
|
||||
|
||||
void concatImpl(const GrQuadListBase<T>& that) {
|
||||
this->upgradeType(that.fType);
|
||||
fXYs.push_back_n(that.fXYs.count(), that.fXYs.begin());
|
||||
if (fType == GrQuad::Type::kPerspective) {
|
||||
if (that.fType == GrQuad::Type::kPerspective) {
|
||||
// Copy the other's ws into the end of this list's data
|
||||
fWs.push_back_n(that.fWs.count(), that.fWs.begin());
|
||||
} else {
|
||||
// This list stores ws but the appended list had implicit 1s, so add explicit 1s to
|
||||
// fill out the total list
|
||||
fWs.push_back_n(4 * that.count(), 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the added item data so that its metadata can be initialized if T is not void
|
||||
QuadData<T>& pushBackImpl(const GrQuad& quad) {
|
||||
this->upgradeType(quad.quadType());
|
||||
QuadData<T>& item = fXYs.push_back();
|
||||
memcpy(item.fX, quad.fX, 4 * sizeof(float));
|
||||
memcpy(item.fY, quad.fY, 4 * sizeof(float));
|
||||
if (fType == GrQuad::Type::kPerspective) {
|
||||
fWs.push_back_n(4, quad.fW);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
const QuadData<T>& item(int i) const {
|
||||
return fXYs[i];
|
||||
}
|
||||
|
||||
QuadData<T>& item(int i) {
|
||||
return fXYs[i];
|
||||
}
|
||||
|
||||
private:
|
||||
void upgradeType(GrQuad::Type type) {
|
||||
// Possibly upgrade the overall type tracked by the list
|
||||
if (type > fType) {
|
||||
fType = type;
|
||||
if (type == GrQuad::Type::kPerspective) {
|
||||
// All existing quads were 2D, so the ws array just needs to be filled with 1s
|
||||
fWs.push_back_n(4 * this->count(), 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interleaves xs, ys, and per-quad metadata so that all data for a single quad is together
|
||||
// (barring ws, which can be dropped entirely if the quad type allows it).
|
||||
SkSTArray<1, QuadData<T>, true> fXYs;
|
||||
// The w channel is kept separate so that it can remain empty when only dealing with 2D quads.
|
||||
SkTArray<float, true> fWs;
|
||||
|
||||
GrQuad::Type fType;
|
||||
};
|
||||
|
||||
// This list only stores the quad data itself.
|
||||
class GrQuadList : public GrQuadListBase<void> {
|
||||
public:
|
||||
GrQuadList() : INHERITED() {}
|
||||
|
||||
void concat(const GrQuadList& that) {
|
||||
this->concatImpl(that);
|
||||
}
|
||||
|
||||
void push_back(const GrQuad& quad) {
|
||||
this->pushBackImpl(quad);
|
||||
}
|
||||
|
||||
private:
|
||||
typedef GrQuadListBase<void> INHERITED;
|
||||
};
|
||||
|
||||
// This variant of the list allows simple metadata to be stored per quad as well, such as color
|
||||
// or texture domain.
|
||||
template<typename T>
|
||||
class GrTQuadList : public GrQuadListBase<T> {
|
||||
public:
|
||||
GrTQuadList() : INHERITED() {}
|
||||
|
||||
void concat(const GrTQuadList<T>& that) {
|
||||
this->concatImpl(that);
|
||||
}
|
||||
|
||||
// Adding to the list requires metadata
|
||||
void push_back(const GrQuad& quad, T&& metadata) {
|
||||
QuadData<T>& item = this->pushBackImpl(quad);
|
||||
item.fMetadata = std::move(metadata);
|
||||
}
|
||||
|
||||
// And provide access to the metadata per quad
|
||||
const T& metadata(int i) const {
|
||||
return this->item(i).fMetadata;
|
||||
}
|
||||
|
||||
T& metadata(int i) {
|
||||
return this->item(i).fMetadata;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef GrQuadListBase<T> INHERITED;
|
||||
};
|
||||
|
||||
#endif
|
@ -14,7 +14,7 @@
|
||||
#include "src/gpu/GrPaint.h"
|
||||
#include "src/gpu/SkGr.h"
|
||||
#include "src/gpu/geometry/GrQuad.h"
|
||||
#include "src/gpu/geometry/GrQuadList.h"
|
||||
#include "src/gpu/geometry/GrQuadBuffer.h"
|
||||
#include "src/gpu/geometry/GrQuadUtils.h"
|
||||
#include "src/gpu/glsl/GrGLSLColorSpaceXformHelper.h"
|
||||
#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
|
||||
@ -79,17 +79,13 @@ public:
|
||||
GrQuadAAFlags edgeFlags, const GrUserStencilSettings* stencil,
|
||||
const GrQuad& deviceQuad, const GrQuad& localQuad)
|
||||
: INHERITED(ClassID())
|
||||
, fHelper(args, aaType, stencil) {
|
||||
// The color stored with the quad is the clear color if a scissor-clear is decided upon
|
||||
// when executing the op.
|
||||
fDeviceQuads.push_back(deviceQuad, { paintColor, edgeFlags });
|
||||
|
||||
if (!fHelper.isTrivial()) {
|
||||
// Conservatively keep track of the local coordinates; it may be that the paint doesn't
|
||||
// need them after analysis is finished. If the paint is known to be solid up front they
|
||||
// can be skipped entirely.
|
||||
fLocalQuads.push_back(localQuad);
|
||||
}
|
||||
, fHelper(args, aaType, stencil)
|
||||
, fQuads(1, !fHelper.isTrivial()) {
|
||||
// Conservatively keep track of the local coordinates; it may be that the paint doesn't
|
||||
// need them after analysis is finished. If the paint is known to be solid up front they
|
||||
// can be skipped entirely.
|
||||
fQuads.append(deviceQuad, { paintColor, edgeFlags },
|
||||
fHelper.isTrivial() ? nullptr : &localQuad);
|
||||
this->setBounds(deviceQuad.bounds(), HasAABloat(aaType == GrAAType::kCoverage),
|
||||
IsZeroArea::kNo);
|
||||
}
|
||||
@ -103,18 +99,17 @@ public:
|
||||
#ifdef SK_DEBUG
|
||||
SkString dumpInfo() const override {
|
||||
SkString str;
|
||||
str.appendf("# draws: %u\n", this->quadCount());
|
||||
str.appendf("# draws: %u\n", fQuads.count());
|
||||
str.appendf("Device quad type: %u, local quad type: %u\n",
|
||||
(uint32_t) fDeviceQuads.quadType(), (uint32_t) fLocalQuads.quadType());
|
||||
(uint32_t) fQuads.deviceQuadType(), (uint32_t) fQuads.localQuadType());
|
||||
str += fHelper.dumpInfo();
|
||||
GrQuad device, local;
|
||||
for (int i = 0; i < this->quadCount(); i++) {
|
||||
device = fDeviceQuads[i];
|
||||
const ColorAndAA& info = fDeviceQuads.metadata(i);
|
||||
if (!fHelper.isTrivial()) {
|
||||
local = fLocalQuads[i];
|
||||
}
|
||||
str += dump_quad_info(i, device, local, info.fColor, info.fAAFlags);
|
||||
int i = 0;
|
||||
auto iter = fQuads.iterator();
|
||||
while(iter.next()) {
|
||||
const ColorAndAA& info = iter.metadata();
|
||||
str += dump_quad_info(i, iter.deviceQuad(), iter.localQuad(),
|
||||
info.fColor, info.fAAFlags);
|
||||
i++;
|
||||
}
|
||||
str += INHERITED::dumpInfo();
|
||||
return str;
|
||||
@ -125,12 +120,12 @@ public:
|
||||
const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
|
||||
GrClampType clampType) override {
|
||||
// Initialize aggregate color analysis with the first quad's color (which always exists)
|
||||
SkASSERT(this->quadCount() > 0);
|
||||
GrProcessorAnalysisColor quadColors(fDeviceQuads.metadata(0).fColor);
|
||||
auto iter = fQuads.metadata();
|
||||
SkAssertResult(iter.next());
|
||||
GrProcessorAnalysisColor quadColors(iter->fColor);
|
||||
// Then combine the colors of any additional quads (e.g. from MakeSet)
|
||||
for (int i = 1; i < this->quadCount(); ++i) {
|
||||
quadColors = GrProcessorAnalysisColor::Combine(quadColors,
|
||||
fDeviceQuads.metadata(i).fColor);
|
||||
while(iter.next()) {
|
||||
quadColors = GrProcessorAnalysisColor::Combine(quadColors, iter->fColor);
|
||||
if (quadColors.isUnknown()) {
|
||||
// No point in accumulating additional starting colors, combining cannot make it
|
||||
// less unknown.
|
||||
@ -147,19 +142,19 @@ public:
|
||||
caps, clip, hasMixedSampledCoverage, clampType, coverage, &quadColors);
|
||||
// If there is a constant color after analysis, that means all of the quads should be set
|
||||
// to the same color (even if they started out with different colors).
|
||||
iter = fQuads.metadata();
|
||||
SkPMColor4f colorOverride;
|
||||
if (quadColors.isConstant(&colorOverride)) {
|
||||
fColorType = GrQuadPerEdgeAA::MinColorType(colorOverride, clampType, caps);
|
||||
for (int i = 0; i < this->quadCount(); ++i) {
|
||||
fDeviceQuads.metadata(i).fColor = colorOverride;
|
||||
while(iter.next()) {
|
||||
iter->fColor = colorOverride;
|
||||
}
|
||||
} else {
|
||||
// Otherwise compute the color type needed as the max over all quads.
|
||||
fColorType = ColorType::kNone;
|
||||
for (int i = 0; i < this->quadCount(); ++i) {
|
||||
SkPMColor4f* color = &fDeviceQuads.metadata(i).fColor;
|
||||
while(iter.next()) {
|
||||
fColorType = SkTMax(fColorType,
|
||||
GrQuadPerEdgeAA::MinColorType(*color, clampType, caps));
|
||||
GrQuadPerEdgeAA::MinColorType(iter->fColor, clampType, caps));
|
||||
}
|
||||
}
|
||||
// Most SkShaders' FPs multiply their calculated color by the paint color or alpha. We want
|
||||
@ -197,7 +192,7 @@ private:
|
||||
using Domain = GrQuadPerEdgeAA::Domain;
|
||||
static constexpr SkRect kEmptyDomain = SkRect::MakeEmpty();
|
||||
|
||||
VertexSpec vertexSpec(fDeviceQuads.quadType(), fColorType, fLocalQuads.quadType(),
|
||||
VertexSpec vertexSpec(fQuads.deviceQuadType(), fColorType, fQuads.localQuadType(),
|
||||
fHelper.usesLocalCoords(), Domain::kNo, fHelper.aaType(),
|
||||
fHelper.compatibleWithCoverageAsAlpha());
|
||||
// Make sure that if the op thought it was a solid color, the vertex spec does not use
|
||||
@ -212,7 +207,7 @@ private:
|
||||
|
||||
// Fill the allocated vertex data
|
||||
void* vdata = target->makeVertexSpace(
|
||||
vertexSize, this->quadCount() * vertexSpec.verticesPerQuad(),
|
||||
vertexSize, fQuads.count() * vertexSpec.verticesPerQuad(),
|
||||
&vbuffer, &vertexOffsetInBuffer);
|
||||
if (!vdata) {
|
||||
SkDebugf("Could not allocate vertices\n");
|
||||
@ -221,27 +216,19 @@ private:
|
||||
|
||||
// vertices pointer advances through vdata based on Tessellate's return value
|
||||
void* vertices = vdata;
|
||||
if (fHelper.isTrivial()) {
|
||||
SkASSERT(fLocalQuads.count() == 0); // No local coords, so send an ignored dummy quad
|
||||
static const GrQuad kIgnoredLocal(SkRect::MakeEmpty());
|
||||
|
||||
for (int i = 0; i < this->quadCount(); ++i) {
|
||||
const ColorAndAA& info = fDeviceQuads.metadata(i);
|
||||
vertices = GrQuadPerEdgeAA::Tessellate(vertices, vertexSpec, fDeviceQuads[i],
|
||||
info.fColor, kIgnoredLocal, kEmptyDomain, info.fAAFlags);
|
||||
}
|
||||
} else {
|
||||
SkASSERT(fLocalQuads.count() == fDeviceQuads.count());
|
||||
for (int i = 0; i < this->quadCount(); ++i) {
|
||||
const ColorAndAA& info = fDeviceQuads.metadata(i);
|
||||
vertices = GrQuadPerEdgeAA::Tessellate(vertices, vertexSpec, fDeviceQuads[i],
|
||||
info.fColor, fLocalQuads[i], kEmptyDomain, info.fAAFlags);
|
||||
}
|
||||
auto iter = fQuads.iterator();
|
||||
while(iter.next()) {
|
||||
// All entries should have local coords, or no entries should have local coords,
|
||||
// matching !helper.isTrivial() (which is more conservative than helper.usesLocalCoords)
|
||||
SkASSERT(iter.isLocalValid() != fHelper.isTrivial());
|
||||
auto info = iter.metadata();
|
||||
vertices = GrQuadPerEdgeAA::Tessellate(vertices, vertexSpec, iter.deviceQuad(),
|
||||
info.fColor, iter.localQuad(), kEmptyDomain, info.fAAFlags);
|
||||
}
|
||||
|
||||
// Configure the mesh for the vertex data
|
||||
GrMesh* mesh = target->allocMeshes(1);
|
||||
if (!GrQuadPerEdgeAA::ConfigureMeshIndices(target, mesh, vertexSpec, this->quadCount())) {
|
||||
if (!GrQuadPerEdgeAA::ConfigureMeshIndices(target, mesh, vertexSpec, fQuads.count())) {
|
||||
SkDebugf("Could not allocate indices\n");
|
||||
return;
|
||||
}
|
||||
@ -259,7 +246,7 @@ private:
|
||||
|
||||
if ((fHelper.aaType() == GrAAType::kCoverage ||
|
||||
that->fHelper.aaType() == GrAAType::kCoverage) &&
|
||||
this->quadCount() + that->quadCount() > GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer) {
|
||||
fQuads.count() + that->fQuads.count() > GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer) {
|
||||
// This limit on batch size seems to help on Adreno devices
|
||||
return CombineResult::kCannotCombine;
|
||||
}
|
||||
@ -285,10 +272,7 @@ private:
|
||||
fHelper.setAAType(GrAAType::kCoverage);
|
||||
}
|
||||
|
||||
fDeviceQuads.concat(that->fDeviceQuads);
|
||||
if (!fHelper.isTrivial()) {
|
||||
fLocalQuads.concat(that->fLocalQuads);
|
||||
}
|
||||
fQuads.concat(that->fQuads);
|
||||
return CombineResult::kMerged;
|
||||
}
|
||||
|
||||
@ -316,17 +300,7 @@ private:
|
||||
newBounds.joinPossiblyEmptyRect(deviceQuad.bounds());
|
||||
this->setBounds(newBounds, HasAABloat(fHelper.aaType() == GrAAType::kCoverage),
|
||||
IsZeroArea::kNo);
|
||||
fDeviceQuads.push_back(deviceQuad, { color, edgeAA });
|
||||
if (!fHelper.isTrivial()) {
|
||||
fLocalQuads.push_back(localQuad);
|
||||
}
|
||||
}
|
||||
|
||||
int quadCount() const {
|
||||
// Sanity check that the parallel arrays for quad properties all have the same size
|
||||
SkASSERT(fDeviceQuads.count() == fLocalQuads.count() ||
|
||||
(fLocalQuads.count() == 0 && fHelper.isTrivial()));
|
||||
return fDeviceQuads.count();
|
||||
fQuads.append(deviceQuad, { color, edgeAA }, fHelper.isTrivial() ? nullptr : &localQuad);
|
||||
}
|
||||
|
||||
struct ColorAndAA {
|
||||
@ -335,9 +309,7 @@ private:
|
||||
};
|
||||
|
||||
Helper fHelper;
|
||||
GrTQuadList<ColorAndAA> fDeviceQuads;
|
||||
// No metadata attached to the local quads; this list is empty when local coords are not needed.
|
||||
GrQuadList fLocalQuads;
|
||||
GrQuadBuffer<ColorAndAA> fQuads;
|
||||
|
||||
ColorType fColorType;
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
#include "src/gpu/GrTextureProxy.h"
|
||||
#include "src/gpu/SkGr.h"
|
||||
#include "src/gpu/geometry/GrQuad.h"
|
||||
#include "src/gpu/geometry/GrQuadList.h"
|
||||
#include "src/gpu/geometry/GrQuadBuffer.h"
|
||||
#include "src/gpu/geometry/GrQuadUtils.h"
|
||||
#include "src/gpu/glsl/GrGLSLVarying.h"
|
||||
#include "src/gpu/ops/GrMeshDrawOp.h"
|
||||
@ -200,13 +200,14 @@ public:
|
||||
SkString dumpInfo() const override {
|
||||
SkString str;
|
||||
str.appendf("# draws: %d\n", fQuads.count());
|
||||
int q = 0;
|
||||
auto iter = fQuads.iterator();
|
||||
for (unsigned p = 0; p < fProxyCnt; ++p) {
|
||||
str.appendf("Proxy ID: %d, Filter: %d\n", fProxies[p].fProxy->uniqueID().asUInt(),
|
||||
static_cast<int>(fFilter));
|
||||
for (int i = 0; i < fProxies[p].fQuadCnt; ++i, ++q) {
|
||||
GrQuad quad = fQuads[q];
|
||||
const ColorDomainAndAA& info = fQuads.metadata(i);
|
||||
int i = 0;
|
||||
while(i < fProxies[p].fQuadCnt && iter.next()) {
|
||||
const GrQuad& quad = iter.deviceQuad();
|
||||
const ColorDomainAndAA& info = iter.metadata();
|
||||
str.appendf(
|
||||
"%d: Color: 0x%08x, TexRect [L: %.2f, T: %.2f, R: %.2f, B: %.2f] "
|
||||
"Quad [(%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f)]\n",
|
||||
@ -215,6 +216,7 @@ public:
|
||||
quad.point(0).fY, quad.point(1).fX, quad.point(1).fY,
|
||||
quad.point(2).fX, quad.point(2).fY, quad.point(3).fX,
|
||||
quad.point(3).fY);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
str += INHERITED::dumpInfo();
|
||||
@ -226,9 +228,9 @@ public:
|
||||
const GrCaps& caps, const GrAppliedClip*, bool hasMixedSampledCoverage,
|
||||
GrClampType clampType) override {
|
||||
fColorType = static_cast<unsigned>(ColorType::kNone);
|
||||
for (int q = 0; q < fQuads.count(); ++q) {
|
||||
const ColorDomainAndAA& info = fQuads.metadata(q);
|
||||
auto colorType = GrQuadPerEdgeAA::MinColorType(info.fColor, clampType, caps);
|
||||
auto iter = fQuads.metadata();
|
||||
while(iter.next()) {
|
||||
auto colorType = GrQuadPerEdgeAA::MinColorType(iter->fColor, clampType, caps);
|
||||
fColorType = SkTMax(fColorType, static_cast<unsigned>(colorType));
|
||||
}
|
||||
return GrProcessorSet::EmptySetAnalysis();
|
||||
@ -244,6 +246,35 @@ public:
|
||||
private:
|
||||
friend class ::GrOpMemoryPool;
|
||||
|
||||
struct ColorDomainAndAA {
|
||||
// Special constructor to convert enums into the packed bits, which should not delete
|
||||
// the implicit move constructor (but it does require us to declare an empty ctor for
|
||||
// use with the GrTQuadList).
|
||||
ColorDomainAndAA(const SkPMColor4f& color, const SkRect& srcRect,
|
||||
Domain hasDomain, GrQuadAAFlags aaFlags)
|
||||
: fColor(color)
|
||||
, fSrcRect(srcRect)
|
||||
, fHasDomain(static_cast<unsigned>(hasDomain))
|
||||
, fAAFlags(static_cast<unsigned>(aaFlags)) {
|
||||
SkASSERT(fHasDomain == static_cast<unsigned>(hasDomain));
|
||||
SkASSERT(fAAFlags == static_cast<unsigned>(aaFlags));
|
||||
}
|
||||
ColorDomainAndAA() = default;
|
||||
|
||||
SkPMColor4f fColor;
|
||||
// Even if fSrcQuadIndex provides source coords, use fSrcRect for domain constraint
|
||||
SkRect fSrcRect;
|
||||
unsigned fHasDomain : 1;
|
||||
unsigned fAAFlags : 4;
|
||||
|
||||
Domain domain() const { return Domain(fHasDomain); }
|
||||
GrQuadAAFlags aaFlags() const { return static_cast<GrQuadAAFlags>(fAAFlags); }
|
||||
};
|
||||
struct Proxy {
|
||||
GrTextureProxy* fProxy;
|
||||
int fQuadCnt;
|
||||
};
|
||||
|
||||
// dstQuad and dstQuadType should be the geometry transformed by the view matrix.
|
||||
// srcRect represents original src rect and will be used as the domain when constraint is strict
|
||||
// If srcQuad is provided, it will be used for the local coords instead of srcRect, although
|
||||
@ -253,6 +284,7 @@ private:
|
||||
SkCanvas::SrcRectConstraint constraint, const GrQuad* srcQuad, GrAAType aaType,
|
||||
GrQuadAAFlags aaFlags, sk_sp<GrColorSpaceXform> textureColorSpaceXform)
|
||||
: INHERITED(ClassID())
|
||||
, fQuads(1, srcQuad != nullptr)
|
||||
, fTextureColorSpaceXform(std::move(textureColorSpaceXform))
|
||||
, fFilter(static_cast<unsigned>(filter)) {
|
||||
// Clean up disparities between the overall aa type and edge configuration and apply
|
||||
@ -275,11 +307,8 @@ private:
|
||||
|
||||
Domain domain = constraint == SkCanvas::kStrict_SrcRectConstraint ? Domain::kYes
|
||||
: Domain::kNo;
|
||||
// Initially, if srcQuad is provided it will always be at index 0 of fSrcQuads
|
||||
fQuads.push_back(dstQuad, {color, srcRect, srcQuad ? 0 : -1, domain, aaFlags});
|
||||
if (srcQuad) {
|
||||
fSrcQuads.push_back(*srcQuad);
|
||||
}
|
||||
fQuads.append(dstQuad, {color, srcRect, domain, aaFlags}, srcQuad);
|
||||
|
||||
fProxyCnt = 1;
|
||||
fProxies[0] = {proxy.release(), 1};
|
||||
this->setBounds(dstQuad.bounds(), HasAABloat(aaType == GrAAType::kCoverage),
|
||||
@ -291,16 +320,13 @@ private:
|
||||
SkCanvas::SrcRectConstraint constraint, const SkMatrix& viewMatrix,
|
||||
sk_sp<GrColorSpaceXform> textureColorSpaceXform)
|
||||
: INHERITED(ClassID())
|
||||
, fQuads(cnt, false)
|
||||
, fTextureColorSpaceXform(std::move(textureColorSpaceXform))
|
||||
, fFilter(static_cast<unsigned>(filter)) {
|
||||
fProxyCnt = SkToUInt(cnt);
|
||||
SkRect bounds = SkRectPriv::MakeLargestInverted();
|
||||
GrAAType overallAAType = GrAAType::kNone; // aa type maximally compatible with all dst rects
|
||||
bool mustFilter = false;
|
||||
// Most dst rects are transformed by the same view matrix, so their quad types start
|
||||
// identical, unless an entry provides a dstClip or additional transform that changes it.
|
||||
// The quad list will automatically adapt to that.
|
||||
fQuads.reserve(cnt, viewMatrix.hasPerspective());
|
||||
bool allOpaque = true;
|
||||
Domain netDomain = Domain::kNo;
|
||||
for (unsigned p = 0; p < fProxyCnt; ++p) {
|
||||
@ -348,16 +374,17 @@ private:
|
||||
float alpha = SkTPin(set[p].fAlpha, 0.f, 1.f);
|
||||
allOpaque &= (1.f == alpha);
|
||||
SkPMColor4f color{alpha, alpha, alpha, alpha};
|
||||
int srcQuadIndex = -1;
|
||||
GrQuad srcQuad;
|
||||
const GrQuad* srcQuadPtr = nullptr;
|
||||
if (set[p].fDstClipQuad) {
|
||||
// Derive new source coordinates that match dstClip's relative locations in dstRect,
|
||||
// but with respect to srcRect
|
||||
SkPoint srcQuad[4];
|
||||
GrMapRectPoints(set[p].fDstRect, set[p].fSrcRect, set[p].fDstClipQuad, srcQuad, 4);
|
||||
fSrcQuads.push_back(GrQuad::MakeFromSkQuad(srcQuad, SkMatrix::I()));
|
||||
srcQuadIndex = fSrcQuads.count() - 1;
|
||||
SkPoint srcPts[4];
|
||||
GrMapRectPoints(set[p].fDstRect, set[p].fSrcRect, set[p].fDstClipQuad, srcPts, 4);
|
||||
srcQuad = GrQuad::MakeFromSkQuad(srcPts, SkMatrix::I());
|
||||
srcQuadPtr = &srcQuad;
|
||||
}
|
||||
fQuads.push_back(quad, {color, set[p].fSrcRect, srcQuadIndex, domainForQuad, aaFlags});
|
||||
fQuads.append(quad, {color, set[p].fSrcRect, domainForQuad, aaFlags}, srcQuadPtr);
|
||||
}
|
||||
fAAType = static_cast<unsigned>(overallAAType);
|
||||
if (!mustFilter) {
|
||||
@ -367,8 +394,8 @@ private:
|
||||
fDomain = static_cast<unsigned>(netDomain);
|
||||
}
|
||||
|
||||
void tess(void* v, const VertexSpec& spec, const GrTextureProxy* proxy, int start,
|
||||
int cnt) const {
|
||||
void tess(void* v, const VertexSpec& spec, const GrTextureProxy* proxy,
|
||||
GrQuadBuffer<ColorDomainAndAA>::Iter* iter, int cnt) const {
|
||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||
auto origin = proxy->origin();
|
||||
const auto* texture = proxy->peekTexture();
|
||||
@ -382,17 +409,18 @@ private:
|
||||
h = 1.f;
|
||||
}
|
||||
|
||||
for (int i = start; i < start + cnt; ++i) {
|
||||
const GrQuad& device = fQuads[i];
|
||||
const ColorDomainAndAA& info = fQuads.metadata(i);
|
||||
int i = 0;
|
||||
while(i < cnt && iter->next()) {
|
||||
const ColorDomainAndAA& info = iter->metadata();
|
||||
|
||||
GrQuad srcQuad = info.fSrcQuadIndex >= 0 ?
|
||||
compute_src_quad(origin, fSrcQuads[info.fSrcQuadIndex], iw, ih, h) :
|
||||
GrQuad srcQuad = iter->isLocalValid() ?
|
||||
compute_src_quad(origin, iter->localQuad(), iw, ih, h) :
|
||||
compute_src_quad_from_rect(origin, info.fSrcRect, iw, ih, h);
|
||||
SkRect domain =
|
||||
compute_domain(info.domain(), this->filter(), origin, info.fSrcRect, iw, ih, h);
|
||||
v = GrQuadPerEdgeAA::Tessellate(v, spec, device, info.fColor, srcQuad, domain,
|
||||
info.aaFlags());
|
||||
v = GrQuadPerEdgeAA::Tessellate(v, spec, iter->deviceQuad(), info.fColor, srcQuad,
|
||||
domain, info.aaFlags());
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,13 +437,11 @@ private:
|
||||
const GrSwizzle& swizzle = fProxies[0].fProxy->textureSwizzle();
|
||||
GrAAType aaType = this->aaType();
|
||||
for (const auto& op : ChainRange<TextureOp>(this)) {
|
||||
if (op.fQuads.quadType() > quadType) {
|
||||
quadType = op.fQuads.quadType();
|
||||
if (op.fQuads.deviceQuadType() > quadType) {
|
||||
quadType = op.fQuads.deviceQuadType();
|
||||
}
|
||||
if (op.fSrcQuads.quadType() > srcQuadType) {
|
||||
// Should only become more general if there are quads to use instead of fSrcRect
|
||||
SkASSERT(op.fSrcQuads.count() > 0);
|
||||
srcQuadType = op.fSrcQuads.quadType();
|
||||
if (op.fQuads.localQuadType() > srcQuadType) {
|
||||
srcQuadType = op.fQuads.localQuadType();
|
||||
}
|
||||
if (op.fDomain) {
|
||||
domain = Domain::kYes;
|
||||
@ -475,7 +501,7 @@ private:
|
||||
|
||||
int m = 0;
|
||||
for (const auto& op : ChainRange<TextureOp>(this)) {
|
||||
int q = 0;
|
||||
auto iter = op.fQuads.iterator();
|
||||
for (unsigned p = 0; p < op.fProxyCnt; ++p) {
|
||||
int quadCnt = op.fProxies[p].fQuadCnt;
|
||||
auto* proxy = op.fProxies[p].fProxy;
|
||||
@ -492,7 +518,7 @@ private:
|
||||
}
|
||||
SkASSERT(numAllocatedVertices >= meshVertexCnt);
|
||||
|
||||
op.tess(vdata, vertexSpec, proxy, q, quadCnt);
|
||||
op.tess(vdata, vertexSpec, proxy, &iter, quadCnt);
|
||||
|
||||
if (!GrQuadPerEdgeAA::ConfigureMeshIndices(target, &(meshes[m]), vertexSpec,
|
||||
quadCnt)) {
|
||||
@ -508,8 +534,10 @@ private:
|
||||
numQuadVerticesLeft -= meshVertexCnt;
|
||||
vertexOffsetInBuffer += meshVertexCnt;
|
||||
vdata = reinterpret_cast<char*>(vdata) + vertexSize * meshVertexCnt;
|
||||
q += quadCnt;
|
||||
}
|
||||
// If quad counts per proxy were calculated correctly, the entire iterator should have
|
||||
// been consumed.
|
||||
SkASSERT(!iter.next());
|
||||
}
|
||||
SkASSERT(!numQuadVerticesLeft);
|
||||
SkASSERT(!numAllocatedVertices);
|
||||
@ -567,79 +595,17 @@ private:
|
||||
fAAType = static_cast<unsigned>(GrAAType::kCoverage);
|
||||
}
|
||||
|
||||
// Concatenate quad lists together, updating the fSrcQuadIndex in the appended quads
|
||||
// to account for the new starting index in fSrcQuads
|
||||
int srcQuadOffset = fSrcQuads.count();
|
||||
int oldQuadCount = fQuads.count();
|
||||
|
||||
fSrcQuads.concat(that->fSrcQuads);
|
||||
// Concatenate quad lists together
|
||||
fQuads.concat(that->fQuads);
|
||||
fProxies[0].fQuadCnt += that->fQuads.count();
|
||||
|
||||
if (that->fSrcQuads.count() > 0) {
|
||||
// Some of the concatenated quads pointed to fSrcQuads, so adjust the indices for the
|
||||
// newly appended quads
|
||||
for (int i = oldQuadCount; i < fQuads.count(); ++i) {
|
||||
if (fQuads.metadata(i).fSrcQuadIndex >= 0) {
|
||||
fQuads.metadata(i).fSrcQuadIndex += srcQuadOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm all tracked state makes sense when in debug builds
|
||||
#ifdef SK_DEBUG
|
||||
SkASSERT(fSrcQuads.count() <= fQuads.count());
|
||||
for (int i = 0; i < fQuads.count(); ++i) {
|
||||
int srcIndex = fQuads.metadata(i).fSrcQuadIndex;
|
||||
if (srcIndex >= 0) {
|
||||
// Make sure it points to a valid index, in the right region of the list
|
||||
SkASSERT(srcIndex < fSrcQuads.count());
|
||||
SkASSERT((i < oldQuadCount && srcIndex < srcQuadOffset) ||
|
||||
(i >= oldQuadCount && srcIndex >= srcQuadOffset));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return CombineResult::kMerged;
|
||||
}
|
||||
|
||||
GrAAType aaType() const { return static_cast<GrAAType>(fAAType); }
|
||||
GrSamplerState::Filter filter() const { return static_cast<GrSamplerState::Filter>(fFilter); }
|
||||
|
||||
struct ColorDomainAndAA {
|
||||
// Special constructor to convert enums into the packed bits, which should not delete
|
||||
// the implicit move constructor (but it does require us to declare an empty ctor for
|
||||
// use with the GrTQuadList).
|
||||
ColorDomainAndAA(const SkPMColor4f& color, const SkRect& srcRect, int srcQuadIndex,
|
||||
Domain hasDomain, GrQuadAAFlags aaFlags)
|
||||
: fColor(color)
|
||||
, fSrcRect(srcRect)
|
||||
, fSrcQuadIndex(srcQuadIndex)
|
||||
, fHasDomain(static_cast<unsigned>(hasDomain))
|
||||
, fAAFlags(static_cast<unsigned>(aaFlags)) {
|
||||
SkASSERT(fHasDomain == static_cast<unsigned>(hasDomain));
|
||||
SkASSERT(fAAFlags == static_cast<unsigned>(aaFlags));
|
||||
}
|
||||
ColorDomainAndAA() = default;
|
||||
|
||||
SkPMColor4f fColor;
|
||||
// Even if fSrcQuadIndex provides source coords, use fSrcRect for domain constraint
|
||||
SkRect fSrcRect;
|
||||
// If >= 0, use to access fSrcQuads instead of fSrcRect for the source coordinates
|
||||
int fSrcQuadIndex;
|
||||
unsigned fHasDomain : 1;
|
||||
unsigned fAAFlags : 4;
|
||||
|
||||
Domain domain() const { return Domain(fHasDomain); }
|
||||
GrQuadAAFlags aaFlags() const { return static_cast<GrQuadAAFlags>(fAAFlags); }
|
||||
};
|
||||
struct Proxy {
|
||||
GrTextureProxy* fProxy;
|
||||
int fQuadCnt;
|
||||
};
|
||||
GrTQuadList<ColorDomainAndAA> fQuads;
|
||||
// The majority of texture ops will not track a complete src quad so this is indexed separately
|
||||
// and may be of different size to fQuads.
|
||||
GrQuadList fSrcQuads;
|
||||
GrQuadBuffer<ColorDomainAndAA> fQuads;
|
||||
sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
|
||||
unsigned fFilter : 2;
|
||||
unsigned fAAType : 2;
|
||||
|
234
tests/GrQuadBufferTest.cpp
Normal file
234
tests/GrQuadBufferTest.cpp
Normal file
@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "tests/Test.h"
|
||||
|
||||
#include "src/gpu/geometry/GrQuadBuffer.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#define ASSERT(cond) REPORTER_ASSERT(r, cond)
|
||||
#define ASSERTF(cond, ...) REPORTER_ASSERT(r, cond, __VA_ARGS__)
|
||||
#define TEST(name) DEF_TEST(GrQuadBuffer##name, r)
|
||||
|
||||
struct TestData {
|
||||
int fItem1;
|
||||
float fItem2;
|
||||
};
|
||||
|
||||
static void assert_quad_eq(skiatest::Reporter* r, const GrQuad& expected, const GrQuad& actual) {
|
||||
ASSERTF(expected.quadType() == actual.quadType(), "Expected type %d, got %d",
|
||||
(int) expected.quadType(), (int) actual.quadType());
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
ASSERTF(expected.x(i) == actual.x(i), "Expected x(%d) = %f, got %d",
|
||||
i, expected.x(i), actual.x(i));
|
||||
ASSERTF(expected.y(i) == actual.y(i), "Expected y(%d) = %f, got %d",
|
||||
i, expected.y(i), actual.y(i));
|
||||
ASSERTF(expected.w(i) == actual.w(i), "Expected w(%d) = %f, got %d",
|
||||
i, expected.w(i), actual.w(i));
|
||||
}
|
||||
}
|
||||
|
||||
static void assert_metadata_eq(skiatest::Reporter* r, const TestData& expected,
|
||||
const TestData& actual) {
|
||||
ASSERTF(expected.fItem1 == actual.fItem1 && expected.fItem2 == actual.fItem2,
|
||||
"Expected { %d, %f } for metadata, got: { %d %f }",
|
||||
expected.fItem1, expected.fItem2, actual.fItem1, actual.fItem2);
|
||||
}
|
||||
|
||||
static std::vector<GrQuad> generate_quads(float seed, int cnt, const GrQuad::Type types[]) {
|
||||
// For convenience use matrix to derive each quad type, rely on different seed values to
|
||||
// differentiate between quads of the same type
|
||||
SkMatrix rotate;
|
||||
rotate.setRotate(45.f);
|
||||
SkMatrix skew;
|
||||
skew.setSkew(0.5f, 0.5f);
|
||||
SkMatrix perspective;
|
||||
perspective.setPerspX(0.01f);
|
||||
perspective.setPerspY(0.001f);
|
||||
|
||||
std::vector<GrQuad> quads;
|
||||
SkRect rect = SkRect::MakeXYWH(seed, 2.f * seed, 2.f * seed, seed);
|
||||
for (int i = 0; i < cnt; ++i) {
|
||||
GrQuad quad;
|
||||
switch(types[i]) {
|
||||
case GrQuad::Type::kAxisAligned:
|
||||
quad = GrQuad(rect);
|
||||
break;
|
||||
case GrQuad::Type::kRectilinear:
|
||||
quad = GrQuad::MakeFromRect(rect, rotate);
|
||||
break;
|
||||
case GrQuad::Type::kGeneral:
|
||||
quad = GrQuad::MakeFromRect(rect, skew);
|
||||
break;
|
||||
default:
|
||||
SkASSERT(types[i] == GrQuad::Type::kPerspective);
|
||||
quad = GrQuad::MakeFromRect(rect, perspective);
|
||||
break;
|
||||
}
|
||||
|
||||
SkASSERT(quad.quadType() == types[i]); // sanity check
|
||||
quads.push_back(quad);
|
||||
}
|
||||
return quads;
|
||||
}
|
||||
|
||||
TEST(Append) {
|
||||
// Generate test data, which includes all quad types out of enum-order and duplicates
|
||||
static const int kQuadCount = 6;
|
||||
static const GrQuad::Type kDeviceTypes[] = {
|
||||
GrQuad::Type::kAxisAligned, GrQuad::Type::kRectilinear, GrQuad::Type::kGeneral,
|
||||
GrQuad::Type::kPerspective, GrQuad::Type::kRectilinear, GrQuad::Type::kAxisAligned
|
||||
};
|
||||
// Odd indexed quads will be ignored and not stored in the buffer
|
||||
static const GrQuad::Type kLocalTypes[] = {
|
||||
GrQuad::Type::kGeneral, GrQuad::Type::kGeneral, GrQuad::Type::kRectilinear,
|
||||
GrQuad::Type::kRectilinear, GrQuad::Type::kAxisAligned, GrQuad::Type::kAxisAligned
|
||||
};
|
||||
static_assert(SK_ARRAY_COUNT(kDeviceTypes) == kQuadCount, "device quad count");
|
||||
static_assert(SK_ARRAY_COUNT(kLocalTypes) == kQuadCount, "local quad count");
|
||||
|
||||
std::vector<GrQuad> expectedDeviceQuads = generate_quads(1.f, kQuadCount, kDeviceTypes);
|
||||
std::vector<GrQuad> expectedLocalQuads = generate_quads(2.f, kQuadCount, kLocalTypes);
|
||||
|
||||
// Fill in the buffer with the device quads, and a local quad if the index is even
|
||||
GrQuadBuffer<TestData> buffer;
|
||||
for (int i = 0; i < kQuadCount; ++i) {
|
||||
buffer.append(expectedDeviceQuads[i], // device quad
|
||||
{ 2 * i, 3.f * i }, // metadata
|
||||
i % 2 == 0 ? &expectedLocalQuads[i] : nullptr); // optional local quad
|
||||
}
|
||||
|
||||
// Confirm the state of the buffer
|
||||
ASSERT(kQuadCount == buffer.count());
|
||||
ASSERT(GrQuad::Type::kPerspective == buffer.deviceQuadType());
|
||||
ASSERT(GrQuad::Type::kGeneral == buffer.localQuadType());
|
||||
|
||||
int i = 0;
|
||||
auto iter = buffer.iterator();
|
||||
while(iter.next()) {
|
||||
// Each entry always has the device quad
|
||||
assert_quad_eq(r, expectedDeviceQuads[i], iter.deviceQuad());
|
||||
assert_metadata_eq(r, {2 * i, 3.f * i}, iter.metadata());
|
||||
|
||||
if (i % 2 == 0) {
|
||||
// Confirm local quads included on even entries
|
||||
ASSERT(iter.isLocalValid());
|
||||
assert_quad_eq(r, expectedLocalQuads[i], iter.localQuad());
|
||||
} else {
|
||||
// Should not have locals
|
||||
ASSERT(!iter.isLocalValid());
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
ASSERTF(i == kQuadCount, "Expected %d iterations, got: %d", kQuadCount, i);
|
||||
}
|
||||
|
||||
TEST(Concat) {
|
||||
static const int kQuadCount = 2;
|
||||
static const GrQuad::Type kTypesA[] = { GrQuad::Type::kAxisAligned, GrQuad::Type::kRectilinear };
|
||||
static const GrQuad::Type kTypesB[] = { GrQuad::Type::kGeneral, GrQuad::Type::kPerspective };
|
||||
static_assert(SK_ARRAY_COUNT(kTypesA) == kQuadCount, "quadsA count");
|
||||
static_assert(SK_ARRAY_COUNT(kTypesB) == kQuadCount, "quadsB count");
|
||||
|
||||
std::vector<GrQuad> quadsA = generate_quads(1.f, kQuadCount, kTypesA);
|
||||
std::vector<GrQuad> quadsB = generate_quads(2.f, kQuadCount, kTypesB);
|
||||
// Make two buffers, the first uses 'quadsA' for device quads and 'quadsB' for local quads
|
||||
// on even indices. The second uses 'quadsB' for device quads and 'quadsA' for local quads
|
||||
// on odd indices.
|
||||
GrQuadBuffer<TestData> buffer1;
|
||||
GrQuadBuffer<TestData> buffer2;
|
||||
for (int i = 0; i < kQuadCount; ++i) {
|
||||
buffer1.append(quadsA[i], {i, 2.f * i}, i % 2 == 0 ? &quadsB[i] : nullptr);
|
||||
buffer2.append(quadsB[i], {2 * i, 0.5f * i}, i % 2 == 0 ? nullptr : &quadsA[i]);
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
ASSERT(kQuadCount == buffer1.count());
|
||||
ASSERT(kQuadCount == buffer2.count());
|
||||
|
||||
// Perform the concatenation and then confirm the new state of buffer1
|
||||
buffer1.concat(buffer2);
|
||||
|
||||
ASSERT(2 * kQuadCount == buffer1.count());
|
||||
int i = 0;
|
||||
auto iter = buffer1.iterator();
|
||||
while(iter.next()) {
|
||||
if (i < kQuadCount) {
|
||||
// First half should match original buffer1
|
||||
assert_quad_eq(r, quadsA[i], iter.deviceQuad());
|
||||
assert_metadata_eq(r, {i, 2.f * i}, iter.metadata());
|
||||
if (i % 2 == 0) {
|
||||
ASSERT(iter.isLocalValid());
|
||||
assert_quad_eq(r, quadsB[i], iter.localQuad());
|
||||
} else {
|
||||
ASSERT(!iter.isLocalValid());
|
||||
}
|
||||
|
||||
} else {
|
||||
// Second half should match buffer2
|
||||
int j = i - kQuadCount;
|
||||
assert_quad_eq(r, quadsB[j], iter.deviceQuad());
|
||||
assert_metadata_eq(r, {2 * j, 0.5f * j}, iter.metadata());
|
||||
if (j % 2 == 0) {
|
||||
ASSERT(!iter.isLocalValid());
|
||||
} else {
|
||||
ASSERT(iter.isLocalValid());
|
||||
assert_quad_eq(r, quadsA[j], iter.localQuad());
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
ASSERTF(i == 2 * kQuadCount, "Expected %d iterations, got: %d",2 * kQuadCount, i);
|
||||
}
|
||||
|
||||
TEST(Metadata) {
|
||||
static const int kQuadCount = 3;
|
||||
|
||||
// This test doesn't really care about the quad coordinates (except that they aren't modified
|
||||
// when mutating the metadata)
|
||||
GrQuad quad(SkRect::MakeLTRB(1.f, 2.f, 3.f, 4.f));
|
||||
|
||||
GrQuadBuffer<TestData> buffer;
|
||||
for (int i = 0; i < kQuadCount; ++i) {
|
||||
buffer.append(quad, {i, 2.f * i}, i % 2 == 0 ? &quad : nullptr);
|
||||
}
|
||||
|
||||
// Iterate once using the metadata iterator, confirm the test data and rewrite
|
||||
int i = 0;
|
||||
auto meta = buffer.metadata();
|
||||
while(meta.next()) {
|
||||
// Confirm initial state
|
||||
assert_metadata_eq(r, {i, 2.f * i}, *meta);
|
||||
// Rewrite
|
||||
*meta = {2 * i, 0.5f * i};
|
||||
i++;
|
||||
}
|
||||
ASSERTF(i == kQuadCount, "Expected %d iterations, got: %d", kQuadCount, i);
|
||||
|
||||
// Now that all metadata has been touched, read with regular iterator and confirm updated state
|
||||
// and that no quad coordinates have been changed.
|
||||
i = 0;
|
||||
auto iter = buffer.iterator();
|
||||
while(iter.next()) {
|
||||
// New metadata
|
||||
assert_metadata_eq(r, {2 * i, 0.5f * i}, iter.metadata());
|
||||
|
||||
// Quad coordinates are unchanged
|
||||
assert_quad_eq(r, quad, iter.deviceQuad());
|
||||
if (i % 2 == 0) {
|
||||
ASSERT(iter.isLocalValid());
|
||||
assert_quad_eq(r, quad, iter.localQuad());
|
||||
} else {
|
||||
ASSERT(!iter.isLocalValid());
|
||||
}
|
||||
i++;
|
||||
}
|
||||
ASSERTF(i == kQuadCount, "Expected %d iterations, got: %d", kQuadCount, i);
|
||||
}
|
@ -1,268 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "tests/Test.h"
|
||||
|
||||
#include "src/gpu/geometry/GrQuadList.h"
|
||||
|
||||
#define ASSERT(cond) REPORTER_ASSERT(r, cond)
|
||||
#define ASSERTF(cond, ...) REPORTER_ASSERT(r, cond, __VA_ARGS__)
|
||||
#define TEST(name) DEF_TEST(GrQuadList##name, r)
|
||||
|
||||
struct TestData {
|
||||
int fItem1;
|
||||
float fItem2;
|
||||
};
|
||||
|
||||
// Simple factories to make placeholder quads used in the tests. The 2D quads
|
||||
// will have the kRect quad type.
|
||||
static GrQuad make_2d_quad() {
|
||||
return GrQuad(SkRect::MakeLTRB(1.f, 2.f, 3.f, 4.f));
|
||||
}
|
||||
static bool is_2d_quad(const GrQuad& quad) {
|
||||
return quad.x(0) == 1.f && quad.x(1) == 1.f && quad.x(2) == 3.f && quad.x(3) == 3.f &&
|
||||
quad.y(0) == 2.f && quad.y(1) == 4.f && quad.y(2) == 2.f && quad.y(3) == 4.f &&
|
||||
quad.w(0) == 1.f && quad.w(1) == 1.f && quad.w(2) == 1.f && quad.w(3) == 1.f;
|
||||
}
|
||||
|
||||
static GrQuad make_3d_quad() {
|
||||
// This perspective matrix leaves x and y unmodified, and sets w to the persp2 value
|
||||
SkMatrix p = SkMatrix::I();
|
||||
p[SkMatrix::kMPersp2] = 13.f;
|
||||
SkASSERT(p.hasPerspective()); // Sanity check
|
||||
return GrQuad::MakeFromRect(SkRect::MakeLTRB(9.f, 10.f, 11.f, 12.f), p);
|
||||
}
|
||||
static bool is_3d_quad(const GrQuad& quad) {
|
||||
return quad.x(0) == 9.f && quad.x(1) == 9.f && quad.x(2) == 11.f && quad.x(3) == 11.f &&
|
||||
quad.y(0) == 10.f && quad.y(1) == 12.f && quad.y(2) == 10.f && quad.y(3) == 12.f &&
|
||||
quad.w(0) == 13.f && quad.w(1) == 13.f && quad.w(2) == 13.f && quad.w(3) == 13.f;
|
||||
}
|
||||
|
||||
TEST(Add2D) {
|
||||
GrQuadList list2D;
|
||||
// Add a plain quad and then a 3D persp quad, then read back and make sure
|
||||
// the coordinates make sense (including that the type was lifted to perspective).
|
||||
list2D.push_back(make_2d_quad());
|
||||
|
||||
// Check 2D state of the list
|
||||
ASSERTF(list2D.count() == 1, "Unexpected count: %d", list2D.count());
|
||||
ASSERTF(list2D.quadType() == GrQuad::Type::kAxisAligned, "Unexpected quad type: %d",
|
||||
(uint32_t) list2D.quadType());
|
||||
ASSERTF(is_2d_quad(list2D[0]), "Incorrect quad at i=0");
|
||||
|
||||
// Force the 2D quads to be updated to store ws by adding a perspective quad
|
||||
list2D.push_back(make_3d_quad());
|
||||
ASSERTF(list2D.quadType() == GrQuad::Type::kPerspective,
|
||||
"Expected 2D list to be upgraded to perspective");
|
||||
|
||||
// Re-check full state of list after type upgrade
|
||||
ASSERTF(list2D.count() == 2, "Unexpected count: %d", list2D.count());
|
||||
ASSERTF(is_2d_quad(list2D[0]), "Incorrect quad at i=0 after upgrade");
|
||||
ASSERTF(is_3d_quad(list2D[1]), "Incorrect quad at i=1");
|
||||
}
|
||||
|
||||
TEST(Add3D) {
|
||||
// Now make a list that starts with a 3D persp quad, then has conventional quads added to it
|
||||
// and make sure its state is correct
|
||||
GrQuadList list3D;
|
||||
list3D.push_back(make_3d_quad());
|
||||
list3D.push_back(make_2d_quad());
|
||||
|
||||
ASSERTF(list3D.count() == 2, "Unexpected count: %d", list3D.count());
|
||||
ASSERTF(is_3d_quad(list3D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(is_2d_quad(list3D[1]), "Incorrect quad at i=2");
|
||||
}
|
||||
|
||||
TEST(AddWithMetadata2D) {
|
||||
// As above, but also make sure that the metadata is saved and read properly
|
||||
GrTQuadList<TestData> list2D;
|
||||
// Add two plain quads, and then a 3D persp quad, then read back and make sure
|
||||
// the coordinates make sense (including that the type was lifted to perspective).
|
||||
list2D.push_back(make_2d_quad(), {1, 1.f});
|
||||
list2D.push_back(make_2d_quad(), {2, 2.f});
|
||||
|
||||
// Check 2D state of the list
|
||||
ASSERTF(list2D.count() == 2, "Unexpected count: %d", list2D.count());
|
||||
ASSERTF(list2D.quadType() == GrQuad::Type::kAxisAligned, "Unexpected quad type: %d",
|
||||
(uint32_t) list2D.quadType());
|
||||
ASSERTF(is_2d_quad(list2D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(list2D.metadata(0).fItem1 == 1 && list2D.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_2d_quad(list2D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(list2D.metadata(1).fItem1 == 2 && list2D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
|
||||
// Force the 2D quads to be updated to store ws by adding a perspective quad
|
||||
list2D.push_back(make_3d_quad(), {3, 3.f});
|
||||
ASSERTF(list2D.quadType() == GrQuad::Type::kPerspective,
|
||||
"Expected 2D list to be upgraded to perspective");
|
||||
|
||||
// Re-check full state of list after type upgrade
|
||||
ASSERTF(list2D.count() == 3, "Unexpected count: %d", list2D.count());
|
||||
ASSERTF(is_2d_quad(list2D[0]), "Incorrect quad at i=0 after upgrade");
|
||||
ASSERTF(list2D.metadata(0).fItem1 == 1 && list2D.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_2d_quad(list2D[1]), "Incorrect quad at i=1 after upgrade");
|
||||
ASSERTF(list2D.metadata(1).fItem1 == 2 && list2D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
ASSERTF(is_3d_quad(list2D[2]), "Incorrect quad at i=2");
|
||||
ASSERTF(list2D.metadata(2).fItem1 == 3 && list2D.metadata(2).fItem2 == 3.f,
|
||||
"Incorrect metadata at i=2");
|
||||
}
|
||||
|
||||
TEST(AddWithMetadata3D) {
|
||||
// Now make a list that starts with a 3D persp quad, then has conventional quads added to it
|
||||
// and make sure its state is correct
|
||||
GrTQuadList<TestData> list3D;
|
||||
list3D.push_back(make_3d_quad(), {3, 3.f});
|
||||
list3D.push_back(make_2d_quad(), {2, 2.f});
|
||||
list3D.push_back(make_2d_quad(), {1, 1.f});
|
||||
|
||||
ASSERTF(list3D.count() == 3, "Unexpected count: %d", list3D.count());
|
||||
ASSERTF(is_3d_quad(list3D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(list3D.metadata(0).fItem1 == 3 && list3D.metadata(0).fItem2 == 3.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_2d_quad(list3D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(list3D.metadata(1).fItem1 == 2 && list3D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
ASSERTF(is_2d_quad(list3D[2]), "Incorrect quad at i=2");
|
||||
ASSERTF(list3D.metadata(2).fItem1 == 1 && list3D.metadata(2).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=2");
|
||||
}
|
||||
|
||||
TEST(Concat2DWith2D) {
|
||||
GrQuadList a2D;
|
||||
a2D.push_back(make_2d_quad());
|
||||
GrQuadList b2D;
|
||||
b2D.push_back(make_2d_quad());
|
||||
|
||||
a2D.concat(b2D);
|
||||
|
||||
ASSERTF(a2D.count() == 2, "Unexpected count: %d", a2D.count());
|
||||
ASSERTF(is_2d_quad(a2D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(is_2d_quad(a2D[1]), "Incorrect quad at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat2DWith3D) {
|
||||
GrQuadList a2D;
|
||||
a2D.push_back(make_2d_quad());
|
||||
GrQuadList b3D;
|
||||
b3D.push_back(make_3d_quad());
|
||||
|
||||
a2D.concat(b3D);
|
||||
|
||||
ASSERTF(a2D.count() == 2, "Unexpected count: %d", a2D.count());
|
||||
ASSERTF(is_2d_quad(a2D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(is_3d_quad(a2D[1]), "Incorrect quad at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat3DWith2D) {
|
||||
GrQuadList a3D;
|
||||
a3D.push_back(make_3d_quad());
|
||||
GrQuadList b2D;
|
||||
b2D.push_back(make_2d_quad());
|
||||
|
||||
a3D.concat(b2D);
|
||||
|
||||
ASSERTF(a3D.count() == 2, "Unexpected count: %d", a3D.count());
|
||||
ASSERTF(is_3d_quad(a3D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(is_2d_quad(a3D[1]), "Incorrect quad at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat3DWith3D) {
|
||||
GrQuadList a3D;
|
||||
a3D.push_back(make_3d_quad());
|
||||
GrQuadList b3D;
|
||||
b3D.push_back(make_3d_quad());
|
||||
|
||||
a3D.concat(b3D);
|
||||
|
||||
ASSERTF(a3D.count() == 2, "Unexpected count: %d", a3D.count());
|
||||
ASSERTF(is_3d_quad(a3D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(is_3d_quad(a3D[1]), "Incorrect quad at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat2DWith2DMetadata) {
|
||||
GrTQuadList<TestData> a2D;
|
||||
a2D.push_back(make_2d_quad(), {1, 1.f});
|
||||
GrTQuadList<TestData> b2D;
|
||||
b2D.push_back(make_2d_quad(), {2, 2.f});
|
||||
|
||||
a2D.concat(b2D);
|
||||
|
||||
ASSERTF(a2D.count() == 2, "Unexpected count: %d", a2D.count());
|
||||
ASSERTF(is_2d_quad(a2D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(a2D.metadata(0).fItem1 == 1 && a2D.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_2d_quad(a2D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(a2D.metadata(1).fItem1 == 2 && a2D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat2DWith3DMetadata) {
|
||||
GrTQuadList<TestData> a2D;
|
||||
a2D.push_back(make_2d_quad(), {1, 1.f});
|
||||
GrTQuadList<TestData> b3D;
|
||||
b3D.push_back(make_3d_quad(), {2, 2.f});
|
||||
|
||||
a2D.concat(b3D);
|
||||
|
||||
ASSERTF(a2D.count() == 2, "Unexpected count: %d", a2D.count());
|
||||
ASSERTF(is_2d_quad(a2D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(a2D.metadata(0).fItem1 == 1 && a2D.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_3d_quad(a2D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(a2D.metadata(1).fItem1 == 2 && a2D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat3DWith2DMetadata) {
|
||||
GrTQuadList<TestData> a3D;
|
||||
a3D.push_back(make_3d_quad(), {1, 1.f});
|
||||
GrTQuadList<TestData> b2D;
|
||||
b2D.push_back(make_2d_quad(), {2, 2.f});
|
||||
|
||||
a3D.concat(b2D);
|
||||
|
||||
ASSERTF(a3D.count() == 2, "Unexpected count: %d", a3D.count());
|
||||
ASSERTF(is_3d_quad(a3D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(a3D.metadata(0).fItem1 == 1 && a3D.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_2d_quad(a3D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(a3D.metadata(1).fItem1 == 2 && a3D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
}
|
||||
|
||||
TEST(Concat3DWith3DMetadata) {
|
||||
GrTQuadList<TestData> a3D;
|
||||
a3D.push_back(make_3d_quad(), {1, 1.f});
|
||||
GrTQuadList<TestData> b3D;
|
||||
b3D.push_back(make_3d_quad(), {2, 2.f});
|
||||
|
||||
a3D.concat(b3D);
|
||||
|
||||
ASSERTF(a3D.count() == 2, "Unexpected count: %d", a3D.count());
|
||||
ASSERTF(is_3d_quad(a3D[0]), "Incorrect quad at i=0");
|
||||
ASSERTF(a3D.metadata(0).fItem1 == 1 && a3D.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0");
|
||||
ASSERTF(is_3d_quad(a3D[1]), "Incorrect quad at i=1");
|
||||
ASSERTF(a3D.metadata(1).fItem1 == 2 && a3D.metadata(1).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=1");
|
||||
}
|
||||
|
||||
TEST(WriteMetadata) {
|
||||
GrTQuadList<TestData> list;
|
||||
list.push_back(make_2d_quad(), {1, 1.f});
|
||||
ASSERTF(list.metadata(0).fItem1 == 1 && list.metadata(0).fItem2 == 1.f,
|
||||
"Incorrect metadata at i=0"); // Sanity check
|
||||
|
||||
// Rewrite metadata within the list and read back
|
||||
list.metadata(0).fItem1 = 2;
|
||||
list.metadata(0).fItem2 = 2.f;
|
||||
ASSERTF(list.metadata(0).fItem1 == 2 && list.metadata(0).fItem2 == 2.f,
|
||||
"Incorrect metadata at i=0 after edit");
|
||||
}
|
Loading…
Reference in New Issue
Block a user