/* * 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& shapeBounds) { fPixels->erase(SkColorSetARGB(alpha, 0xFF, 0xFF, 0xFF), shapeBounds); } void RasterMaskHelper::drawShape(const Shape& shape, const Transform& localToDevice, const SkStrokeRec& strokeRec, const SkIRect& shapeBounds) { fRasterClip.setRect(shapeBounds); 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 // resultBounds 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(shapeBounds.x() - fTransformedMaskOffset.x(), shapeBounds.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& resultBounds) { fRasterClip.setRect(resultBounds); 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 // resultBounds 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(resultBounds.x() - fTransformedMaskOffset.x(), resultBounds.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, true); } } 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) { skgpu::UniqueKey maskKey; { static constexpr int kMaxShapeCountForKey = 2; static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain(); // 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) { skgpu::UniqueKey::Builder builder(&maskKey, kDomain, keySize, "Clip Path Mask"); int elementKeyIndex = 0; 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(element->fOp); builder[elementKeyIndex + 4] = fracBits | (opBits << 16); const Shape& shape = element->fShape; shape.writeKey(&builder[elementKeyIndex + kXformKeySize], /*includeInverted=*/true); elementKeyIndex += kXformKeySize + shape.keySize(); } 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 Path Mask"); builder[0] = stackRecordID; } return maskKey; } } // namespace skgpu::graphite