[graphite] Add caching functionality to ClipAtlasManager
Bug: b/388809647
Bug: b/388808216
Change-Id: I9908a0059d4f39cfc2707049afb640d92a95fca9
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/937417
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/graphite/ClipAtlasManager.cpp b/src/gpu/graphite/ClipAtlasManager.cpp
index 8a65dca..128d107 100644
--- a/src/gpu/graphite/ClipAtlasManager.cpp
+++ b/src/gpu/graphite/ClipAtlasManager.cpp
@@ -9,6 +9,7 @@
#include "include/gpu/graphite/Recorder.h"
#include "src/gpu/graphite/AtlasProvider.h"
+#include "src/gpu/graphite/RasterPathUtils.h"
#include "src/gpu/graphite/RecorderPriv.h"
#include "src/gpu/graphite/TextureProxy.h"
@@ -38,36 +39,187 @@
}
}
-std::tuple<const TextureProxy*, Rect> ClipAtlasManager::findClip(const UniqueKey&) {
- // TODO
- return {nullptr, {}};
+namespace {
+// TODO: is this necessary for clips?
+constexpr int kEntryPadding = 1;
+} // namespace
+
+const TextureProxy* ClipAtlasManager::findOrCreateEntry(uint32_t stackRecordID,
+ const ClipStack::ElementList* elementList,
+ const Rect& bounds,
+ skvx::half2* outPos) {
+ skgpu::UniqueKey maskKey = GenerateClipMaskKey(stackRecordID, elementList);
+
+ MaskHashArray* cachedArray = fMaskCache.find(maskKey);
+ if (cachedArray) {
+ for (int i = 0; i < cachedArray->size(); ++i) {
+ MaskHashEntry& entry = (*cachedArray)[i];
+ // We can reuse a clip mask if has the same key and our bounds is contained in it
+ if (entry.fBounds.contains(bounds)) {
+ SkIPoint topLeft = entry.fLocator.topLeft();
+ // We need to adjust the returned outPos to reflect the subset we're using
+ skvx::float2 subsetRelativePos = bounds.topLeft() - entry.fBounds.topLeft();
+ *outPos = skvx::half2(topLeft.x() + kEntryPadding + subsetRelativePos.x(),
+ topLeft.y() + kEntryPadding + subsetRelativePos.y());
+ fDrawAtlas->setLastUseToken(entry.fLocator,
+ fRecorder->priv().tokenTracker()->nextFlushToken());
+ return fDrawAtlas->getProxies()[entry.fLocator.pageIndex()].get();
+ }
+ }
+ }
+
+ AtlasLocator locator;
+ const TextureProxy* proxy = this->addToAtlas(elementList, bounds, outPos, &locator);
+ if (!proxy) {
+ return nullptr;
+ }
+
+ // Add locator and bounds to MaskCache.
+ if (cachedArray) {
+ cachedArray->push_back({bounds, locator});
+ } else {
+ MaskHashArray initialArray;
+ initialArray.push_back({bounds, locator});
+ fMaskCache.set(maskKey, initialArray);
+ }
+ // Add key to Plot's MaskKeyList.
+ uint32_t index = fDrawAtlas->getListIndex(locator.plotLocator());
+ MaskKeyEntry* keyEntry = new MaskKeyEntry();
+ keyEntry->fKey = maskKey;
+ keyEntry->fBounds = bounds;
+ fKeyLists[index].addToTail(keyEntry);
+
+ return proxy;
}
-std::tuple<const TextureProxy*, Rect> ClipAtlasManager::addClip(const UniqueKey&, Rect bounds,
- const ClipStack::ElementList*) {
- // TODO
- return {nullptr, {}};
+// Copied and modified from Ganesh ClipStack
+void draw_to_sw_mask(RasterMaskHelper* helper,
+ const ClipStack::Element& e,
+ bool clearMask,
+ const SkIRect& resultBounds) {
+ // If the first element to draw is an intersect, we clear to 0 and will draw it directly with
+ // coverage 1 (subsequent intersect elements will be inverse-filled and draw 0 outside).
+ // If the first element to draw is a difference, we clear to 1, and in all cases we draw the
+ // difference element directly with coverage 0.
+ if (clearMask) {
+ helper->clear(e.fOp == SkClipOp::kIntersect ? 0x00 : 0xFF, resultBounds);
+ }
+
+ uint8_t alpha;
+ bool invert;
+ if (e.fOp == SkClipOp::kIntersect) {
+ // Intersect modifies pixels outside of its geometry. If this isn't the first op, we
+ // draw the inverse-filled shape with 0 coverage to erase everything outside the element
+ // But if we are the first element, we can draw directly with coverage 1 since we
+ // cleared to 0.
+ if (clearMask) {
+ alpha = 0xFF;
+ invert = false;
+ } else {
+ alpha = 0x00;
+ invert = true;
+ }
+ } else {
+ // For difference ops, can always just subtract the shape directly by drawing 0 coverage
+ SkASSERT(e.fOp == SkClipOp::kDifference);
+ alpha = 0x00;
+ invert = false;
+ }
+
+ // Draw the shape; based on how we've initialized the buffer and chosen alpha+invert,
+ // every element is drawn with the kReplace_Op
+ if (invert) {
+ // Must invert the path
+ SkASSERT(!e.fShape.inverted());
+ // TODO: this is an extra copy effectively, just so we can toggle inversion; would be
+ // better perhaps to just call a drawPath() since we know it'll use path rendering w/
+ // the inverse fill type.
+ Shape inverted(e.fShape);
+ inverted.setInverted(true);
+ helper->drawClip(inverted, e.fLocalToDevice, alpha, resultBounds);
+ } else {
+ helper->drawClip(e.fShape, e.fLocalToDevice, alpha, resultBounds);
+ }
+}
+
+const TextureProxy* ClipAtlasManager::addToAtlas(const ClipStack::ElementList* elementsForMask,
+ const Rect& bounds,
+ skvx::half2* outPos,
+ AtlasLocator* locator) {
+ // Render mask.
+ skvx::float2 maskSize = bounds.size();
+ if (!all(maskSize)) {
+ return nullptr;
+ }
+
+ SkIRect iShapeBounds = SkIRect::MakeXYWH(0, 0, maskSize.x(), maskSize.y());
+ // Outset to take padding into account
+ SkIRect iAtlasBounds = iShapeBounds.makeOutset(kEntryPadding, kEntryPadding);
+
+ // Request space in DrawAtlas.
+ DrawAtlas::ErrorCode errorCode = fDrawAtlas->addRect(fRecorder,
+ iAtlasBounds.width(),
+ iAtlasBounds.height(),
+ locator);
+ if (errorCode != DrawAtlas::ErrorCode::kSucceeded) {
+ return nullptr;
+ }
+ SkIPoint topLeft = locator->topLeft();
+ *outPos = skvx::half2(topLeft.x() + kEntryPadding, topLeft.y() + kEntryPadding);
+
+ // Rasterize path to backing pixmap.
+ // This pixmap will be the size of the Plot that contains the given rect, not the entire atlas,
+ // and hence the position we render at will be relative to that Plot.
+ // The value of outPos is relative to the entire texture, to be used for texture coords.
+ SkAutoPixmapStorage dst;
+ SkIPoint renderPos = fDrawAtlas->prepForRender(*locator, &dst);
+
+ // This will remove the base integer translation from each element's transform
+ Rect iBounds = bounds.makeRoundOut();
+ RasterMaskHelper helper(&dst);
+ if (!helper.init(fDrawAtlas->plotSize(), iBounds.topLeft())) {
+ return nullptr;
+ }
+
+ // Offset to plot location and draw
+ iShapeBounds.offset(renderPos.x() + kEntryPadding, renderPos.y() + kEntryPadding);
+
+ SkASSERT(elementsForMask->size() > 0);
+ for (int i = 0; i < elementsForMask->size(); ++i) {
+ draw_to_sw_mask(&helper, *(*elementsForMask)[i], i == 0, iShapeBounds);
+ }
+
+ fDrawAtlas->setLastUseToken(*locator,
+ fRecorder->priv().tokenTracker()->nextFlushToken());
+
+ return fDrawAtlas->getProxies()[locator->pageIndex()].get();
}
bool ClipAtlasManager::recordUploads(DrawContext* dc) {
return (fDrawAtlas && !fDrawAtlas->recordUploads(dc, fRecorder));
}
-namespace {
-uint32_t mask_key_list_index(const PlotLocator& locator, const DrawAtlas* drawAtlas) {
- return locator.pageIndex() * drawAtlas->numPlots() + locator.plotIndex();
-}
-} // namespace
-
void ClipAtlasManager::evict(PlotLocator plotLocator) {
// Remove all entries for this Plot from the MaskCache
- uint32_t index = mask_key_list_index(plotLocator, fDrawAtlas.get());
+ uint32_t index = fDrawAtlas->getListIndex(plotLocator);
MaskKeyList::Iter iter;
iter.init(fKeyLists[index], MaskKeyList::Iter::kHead_IterStart);
MaskKeyEntry* currEntry;
while ((currEntry = iter.get())) {
iter.next();
- fMaskCache.remove(currEntry->fKey);
+ MaskHashArray* cachedArray = fMaskCache.find(currEntry->fKey);
+ // Remove this entry from the hashed array
+ for (int i = cachedArray->size()-1; i >=0 ; --i) {
+ MaskHashEntry& entry = (*cachedArray)[i];
+ if (entry.fBounds == currEntry->fBounds) {
+ (*cachedArray).removeShuffle(i);
+ break;
+ }
+ }
+ // If we removed the last one, remove the hash entry
+ if (cachedArray->size() == 0) {
+ fMaskCache.remove(currEntry->fKey);
+ }
fKeyLists[index].remove(currEntry);
delete currEntry;
}
diff --git a/src/gpu/graphite/ClipAtlasManager.h b/src/gpu/graphite/ClipAtlasManager.h
index 11e0ca7..a791303 100644
--- a/src/gpu/graphite/ClipAtlasManager.h
+++ b/src/gpu/graphite/ClipAtlasManager.h
@@ -28,9 +28,14 @@
ClipAtlasManager(Recorder* recorder);
~ClipAtlasManager() override = default;
- std::tuple<const TextureProxy*, Rect> findClip(const UniqueKey&);
- std::tuple<const TextureProxy*, Rect> addClip(const UniqueKey&, Rect bounds,
- const ClipStack::ElementList*);
+ const TextureProxy* findOrCreateEntry(uint32_t stackRecordID,
+ const ClipStack::ElementList*,
+ const Rect& bounds,
+ skvx::half2* outPos);
+ const TextureProxy* addToAtlas(const ClipStack::ElementList*,
+ const Rect& bounds,
+ skvx::half2* outPos,
+ AtlasLocator* locator);
bool recordUploads(DrawContext* dc);
void evict(PlotLocator) override;
@@ -42,11 +47,17 @@
Recorder* fRecorder;
std::unique_ptr<DrawAtlas> fDrawAtlas;
- // Tracks whether a clip mask is already in the DrawAtlas, and its location in the atlas
+ // Tracks whether a combined clip mask is already in the DrawAtlas and its location in the atlas
+ struct MaskHashEntry {
+ Rect fBounds;
+ AtlasLocator fLocator;
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(MaskHashEntry);
+ };
+ using MaskHashArray = SkTDArray<MaskHashEntry>;
struct UniqueKeyHash {
uint32_t operator()(const skgpu::UniqueKey& key) const { return key.hash(); }
};
- using MaskCache = skia_private::THashMap<skgpu::UniqueKey, AtlasLocator, UniqueKeyHash>;
+ using MaskCache = skia_private::THashMap<skgpu::UniqueKey, MaskHashArray, UniqueKeyHash>;
MaskCache fMaskCache;
// List of stored keys per Plot, used to invalidate cache entries.
@@ -55,6 +66,7 @@
// then iterate through the list and remove entries matching those keys from the MaskCache.
struct MaskKeyEntry {
skgpu::UniqueKey fKey;
+ Rect fBounds;
SK_DECLARE_INTERNAL_LLIST_INTERFACE(MaskKeyEntry);
};
using MaskKeyList = SkTInternalLList<MaskKeyEntry>;
diff --git a/src/gpu/graphite/DrawAtlas.h b/src/gpu/graphite/DrawAtlas.h
index 3fc169d..d81e192 100644
--- a/src/gpu/graphite/DrawAtlas.h
+++ b/src/gpu/graphite/DrawAtlas.h
@@ -124,6 +124,9 @@
uint32_t numActivePages() const { return fNumActivePages; }
unsigned int numPlots() const { return fNumPlots; }
SkISize plotSize() const { return {fPlotWidth, fPlotHeight}; }
+ uint32_t getListIndex(const PlotLocator& locator) {
+ return locator.pageIndex() * fNumPlots + locator.plotIndex();
+ }
bool hasID(const PlotLocator& plotLocator) {
if (!plotLocator.isValid()) {
diff --git a/src/gpu/graphite/PathAtlas.cpp b/src/gpu/graphite/PathAtlas.cpp
index 7f28a5f..27adba9 100644
--- a/src/gpu/graphite/PathAtlas.cpp
+++ b/src/gpu/graphite/PathAtlas.cpp
@@ -103,12 +103,6 @@
}
}
-namespace {
-uint32_t shape_key_list_index(const PlotLocator& locator, const DrawAtlas* drawAtlas) {
- return locator.pageIndex() * drawAtlas->numPlots() + locator.plotIndex();
-}
-} // namespace
-
const TextureProxy* PathAtlas::DrawAtlasMgr::findOrCreateEntry(Recorder* recorder,
const Shape& shape,
const Transform& localToDevice,
@@ -139,7 +133,7 @@
// Add locator to ShapeCache.
fShapeCache.set(maskKey, locator);
// Add key to Plot's ShapeKeyList.
- uint32_t index = shape_key_list_index(locator.plotLocator(), fDrawAtlas.get());
+ uint32_t index = fDrawAtlas->getListIndex(locator.plotLocator());
ShapeKeyEntry* keyEntry = new ShapeKeyEntry();
keyEntry->fKey = maskKey;
fKeyLists[index].addToTail(keyEntry);
@@ -197,7 +191,7 @@
void PathAtlas::DrawAtlasMgr::evict(PlotLocator plotLocator) {
// Remove all entries for this Plot from the ShapeCache
- uint32_t index = shape_key_list_index(plotLocator, fDrawAtlas.get());
+ uint32_t index = fDrawAtlas->getListIndex(plotLocator);
ShapeKeyList::Iter iter;
iter.init(fKeyLists[index], ShapeKeyList::Iter::kHead_IterStart);
ShapeKeyEntry* currEntry;
diff --git a/src/gpu/graphite/RasterPathUtils.cpp b/src/gpu/graphite/RasterPathUtils.cpp
index 1ce5d29..5d1548a 100644
--- a/src/gpu/graphite/RasterPathUtils.cpp
+++ b/src/gpu/graphite/RasterPathUtils.cpp
@@ -60,6 +60,7 @@
resultBounds.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
@@ -68,6 +69,69 @@
fDraw.drawPathCoverage(path, paint);
}
+void RasterMaskHelper::drawClip(const Shape& shape,
+ const Transform& transform,
+ 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(transform);
+ // The atlas transform of the shape is the linear-components (scale, rotation, skew) of
+ // `localToDevice` translated by the top-left offset of the resultBounds.
+ // We will need to translate draws so the bound's UL corner is at the origin
+ translatedMatrix.postTranslate(resultBounds.x(), resultBounds.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,
@@ -86,31 +150,8 @@
builder[0] = maskOrigin.x() | (maskOrigin.y() << 16);
builder[1] = maskSize.x() | (maskSize.y() << 16);
- // 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[2] = SkFloat2Bits(sx);
- builder[3] = SkFloat2Bits(sy);
- builder[4] = SkFloat2Bits(kx);
- builder[5] = 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);
+ // 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.
@@ -132,4 +173,45 @@
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) {
+ skgpu::UniqueKey::Builder builder(&maskKey, kDomain, 1, "Clip Path Mask");
+ builder[0] = stackRecordID;
+ } else {
+ int xformKeySize = 5;
+ int keySize = 0;
+ for (int i = 0; i < elementsForMask->size(); ++i) {
+ keySize += xformKeySize + (*elementsForMask)[i]->fShape.keySize();
+ }
+ skgpu::UniqueKey::Builder builder(&maskKey, kDomain, keySize,
+ "Clip Path Mask");
+ int elementKeyIndex = 0;
+ for (int i = 0; i < elementsForMask->size(); ++i) {
+ // Add transform key and get packed fractional translation bits
+ uint32_t fracBits = add_transform_key(&builder,
+ elementKeyIndex,
+ (*elementsForMask)[i]->fLocalToDevice);
+ uint32_t opBits = static_cast<uint32_t>((*elementsForMask)[i]->fOp);
+ builder[elementKeyIndex + 4] = fracBits | (opBits << 16);
+
+ const Shape& shape = (*elementsForMask)[i]->fShape;
+ shape.writeKey(&builder[elementKeyIndex + xformKeySize], /*includeInverted=*/true);
+
+ elementKeyIndex += xformKeySize + shape.keySize();
+ }
+ }
+
+ }
+
+ return maskKey;
+
+}
+
} // namespace skgpu::graphite
diff --git a/src/gpu/graphite/RasterPathUtils.h b/src/gpu/graphite/RasterPathUtils.h
index baa4cea..e431fc6 100644
--- a/src/gpu/graphite/RasterPathUtils.h
+++ b/src/gpu/graphite/RasterPathUtils.h
@@ -14,6 +14,7 @@
#include "src/core/SkDrawBase.h"
#include "src/core/SkRasterClip.h"
#include "src/gpu/ResourceKey.h"
+#include "src/gpu/graphite/ClipStack_graphite.h"
namespace skgpu::graphite {
@@ -40,12 +41,25 @@
bool init(SkISize pixmapSize, skvx::float2 transformedMaskOffset);
- // Draw a single shape into the bitmap (as a path) at location resultBounds
+ void clear(uint8_t alpha, const SkIRect& resultBounds) {
+ SkPaint paint;
+ paint.setColor(SkColorSetARGB(alpha, 0xFF, 0xFF, 0xFF));
+ fDraw.drawRect(SkRect::Make(resultBounds), paint);
+ }
+
+ // Draw a single shape into the bitmap (as a path) at location resultBounds.
void drawShape(const Shape& shape,
const Transform& localToDevice,
const SkStrokeRec& strokeRec,
const SkIRect& resultBounds);
+ // Draw a single shape into the bitmap (as a path) at location resultBounds.
+ // Variant used for clipping.
+ void drawClip(const Shape& shape,
+ const Transform& transform,
+ uint8_t alpha,
+ const SkIRect& resultBounds);
+
private:
SkAutoPixmapStorage* fPixels;
SkDrawBase fDraw;
@@ -58,6 +72,10 @@
const SkStrokeRec& strokeRec,
skvx::half2 maskOrigin,
skvx::half2 maskSize);
+
+skgpu::UniqueKey GenerateClipMaskKey(uint32_t stackRecordID,
+ const ClipStack::ElementList* elementsForMask);
+
} // namespace skgpu::graphite
#endif // skgpu_graphite_RasterPathUtils_DEFINED