/* * Copyright 2021 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/gpu/graphite/geom/Shape.h" #include "include/core/SkPathBuilder.h" #include "include/core/SkRect.h" #include "include/core/SkScalar.h" #include "include/private/base/SkAlign.h" #include "include/private/base/SkDebug.h" #include "include/private/base/SkMalloc.h" #include "src/core/SkPathPriv.h" #include "src/core/SkRRectPriv.h" #include namespace { // Keys for paths may be extracted from the path data for small paths, to maximize matches // even when the genIDs may differ. The value is based on emperical experience, to trade off // matches vs. key size. constexpr int kMaxKeyFromDataVerbCnt = 10; } namespace skgpu::graphite { Shape& Shape::operator=(const Shape& shape) { switch (shape.type()) { case Type::kEmpty: this->reset(); break; case Type::kLine: this->setLine(shape.p0(), shape.p1()); break; case Type::kRect: this->setRect(shape.rect()); break; case Type::kRRect: this->setRRect(shape.rrect()); break; case Type::kArc: this->setArc(shape.arc()); break; case Type::kPath: this->setPath(shape.path()); break; } fInverted = shape.fInverted; return *this; } bool Shape::conservativeContains(const Rect& rect) const { switch (fType) { case Type::kEmpty: return false; case Type::kLine: return false; case Type::kRect: return fRect.contains(rect); case Type::kRRect: return fRRect.contains(rect.asSkRect()); case Type::kPath: // We need to ensure the path is non-inverted. if (this->inverted()) { SkPath nonInverted(fPath); nonInverted.toggleInverseFillType(); return nonInverted.conservativelyContainsRect(rect.asSkRect()); } else { return fPath.conservativelyContainsRect(rect.asSkRect()); } case Type::kArc: if (fArc.fType == SkArc::Type::kWedge) { SkPath arc = this->asPath(); if (this->inverted()) { arc.toggleInverseFillType(); } return arc.conservativelyContainsRect(rect.asSkRect()); } else { return false; } } SkUNREACHABLE; } bool Shape::conservativeContains(skvx::float2 point) const { switch (fType) { case Type::kEmpty: return false; case Type::kLine: return false; case Type::kRect: return fRect.contains(Rect::Point(point)); case Type::kRRect: return SkRRectPriv::ContainsPoint(fRRect, {point.x(), point.y()}); case Type::kPath: // We need to ensure the path is non-inverted. if (this->inverted()) { SkPath nonInverted(fPath); nonInverted.toggleInverseFillType(); return nonInverted.contains(point.x(), point.y()); } else { return fPath.contains(point.x(), point.y()); } case Type::kArc: return false; } SkUNREACHABLE; } bool Shape::convex(bool simpleFill) const { if (this->isPath()) { // SkPath.isConvex() really means "is this path convex were it to be closed". return (simpleFill || fPath.isLastContourClosed()) && fPath.isConvex(); } else if (this->isArc()) { return SkPathPriv::DrawArcIsConvex(fArc.sweepAngle(), fArc.fType, simpleFill); } else { // Every other shape type is convex by construction. return true; } } Rect Shape::bounds() const { switch (fType) { case Type::kEmpty: return Rect(0, 0, 0, 0); case Type::kLine: return fRect.makeSorted(); // sorting corners computes bbox of segment case Type::kRect: return fRect; // assuming it's sorted case Type::kRRect: return fRRect.getBounds(); case Type::kArc: return fArc.oval(); case Type::kPath: return fPath.getBounds(); } SkUNREACHABLE; } SkPath Shape::asPath() const { if (fType == Type::kPath) { return fPath; } if (fType == Type::kArc) { SkPath out; // Filled ovals are already culled out so we assume no simple fills SkPathPriv::CreateDrawArcPath(&out, fArc, /*isFillNoPathEffect=*/false); // CreateDrawArcPath resets the output path and configures its fill // type, so we just have to ensure invertedness is correct. if (fInverted) { out.toggleInverseFillType(); } return out; } SkPathBuilder builder(this->fillType()); switch (fType) { case Type::kEmpty: /* do nothing */ break; case Type::kLine: builder.moveTo(fRect.left(), fRect.top()) .lineTo(fRect.right(), fRect.bot()); break; case Type::kRect: builder.addRect(fRect.asSkRect()); break; case Type::kRRect: builder.addRRect(fRRect); break; case Type::kPath: case Type::kArc: SkUNREACHABLE; } return builder.detach(); } namespace { int path_key_from_data_size(const SkPath& path) { const int verbCnt = path.countVerbs(); if (verbCnt > kMaxKeyFromDataVerbCnt) { return -1; } const int pointCnt = path.countPoints(); const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path); static_assert(sizeof(SkPoint) == 2 * sizeof(uint32_t)); static_assert(sizeof(SkScalar) == sizeof(uint32_t)); // 1 is for the verb count. Each verb is a byte but we'll pad the verb data out to // a uint32_t length. return 1 + (SkAlign4(verbCnt) >> 2) + 2 * pointCnt + conicWeightCnt; } // Writes the path data key into the passed pointer. void write_path_key_from_data(const SkPath& path, uint32_t* origKey) { uint32_t* key = origKey; // The check below should take care of negative values casted positive. const int verbCnt = path.countVerbs(); const int pointCnt = path.countPoints(); const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path); SkASSERT(verbCnt <= kMaxKeyFromDataVerbCnt); SkASSERT(pointCnt && verbCnt); *key++ = verbCnt; memcpy(key, SkPathPriv::VerbData(path), verbCnt * sizeof(uint8_t)); int verbKeySize = SkAlign4(verbCnt); // pad out to uint32_t alignment using value that will stand out when debugging. uint8_t* pad = reinterpret_cast(key)+ verbCnt; memset(pad, 0xDE, verbKeySize - verbCnt); key += verbKeySize >> 2; memcpy(key, SkPathPriv::PointData(path), sizeof(SkPoint) * pointCnt); static_assert(sizeof(SkPoint) == 2 * sizeof(uint32_t)); key += 2 * pointCnt; sk_careful_memcpy(key, SkPathPriv::ConicWeightData(path), sizeof(SkScalar) * conicWeightCnt); static_assert(sizeof(SkScalar) == sizeof(uint32_t)); SkDEBUGCODE(key += conicWeightCnt); SkASSERT(key - origKey == path_key_from_data_size(path)); } } // anonymous namespace int Shape::keySize() const { int count = 1; // Every key has the state flags from the Shape switch(this->type()) { case Type::kLine: static_assert(0 == sizeof(skvx::float4) % sizeof(uint32_t)); count += sizeof(skvx::float4) / sizeof(uint32_t); break; case Type::kRect: static_assert(0 == sizeof(Rect) % sizeof(uint32_t)); count += sizeof(Rect) / sizeof(uint32_t); break; case Type::kRRect: static_assert(0 == SkRRect::kSizeInMemory % sizeof(uint32_t)); count += SkRRect::kSizeInMemory / sizeof(uint32_t); break; case Type::kArc: static_assert(0 == sizeof(SkArc) % sizeof(uint32_t)); count += sizeof(SkArc) / sizeof(uint32_t); break; case Type::kPath: { if (this->path().isVolatile()) { return -1; // volatile, so won't be keyed } if (this->path().isEmpty()) { return -1; // empty, so won't be keyed } int dataKeySize = path_key_from_data_size(this->path()); if (dataKeySize >= 0) { count += dataKeySize; } else { count++; // Just adds the gen ID. } break; } default: // else it's empty, which just needs the state flags for its key SkASSERT(this->isEmpty()); } return count; } void Shape::writeKey(uint32_t* key, bool includeInverted) const { SkASSERT(this->keySize()); SkDEBUGCODE(uint32_t* origKey = key;) // Every key starts with the state from the Shape (this includes path fill type, // and any tracked inversion, as well as the class of geometry). *key++ = this->stateKey(includeInverted); switch(this->type()) { case Type::kPath: { SkASSERT(!this->path().isVolatile()); SkASSERT(!this->path().isEmpty()); // Ensure that the path's inversion matches our state so that the path's key suffices. SkASSERT(this->inverted() == this->path().isInverseFillType()); int dataKeySize = path_key_from_data_size(this->path()); if (dataKeySize >= 0) { write_path_key_from_data(this->path(), key); return; } else { *key++ = this->path().getGenerationID(); } break; } case Type::kRect: memcpy(key, &this->rect(), sizeof(Rect)); key += sizeof(Rect) / sizeof(uint32_t); break; case Type::kRRect: this->rrect().writeToMemory(key); key += SkRRect::kSizeInMemory / sizeof(uint32_t); break; case Type::kArc: { // Write dense floats first memcpy(key, &fArc, sizeof(SkRect) + 2 * sizeof(float)); key += (sizeof(SkArc) / sizeof(uint32_t) - 1); // Then write the final bool as an int, to make sure upper bits are set *key++ = fArc.isWedge() ? 1 : 0; break; } case Type::kLine: { skvx::float4 line = this->line(); memcpy(key, &line, sizeof(skvx::float4)); key += sizeof(skvx::float4) / sizeof(uint32_t); break; } default: // Nothing other than the flag state is needed in the key for an empty shape SkASSERT(this->isEmpty()); } SkASSERT(key - origKey == this->keySize()); } namespace { SkPathFillType noninverted_fill_type(SkPathFillType fillType) { switch (fillType) { case SkPathFillType::kWinding: case SkPathFillType::kInverseWinding: return SkPathFillType::kWinding; case SkPathFillType::kEvenOdd: case SkPathFillType::kInverseEvenOdd: return SkPathFillType::kEvenOdd; } SkUNREACHABLE; } } // anonymous namespace uint32_t Shape::stateKey(bool includeInverted) const { uint32_t key; if (includeInverted) { // Use the path's full fill type instead of just whether or not it's inverted. key = this->isPath() ? static_cast(fPath.getFillType()) : (fInverted ? 1 : 0); } else { // Use the path's noninverted fill type. key = this->isPath() ? static_cast(noninverted_fill_type(fPath.getFillType())) : 0; } key |= ((uint32_t) fType) << 2; // fill type was 2 bits return key; } } // namespace skgpu::graphite