From 425eb45435a2ba118fe19f982964b5de898e22ea Mon Sep 17 00:00:00 2001 From: Michael Ludwig Date: Thu, 27 Jun 2019 10:13:27 -0400 Subject: [PATCH] Reland "Replace GrQuadList with variable-length quad buffer" This reverts commit 19628ec144a6e480e56bbfff38ae07a3db1588d1. 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 f2816044292f0d405c3060572d65fb35cef7cc3a. > > 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 > > Commit-Queue: Michael Ludwig > > 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 > Commit-Queue: Brian Salomon 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 Commit-Queue: Michael Ludwig --- gn/gpu.gni | 2 +- gn/tests.gni | 2 +- src/gpu/geometry/GrQuad.h | 4 +- src/gpu/geometry/GrQuadBuffer.h | 375 ++++++++++++++++++++++++++++++++ src/gpu/geometry/GrQuadList.h | 172 --------------- src/gpu/ops/GrFillRectOp.cpp | 112 ++++------ src/gpu/ops/GrTextureOp.cpp | 176 ++++++--------- tests/GrQuadBufferTest.cpp | 234 ++++++++++++++++++++ tests/GrQuadListTest.cpp | 268 ----------------------- 9 files changed, 727 insertions(+), 618 deletions(-) create mode 100644 src/gpu/geometry/GrQuadBuffer.h delete mode 100644 src/gpu/geometry/GrQuadList.h create mode 100644 tests/GrQuadBufferTest.cpp delete mode 100644 tests/GrQuadListTest.cpp diff --git a/gn/gpu.gni b/gn/gpu.gni index 17177ed4c8..185c2f9f37 100644 --- a/gn/gpu.gni +++ b/gn/gpu.gni @@ -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", diff --git a/gn/tests.gni b/gn/tests.gni index 43523d5ee5..bba8287258 100644 --- a/gn/tests.gni +++ b/gn/tests.gni @@ -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", diff --git a/src/gpu/geometry/GrQuad.h b/src/gpu/geometry/GrQuad.h index ea3cdec200..361c9a3e11 100644 --- a/src/gpu/geometry/GrQuad.h +++ b/src/gpu/geometry/GrQuad.h @@ -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; } diff --git a/src/gpu/geometry/GrQuadBuffer.h b/src/gpu/geometry/GrQuadBuffer.h new file mode 100644 index 0000000000..a2e2f4c87e --- /dev/null +++ b/src/gpu/geometry/GrQuadBuffer.h @@ -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 +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& 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* 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* 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* 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* 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 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(header->fLocalType); + return this->entrySize(static_cast(header->fDeviceType), &localType); + } else { + return this->entrySize(static_cast(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(static_cast(entry)); + } + inline const Header* header(const char* entry) const { + return static_cast(static_cast(entry)); + } + + inline T* metadata(char* entry) { + return static_cast(static_cast(entry + sizeof(Header))); + } + inline const T* metadata(const char* entry) const { + return static_cast(static_cast(entry + sizeof(Header))); + } + + inline float* coords(char* entry) { + return static_cast(static_cast(entry + kMetaSize)); + } + inline const float* coords(const char* entry) const { + return static_cast(static_cast(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 +float* GrQuadBuffer::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 +const float* GrQuadBuffer::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 +void GrQuadBuffer::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(deviceQuad.quadType()); + h->fHasLocals = static_cast(localQuad != nullptr); + h->fLocalType = static_cast(localQuad ? localQuad->quadType() + : GrQuad::Type::kAxisAligned); + SkDEBUGCODE(h->fSentinel = static_cast(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 +void GrQuadBuffer::concat(const GrQuadBuffer& 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 +void GrQuadBuffer::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 +bool GrQuadBuffer::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(h->fDeviceType), coords, &fDeviceQuad); + if (h->fHasLocals) { + coords = fBuffer->unpackQuad(static_cast(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(static_cast(coords)); + SkASSERT((fNextEntry - fCurrentEntry) == fBuffer->entrySize(h)); + return true; +} + +template +bool GrQuadBuffer::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(); +} diff --git a/src/gpu/geometry/GrQuadList.h b/src/gpu/geometry/GrQuadList.h deleted file mode 100644 index b1cbf6ae22..0000000000 --- a/src/gpu/geometry/GrQuadList.h +++ /dev/null @@ -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 -struct QuadData { - float fX[4]; - float fY[4]; - T fMetadata; -}; - -template<> -struct QuadData { - 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 -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& 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& 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& pushBackImpl(const GrQuad& quad) { - this->upgradeType(quad.quadType()); - QuadData& 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& item(int i) const { - return fXYs[i]; - } - - QuadData& 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, true> fXYs; - // The w channel is kept separate so that it can remain empty when only dealing with 2D quads. - SkTArray fWs; - - GrQuad::Type fType; -}; - -// This list only stores the quad data itself. -class GrQuadList : public GrQuadListBase { -public: - GrQuadList() : INHERITED() {} - - void concat(const GrQuadList& that) { - this->concatImpl(that); - } - - void push_back(const GrQuad& quad) { - this->pushBackImpl(quad); - } - -private: - typedef GrQuadListBase INHERITED; -}; - -// This variant of the list allows simple metadata to be stored per quad as well, such as color -// or texture domain. -template -class GrTQuadList : public GrQuadListBase { -public: - GrTQuadList() : INHERITED() {} - - void concat(const GrTQuadList& that) { - this->concatImpl(that); - } - - // Adding to the list requires metadata - void push_back(const GrQuad& quad, T&& metadata) { - QuadData& 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 INHERITED; -}; - -#endif diff --git a/src/gpu/ops/GrFillRectOp.cpp b/src/gpu/ops/GrFillRectOp.cpp index f5f7c7c75e..fe3cc6567d 100644 --- a/src/gpu/ops/GrFillRectOp.cpp +++ b/src/gpu/ops/GrFillRectOp.cpp @@ -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 fDeviceQuads; - // No metadata attached to the local quads; this list is empty when local coords are not needed. - GrQuadList fLocalQuads; + GrQuadBuffer fQuads; ColorType fColorType; diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp index d77e88b7bb..2aa2c06a74 100644 --- a/src/gpu/ops/GrTextureOp.cpp +++ b/src/gpu/ops/GrTextureOp.cpp @@ -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(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(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(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(hasDomain)) + , fAAFlags(static_cast(aaFlags)) { + SkASSERT(fHasDomain == static_cast(hasDomain)); + SkASSERT(fAAFlags == static_cast(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(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 textureColorSpaceXform) : INHERITED(ClassID()) + , fQuads(1, srcQuad != nullptr) , fTextureColorSpaceXform(std::move(textureColorSpaceXform)) , fFilter(static_cast(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 textureColorSpaceXform) : INHERITED(ClassID()) + , fQuads(cnt, false) , fTextureColorSpaceXform(std::move(textureColorSpaceXform)) , fFilter(static_cast(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(overallAAType); if (!mustFilter) { @@ -367,8 +394,8 @@ private: fDomain = static_cast(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::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(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(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(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(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(fAAType); } GrSamplerState::Filter filter() const { return static_cast(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(hasDomain)) - , fAAFlags(static_cast(aaFlags)) { - SkASSERT(fHasDomain == static_cast(hasDomain)); - SkASSERT(fAAFlags == static_cast(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(fAAFlags); } - }; - struct Proxy { - GrTextureProxy* fProxy; - int fQuadCnt; - }; - GrTQuadList 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 fQuads; sk_sp fTextureColorSpaceXform; unsigned fFilter : 2; unsigned fAAType : 2; diff --git a/tests/GrQuadBufferTest.cpp b/tests/GrQuadBufferTest.cpp new file mode 100644 index 0000000000..928438f529 --- /dev/null +++ b/tests/GrQuadBufferTest.cpp @@ -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 + +#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 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 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 expectedDeviceQuads = generate_quads(1.f, kQuadCount, kDeviceTypes); + std::vector 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 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 quadsA = generate_quads(1.f, kQuadCount, kTypesA); + std::vector 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 buffer1; + GrQuadBuffer 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 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); +} diff --git a/tests/GrQuadListTest.cpp b/tests/GrQuadListTest.cpp deleted file mode 100644 index 7ec9ca4249..0000000000 --- a/tests/GrQuadListTest.cpp +++ /dev/null @@ -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 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 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 a2D; - a2D.push_back(make_2d_quad(), {1, 1.f}); - GrTQuadList 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 a2D; - a2D.push_back(make_2d_quad(), {1, 1.f}); - GrTQuadList 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 a3D; - a3D.push_back(make_3d_quad(), {1, 1.f}); - GrTQuadList 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 a3D; - a3D.push_back(make_3d_quad(), {1, 1.f}); - GrTQuadList 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 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"); -}