blob: 128d107afe283d3e4dd91f5ea7dae03059e06996 [file] [log] [blame]
/*
* 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/ClipAtlasManager.h"
#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"
namespace skgpu::graphite {
ClipAtlasManager::ClipAtlasManager(Recorder* recorder) : fRecorder(recorder) {
static constexpr SkColorType kColorType = kAlpha_8_SkColorType;
static constexpr int kWidth = 2048;
static constexpr int kHeight = 2048;
const Caps* caps = recorder->priv().caps();
fDrawAtlas = DrawAtlas::Make(kColorType,
SkColorTypeBytesPerPixel(kColorType),
kWidth, kHeight,
/*plotWidth=*/kWidth, /*plotHeight=*/kHeight,
/*generationCounter=*/this,
caps->allowMultipleAtlasTextures() ?
DrawAtlas::AllowMultitexturing::kYes :
DrawAtlas::AllowMultitexturing::kNo,
DrawAtlas::UseStorageTextures::kNo,
/*evictor=*/this,
"ClipAtlas");
SkASSERT(fDrawAtlas);
fKeyLists.resize(fDrawAtlas->numPlots() * fDrawAtlas->maxPages());
for (int i = 0; i < fKeyLists.size(); ++i) {
fKeyLists[i].reset();
}
}
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;
}
// 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));
}
void ClipAtlasManager::evict(PlotLocator plotLocator) {
// Remove all entries for this Plot from the MaskCache
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();
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;
}
}
void ClipAtlasManager::evictAll() {
fDrawAtlas->evictAllPlots();
SkASSERT(fMaskCache.empty());
}
void ClipAtlasManager::compact(bool forceCompact) {
auto tokenTracker = fRecorder->priv().tokenTracker();
if (fDrawAtlas) {
fDrawAtlas->compact(tokenTracker->nextFlushToken(), forceCompact);
}
}
} // namespace skgpu::graphite