| /* | |
| * 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/core/SkStrokeRec.h" | |
| #include "include/private/SkAlign.h" | |
| #include "include/private/SkDebug.h" | |
| #include "include/private/SkMalloc.h" | |
| #include "src/core/SkPathPriv.h" | |
| #include <cstring> | |
| 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 fRRect.contains(SkPoint::Make(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) { | |
| // Filled ovals are already culled out so we assume no simple fills | |
| SkPath out = SkPathPriv::CreateDrawArcPath(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(); | |
| } | |
| float Shape::getInflationRadius(SkPaint::Join join, | |
| float miterLimit, | |
| SkPaint::Cap cap, | |
| float strokeWidth) const { | |
| SkASSERT(miterLimit >= 0.f && strokeWidth >= 0.f); | |
| // Rectangles, rounded rectangles, and lines do not produce miters so don't count the | |
| // pessimistic limit against their draw bounds. | |
| const float effectiveMiterLimit = this->isPath() ? miterLimit : 1.f; | |
| // Rectangles and rounded rectangles don't have caps, so don't count that against their | |
| // draw bounds (if we could efficiently know a path was a closed contour, it could | |
| // be included here too). | |
| const SkPaint::Cap effectiveCap = this->isRect() || this->isRRect() ? SkPaint::kButt_Cap : cap; | |
| return SkStrokeRec::GetInflationRadius(join, effectiveMiterLimit, effectiveCap, strokeWidth); | |
| } | |
| namespace { | |
| int path_key_from_data_size(const SkPath& path) { | |
| const int verbCnt = path.countVerbs(); | |
| if (verbCnt > kMaxKeyFromDataVerbCnt) { | |
| return -1; | |
| } | |
| const size_t pointCnt = path.points().size(); | |
| const size_t conicWeightCnt = path.conicWeights().size(); | |
| 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. | |
| SkSpan<const SkPathVerb> verbs = path.verbs(); | |
| SkSpan<const SkPoint> points = path.points(); | |
| SkSpan<const float> conics = path.conicWeights(); | |
| SkASSERT(verbs.size() <= kMaxKeyFromDataVerbCnt); | |
| SkASSERT(points.size() && verbs.size()); | |
| *key++ = SkToInt(verbs.size()); | |
| memcpy(key, verbs.data(), verbs.size_bytes()); | |
| const size_t verbKeySize = SkAlign4(verbs.size()); | |
| // pad out to uint32_t alignment using value that will stand out when debugging. | |
| uint8_t* pad = reinterpret_cast<uint8_t*>(key)+ verbs.size(); | |
| memset(pad, 0xDE, verbKeySize - verbs.size()); | |
| key += verbKeySize >> 2; | |
| memcpy(key, points.data(), points.size_bytes()); | |
| static_assert(sizeof(SkPoint) == 2 * sizeof(uint32_t)); | |
| key += 2 * points.size(); | |
| sk_careful_memcpy(key, conics.data(), conics.size_bytes()); | |
| static_assert(sizeof(SkScalar) == sizeof(uint32_t)); | |
| SkDEBUGCODE(key += conics.size()); | |
| SkASSERT(key - origKey == path_key_from_data_size(path)); | |
| } | |
| } // anonymous namespace | |
| uint16_t Shape::keySize() const { | |
| uint16_t 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: { | |
| // An empty path is the same as an empty shape -- only needs the state flags | |
| if (!this->path().isEmpty()) { | |
| int dataKeySize = path_key_from_data_size(this->path()); | |
| if (dataKeySize >= 0) { | |
| count += SkTo<uint16_t>(dataKeySize); | |
| } else { | |
| count += sizeof(uint64_t) / sizeof(uint32_t); // 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: { | |
| // An empty path is the same as an empty shape -- only needs the state flags | |
| if (!this->path().isEmpty()) { | |
| // The path's inversion must match our state in order for the path's key to suffice. | |
| SkASSERT(this->inverted() == this->path().isInverseFillType()); | |
| int dataKeySize = path_key_from_data_size(this->path()); | |
| if (dataKeySize >= 0) { | |
| // We check the rest of the size in write_path_key_from_data | |
| SkASSERT(key - origKey == 1); | |
| write_path_key_from_data(this->path(), key); | |
| return; | |
| } else { | |
| uint64_t genID = this->path().getGenerationID(); | |
| memcpy(key, &genID, sizeof(uint64_t)); | |
| key += sizeof(uint64_t) / sizeof(uint32_t); | |
| } | |
| } | |
| 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<uint32_t>(fPath.getFillType()) | |
| : (fInverted ? 1 : 0); | |
| } else { | |
| // Use the path's noninverted fill type. | |
| key = this->isPath() ? static_cast<uint32_t>(noninverted_fill_type(fPath.getFillType())) | |
| : 0; | |
| } | |
| key |= ((uint32_t) fType) << 2; // fill type was 2 bits | |
| return key; | |
| } | |
| } // namespace skgpu::graphite |