Reland "Replace GrQuadList with variable-length quad buffer"

This reverts commit 19628ec144.

Reason for revert: fixed struct size on 32-bit iOS

Original change's description:
> Revert "Replace GrQuadList with variable-length quad buffer"
> 
> This reverts commit f281604429.
> 
> 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:
Michael Ludwig 2019-06-27 10:13:27 -04:00 committed by Skia Commit-Bot
parent 1958fbbcff
commit 425eb45435
9 changed files with 727 additions and 618 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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; }

View 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();
}

View File

@ -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

View File

@ -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;

View File

@ -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
View 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);
}

View File

@ -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");
}