|  | /* | 
|  | * Copyright 2024 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/RasterPathUtils.h" | 
|  |  | 
|  | #include "include/core/SkStrokeRec.h" | 
|  | #include "include/private/base/SkFixed.h" | 
|  | #include "src/base/SkFloatBits.h" | 
|  | #include "src/core/SkBlitter_A8.h" | 
|  | #include "src/gpu/graphite/geom/Shape.h" | 
|  | #include "src/gpu/graphite/geom/Transform.h" | 
|  |  | 
|  | namespace skgpu::graphite { | 
|  |  | 
|  | bool RasterMaskHelper::init(SkISize pixmapSize, SkIVector transformedMaskOffset) { | 
|  | if (!fPixels) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Allocate pixmap if needed | 
|  | if (!fPixels->addr()) { | 
|  | const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(pixmapSize); | 
|  | if (!fPixels->tryAlloc(bmImageInfo)) { | 
|  | return false; | 
|  | } | 
|  | fPixels->erase(0); | 
|  | } else if (fPixels->dimensions() != pixmapSize) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | fDraw.fBlitterChooser = SkA8Blitter_Choose; | 
|  | fDraw.fDst = *fPixels; | 
|  | fDraw.fRC = &fRasterClip; | 
|  | fTransformedMaskOffset = transformedMaskOffset; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void RasterMaskHelper::clear(uint8_t alpha, const SkIRect& drawBounds) { | 
|  | fPixels->erase(SkColorSetARGB(alpha, 0xFF, 0xFF, 0xFF), drawBounds); | 
|  | } | 
|  |  | 
|  | void RasterMaskHelper::drawShape(const Shape& shape, | 
|  | const Transform& localToDevice, | 
|  | const SkStrokeRec& strokeRec, | 
|  | const SkIRect& drawBounds) { | 
|  | fRasterClip.setRect(drawBounds); | 
|  |  | 
|  | SkPaint paint; | 
|  | paint.setBlendMode(SkBlendMode::kSrc);  // "Replace" mode | 
|  | paint.setAntiAlias(true); | 
|  | // SkPaint's color is unpremul so this will produce alpha in every channel. | 
|  | paint.setColor(SK_ColorWHITE); | 
|  | strokeRec.applyToPaint(&paint); | 
|  |  | 
|  | SkMatrix translatedMatrix = SkMatrix(localToDevice); | 
|  | // The atlas transform of the shape is `localToDevice` translated by the top-left offset of the | 
|  | // drawBounds and the inverse of the base mask transform offset for the current set of shapes. | 
|  | // We will need to translate draws so the bound's UL corner is at the origin | 
|  | translatedMatrix.postTranslate(drawBounds.x() - fTransformedMaskOffset.x(), | 
|  | drawBounds.y() - fTransformedMaskOffset.y()); | 
|  |  | 
|  | fDraw.fCTM = &translatedMatrix; | 
|  | // TODO: use drawRect, drawRRect, drawArc | 
|  | SkPath path = shape.asPath(); | 
|  | if (path.isInverseFillType()) { | 
|  | // The shader will handle the inverse fill in this case | 
|  | path.toggleInverseFillType(); | 
|  | } | 
|  | fDraw.drawPathCoverage(path, paint); | 
|  | } | 
|  |  | 
|  | void RasterMaskHelper::drawClip(const Shape& shape, | 
|  | const Transform& localToDevice, | 
|  | uint8_t alpha, | 
|  | const SkIRect& drawBounds) { | 
|  | fRasterClip.setRect(drawBounds); | 
|  |  | 
|  | SkPaint paint; | 
|  | paint.setBlendMode(SkBlendMode::kSrc);  // "Replace" mode | 
|  | paint.setAntiAlias(true); | 
|  | // SkPaint's color is unpremul so this will produce alpha in every channel. | 
|  | paint.setColor(SkColorSetARGB(alpha, 0xFF, 0xFF, 0xFF)); | 
|  |  | 
|  | SkMatrix translatedMatrix = SkMatrix(localToDevice); | 
|  | // The atlas transform of the shape is `localToDevice` translated by the top-left offset of the | 
|  | // drawBounds and the inverse of the base mask transform offset for the current set of shapes. | 
|  | // We will need to translate draws so the bound's UL corner is at the origin | 
|  | translatedMatrix.postTranslate(drawBounds.x() - fTransformedMaskOffset.x(), | 
|  | drawBounds.y() - fTransformedMaskOffset.y()); | 
|  |  | 
|  | fDraw.fCTM = &translatedMatrix; | 
|  | // TODO: use drawRect, drawRRect, drawArc | 
|  | SkPath path = shape.asPath(); | 
|  | // Because we could be combining multiple paths into one entry we don't touch | 
|  | // the inverse fill in this case. | 
|  | if (0xFF == alpha) { | 
|  | SkASSERT(0xFF == paint.getAlpha()); | 
|  | fDraw.drawPathCoverage(path, paint); | 
|  | } else { | 
|  | fDraw.drawPath(path, paint, nullptr); | 
|  | } | 
|  | } | 
|  |  | 
|  | uint32_t add_transform_key(skgpu::UniqueKey::Builder* builder, | 
|  | int startIndex, | 
|  | const Transform& transform) { | 
|  | // We require the upper left 2x2 of the matrix to match exactly for a cache hit. | 
|  | SkMatrix mat = transform.matrix().asM33(); | 
|  | SkScalar sx = mat.get(SkMatrix::kMScaleX); | 
|  | SkScalar sy = mat.get(SkMatrix::kMScaleY); | 
|  | SkScalar kx = mat.get(SkMatrix::kMSkewX); | 
|  | SkScalar ky = mat.get(SkMatrix::kMSkewY); | 
|  | #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK | 
|  | // Fractional translate does not affect caching on Android. This is done for better cache | 
|  | // hit ratio and speed and is matching HWUI behavior, which didn't consider the matrix | 
|  | // at all when caching paths. | 
|  | SkFixed fracX = 0; | 
|  | SkFixed fracY = 0; | 
|  | #else | 
|  | SkScalar tx = mat.get(SkMatrix::kMTransX); | 
|  | SkScalar ty = mat.get(SkMatrix::kMTransY); | 
|  | // Allow 8 bits each in x and y of subpixel positioning. | 
|  | SkFixed fracX = SkScalarToFixed(SkScalarFraction(tx)) & 0x0000FF00; | 
|  | SkFixed fracY = SkScalarToFixed(SkScalarFraction(ty)) & 0x0000FF00; | 
|  | #endif | 
|  | (*builder)[startIndex + 0] = SkFloat2Bits(sx); | 
|  | (*builder)[startIndex + 1] = SkFloat2Bits(sy); | 
|  | (*builder)[startIndex + 2] = SkFloat2Bits(kx); | 
|  | (*builder)[startIndex + 3] = SkFloat2Bits(ky); | 
|  | // FracX and fracY are &ed with 0x0000ff00, so need to shift one down to fill 16 bits. | 
|  | uint32_t fracBits = fracX | (fracY >> 8); | 
|  |  | 
|  | return fracBits; | 
|  | } | 
|  |  | 
|  | skgpu::UniqueKey GeneratePathMaskKey(const Shape& shape, | 
|  | const Transform& transform, | 
|  | const SkStrokeRec& strokeRec, | 
|  | skvx::half2 maskOrigin, | 
|  | skvx::half2 maskSize) { | 
|  | skgpu::UniqueKey maskKey; | 
|  | { | 
|  | static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain(); | 
|  | int styleKeySize = 7; | 
|  | if (!strokeRec.isHairlineStyle() && !strokeRec.isFillStyle()) { | 
|  | // Add space for width and miter if needed | 
|  | styleKeySize += 2; | 
|  | } | 
|  | skgpu::UniqueKey::Builder builder(&maskKey, kDomain, styleKeySize + shape.keySize(), | 
|  | "Raster Path Mask"); | 
|  | builder[0] = maskOrigin.x() | (maskOrigin.y() << 16); | 
|  | builder[1] = maskSize.x() | (maskSize.y() << 16); | 
|  |  | 
|  | // Add transform key and get packed fractional translation bits | 
|  | uint32_t fracBits = add_transform_key(&builder, 2, transform); | 
|  | // Distinguish between path styles. For anything but fill, we also need to include | 
|  | // the cap. (SW grows hairlines by 0.5 pixel with round and square caps). For stroke | 
|  | // or fill-and-stroke we need to include the join, width, and miter. | 
|  | static_assert(SkStrokeRec::kStyleCount <= (1 << 2)); | 
|  | static_assert(SkPaint::kCapCount <= (1 << 2)); | 
|  | static_assert(SkPaint::kJoinCount <= (1 << 2)); | 
|  | uint32_t styleBits = strokeRec.getStyle(); | 
|  | if (!strokeRec.isFillStyle()) { | 
|  | styleBits |= (strokeRec.getCap() << 2); | 
|  | } | 
|  | if (!strokeRec.isHairlineStyle() && !strokeRec.isFillStyle()) { | 
|  | styleBits |= (strokeRec.getJoin() << 4); | 
|  | builder[6] = SkFloat2Bits(strokeRec.getWidth()); | 
|  | builder[7] = SkFloat2Bits(strokeRec.getMiter()); | 
|  | } | 
|  | builder[styleKeySize-1] = fracBits | (styleBits << 16); | 
|  | shape.writeKey(&builder[styleKeySize], /*includeInverted=*/false); | 
|  | } | 
|  | return maskKey; | 
|  | } | 
|  |  | 
|  | skgpu::UniqueKey GenerateClipMaskKey(uint32_t stackRecordID, | 
|  | const ClipStack::ElementList* elementsForMask, | 
|  | SkIRect maskDeviceBounds, | 
|  | bool includeBounds, | 
|  | SkIRect* keyBounds, | 
|  | bool* usesPathKey) { | 
|  | static constexpr int kMaxShapeCountForKey = 2; | 
|  | static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain(); | 
|  |  | 
|  | skgpu::UniqueKey maskKey; | 
|  | // if the element list is too large we just use the stackRecordID | 
|  | if (elementsForMask->size() <= kMaxShapeCountForKey) { | 
|  | constexpr int kXformKeySize = 5; | 
|  | int keySize = 0; | 
|  | bool canCreateKey = true; | 
|  | // Iterate through to get key size and see if we can create a key at all | 
|  | for (int i = 0; i < elementsForMask->size(); ++i) { | 
|  | int shapeKeySize = (*elementsForMask)[i]->fShape.keySize(); | 
|  | if (shapeKeySize < 0) { | 
|  | canCreateKey = false; | 
|  | break; | 
|  | } | 
|  | keySize += kXformKeySize + shapeKeySize; | 
|  | } | 
|  | if (canCreateKey) { | 
|  | if (includeBounds) { | 
|  | keySize += 2; | 
|  | } | 
|  | skgpu::UniqueKey::Builder builder(&maskKey, kDomain, keySize, | 
|  | "Clip Path Mask"); | 
|  | int elementKeyIndex = 0; | 
|  | Rect unclippedBounds = Rect::InfiniteInverted(); | 
|  | for (int i = 0; i < elementsForMask->size(); ++i) { | 
|  | const ClipStack::Element* element = (*elementsForMask)[i]; | 
|  |  | 
|  | // Add transform key and get packed fractional translation bits | 
|  | uint32_t fracBits = add_transform_key(&builder, | 
|  | elementKeyIndex, | 
|  | element->fLocalToDevice); | 
|  | uint32_t opBits = static_cast<uint32_t>(element->fOp); | 
|  | builder[elementKeyIndex + 4] = fracBits | (opBits << 16); | 
|  |  | 
|  | const Shape& shape = element->fShape; | 
|  | shape.writeKey(&builder[elementKeyIndex + kXformKeySize], | 
|  | /*includeInverted=*/true); | 
|  |  | 
|  | elementKeyIndex += kXformKeySize + shape.keySize(); | 
|  |  | 
|  | Rect transformedBounds = element->fLocalToDevice.mapRect(element->fShape.bounds()); | 
|  | unclippedBounds.join(transformedBounds); | 
|  | } | 
|  |  | 
|  | // The keyBounds are the maskDeviceBounds relative to the full transformed mask. We use | 
|  | // this to ensure we capture the situation where the maskDeviceBounds are equal in two | 
|  | // cases but actually enclose different regions of the full mask due to an integer | 
|  | // translation (which is not captured in the key) in the element transforms. | 
|  | *keyBounds = maskDeviceBounds.makeOffset(-unclippedBounds.left(), | 
|  | -unclippedBounds.top()); | 
|  |  | 
|  | if (includeBounds) { | 
|  | SkASSERT(SkTFitsIn<int16_t>(keyBounds->left())); | 
|  | SkASSERT(SkTFitsIn<int16_t>(keyBounds->top())); | 
|  | SkASSERT(SkTFitsIn<int16_t>(keyBounds->right())); | 
|  | SkASSERT(SkTFitsIn<int16_t>(keyBounds->bottom())); | 
|  |  | 
|  | builder[elementKeyIndex] = keyBounds->left() | (keyBounds->top() << 16); | 
|  | builder[elementKeyIndex+1] = keyBounds->right() | (keyBounds->bottom() << 16); | 
|  | } | 
|  |  | 
|  | *usesPathKey = true; | 
|  | return maskKey; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Either we have too many elements or at least one shape can't create a key | 
|  | skgpu::UniqueKey::Builder builder(&maskKey, kDomain, 1, "Clip SaveRecord Mask"); | 
|  | builder[0] = stackRecordID; | 
|  |  | 
|  | *usesPathKey = false; | 
|  | // It doesn't matter what the keyBounds are in this case -- | 
|  | // the stackRecordID is enough to distinguish between clips. | 
|  | *keyBounds = {}; | 
|  | return maskKey; | 
|  | } | 
|  |  | 
|  | }  // namespace skgpu::graphite |