[graphite] Add DrawAtlas and AtlasManager
Bug: skia:13118 Change-Id: I06d3f899b60ac2cf8d48a46da6097a15c5d40024 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/543923 Reviewed-by: Greg Daniel <egdaniel@google.com> Commit-Queue: Jim Van Verth <jvanverth@google.com> Reviewed-by: Herb Derby <herb@google.com>
This commit is contained in:
parent
b0912790c6
commit
265b53bc76
@ -39,6 +39,8 @@ skia_graphite_sources = [
|
||||
"$_src/CopyTask.h",
|
||||
"$_src/Device.cpp",
|
||||
"$_src/Device.h",
|
||||
"$_src/DrawAtlas.cpp",
|
||||
"$_src/DrawAtlas.h",
|
||||
"$_src/DrawBufferManager.cpp",
|
||||
"$_src/DrawBufferManager.h",
|
||||
"$_src/DrawContext.cpp",
|
||||
@ -127,6 +129,8 @@ skia_graphite_sources = [
|
||||
"$_src/render/TessellateStrokesRenderStep.h",
|
||||
"$_src/render/TessellateWedgesRenderStep.cpp",
|
||||
"$_src/render/TessellateWedgesRenderStep.h",
|
||||
"$_src/text/AtlasManager.cpp",
|
||||
"$_src/text/AtlasManager.h",
|
||||
]
|
||||
|
||||
skia_graphite_mtl_sources = [
|
||||
|
@ -17,8 +17,13 @@ class SkTextureDataBlock;
|
||||
class SkUniformDataBlock;
|
||||
class SkUniformDataBlockPassThrough; // TODO: remove
|
||||
|
||||
namespace skgpu { class TokenTracker; }
|
||||
|
||||
namespace sktext::gpu { class StrikeCache; }
|
||||
|
||||
namespace skgpu::graphite {
|
||||
|
||||
class AtlasManager;
|
||||
class Caps;
|
||||
class Device;
|
||||
class DrawBufferManager;
|
||||
@ -93,6 +98,10 @@ private:
|
||||
std::unique_ptr<UploadBufferManager> fUploadBufferManager;
|
||||
std::vector<Device*> fTrackedDevices;
|
||||
|
||||
std::unique_ptr<AtlasManager> fAtlasManager;
|
||||
std::unique_ptr<TokenTracker> fTokenTracker;
|
||||
std::unique_ptr<sktext::gpu::StrikeCache> fStrikeCache;
|
||||
|
||||
// In debug builds we guard against improper thread handling
|
||||
// This guard is passed to the ResourceCache.
|
||||
// TODO: Should we also pass this to Device, DrawContext, and similar classes?
|
||||
|
@ -454,6 +454,7 @@ public:
|
||||
void resetFlushesSinceLastUsed() { fFlushesSinceLastUse = 0; }
|
||||
void incFlushesSinceLastUsed() { fFlushesSinceLastUse++; }
|
||||
|
||||
bool needsUpload() { return !fDirtyRect.isEmpty(); }
|
||||
std::pair<const void*, SkIRect> prepareForUpload();
|
||||
void resetRects();
|
||||
|
||||
|
502
src/gpu/graphite/DrawAtlas.cpp
Normal file
502
src/gpu/graphite/DrawAtlas.cpp
Normal file
@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Copyright 2022 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/DrawAtlas.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "include/gpu/graphite/Recorder.h"
|
||||
#include "include/private/SkTPin.h"
|
||||
#include "src/core/SkMathPriv.h"
|
||||
#include "src/core/SkOpts.h"
|
||||
#include "src/core/SkTraceEvent.h"
|
||||
#include "src/gpu/AtlasTypes.h"
|
||||
#include "src/gpu/graphite/Caps.h"
|
||||
#include "src/gpu/graphite/CommandTypes.h"
|
||||
#include "src/gpu/graphite/DrawContext.h"
|
||||
#include "src/gpu/graphite/RecorderPriv.h"
|
||||
#include "src/gpu/graphite/TextureProxy.h"
|
||||
|
||||
namespace skgpu::graphite {
|
||||
|
||||
#if defined(DUMP_ATLAS_DATA)
|
||||
static const constexpr bool kDumpAtlasData = true;
|
||||
#else
|
||||
static const constexpr bool kDumpAtlasData = false;
|
||||
#endif
|
||||
|
||||
#ifdef SK_DEBUG
|
||||
void DrawAtlas::validate(const AtlasLocator& atlasLocator) const {
|
||||
// Verify that the plotIndex stored in the PlotLocator is consistent with the glyph rectangle
|
||||
int numPlotsX = fTextureWidth / fPlotWidth;
|
||||
int numPlotsY = fTextureHeight / fPlotHeight;
|
||||
|
||||
int plotIndex = atlasLocator.plotIndex();
|
||||
auto topLeft = atlasLocator.topLeft();
|
||||
int plotX = topLeft.x() / fPlotWidth;
|
||||
int plotY = topLeft.y() / fPlotHeight;
|
||||
SkASSERT(plotIndex == (numPlotsY - plotY - 1) * numPlotsX + (numPlotsX - plotX - 1));
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<DrawAtlas> DrawAtlas::Make(SkColorType colorType, size_t bpp, int width,
|
||||
int height, int plotWidth, int plotHeight,
|
||||
AtlasGenerationCounter* generationCounter,
|
||||
AllowMultitexturing allowMultitexturing,
|
||||
PlotEvictionCallback* evictor,
|
||||
std::string_view label) {
|
||||
std::unique_ptr<DrawAtlas> atlas(new DrawAtlas(colorType, bpp, width, height,
|
||||
plotWidth, plotHeight, generationCounter,
|
||||
allowMultitexturing, label));
|
||||
|
||||
if (evictor != nullptr) {
|
||||
atlas->fEvictionCallbacks.emplace_back(evictor);
|
||||
}
|
||||
return atlas;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
DrawAtlas::DrawAtlas(SkColorType colorType, size_t bpp, int width, int height,
|
||||
int plotWidth, int plotHeight, AtlasGenerationCounter* generationCounter,
|
||||
AllowMultitexturing allowMultitexturing, std::string_view label)
|
||||
: fColorType(colorType)
|
||||
, fBytesPerPixel(bpp)
|
||||
, fTextureWidth(width)
|
||||
, fTextureHeight(height)
|
||||
, fPlotWidth(plotWidth)
|
||||
, fPlotHeight(plotHeight)
|
||||
, fLabel(label)
|
||||
, fGenerationCounter(generationCounter)
|
||||
, fAtlasGeneration(fGenerationCounter->next())
|
||||
, fPrevFlushToken(DrawToken::AlreadyFlushedToken())
|
||||
, fFlushesSinceLastUse(0)
|
||||
, fMaxPages(AllowMultitexturing::kYes == allowMultitexturing ?
|
||||
PlotLocator::kMaxMultitexturePages : 1)
|
||||
, fNumActivePages(0) {
|
||||
int numPlotsX = width/plotWidth;
|
||||
int numPlotsY = height/plotHeight;
|
||||
SkASSERT(numPlotsX * numPlotsY <= PlotLocator::kMaxPlots);
|
||||
SkASSERT(fPlotWidth * numPlotsX == fTextureWidth);
|
||||
SkASSERT(fPlotHeight * numPlotsY == fTextureHeight);
|
||||
|
||||
fNumPlots = numPlotsX * numPlotsY;
|
||||
|
||||
this->createPages(generationCounter);
|
||||
}
|
||||
|
||||
inline void DrawAtlas::processEviction(PlotLocator plotLocator) {
|
||||
for (PlotEvictionCallback* evictor : fEvictionCallbacks) {
|
||||
evictor->evict(plotLocator);
|
||||
}
|
||||
|
||||
fAtlasGeneration = fGenerationCounter->next();
|
||||
}
|
||||
|
||||
inline bool DrawAtlas::updatePlot(AtlasLocator* atlasLocator, Plot* plot) {
|
||||
int pageIdx = plot->pageIndex();
|
||||
this->makeMRU(plot, pageIdx);
|
||||
|
||||
// The actual upload will be created in recordUploads().
|
||||
|
||||
atlasLocator->updatePlotLocator(plot->plotLocator());
|
||||
SkDEBUGCODE(this->validate(*atlasLocator);)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DrawAtlas::addToPage(unsigned int pageIdx, int width, int height, const void* image,
|
||||
AtlasLocator* atlasLocator) {
|
||||
SkASSERT(fProxies[pageIdx]);
|
||||
|
||||
// look through all allocated plots for one we can share, in Most Recently Refed order
|
||||
PlotList::Iter plotIter;
|
||||
plotIter.init(fPages[pageIdx].fPlotList, PlotList::Iter::kHead_IterStart);
|
||||
|
||||
for (Plot* plot = plotIter.get(); plot; plot = plotIter.next()) {
|
||||
if (plot->addSubImage(width, height, image, atlasLocator)) {
|
||||
return this->updatePlot(atlasLocator, plot);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DrawAtlas::recordUploads(DrawContext* dc, Recorder* recorder) {
|
||||
TRACE_EVENT0("skia.gpu", TRACE_FUNC);
|
||||
for (uint32_t pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
|
||||
PlotList::Iter plotIter;
|
||||
plotIter.init(fPages[pageIdx].fPlotList, PlotList::Iter::kHead_IterStart);
|
||||
for (Plot* plot = plotIter.get(); plot; plot = plotIter.next()) {
|
||||
if (plot->needsUpload()) {
|
||||
TextureProxy* proxy = fProxies[pageIdx].get();
|
||||
SkASSERT(proxy);
|
||||
|
||||
const void* dataPtr;
|
||||
SkIRect dstRect;
|
||||
std::tie(dataPtr, dstRect) = plot->prepareForUpload();
|
||||
|
||||
std::vector<MipLevel> levels;
|
||||
levels.push_back({dataPtr, fBytesPerPixel*fPlotWidth});
|
||||
|
||||
if (!dc->recordUpload(recorder, sk_ref_sp(proxy), fColorType, levels, dstRect)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Number of atlas-related flushes beyond which we consider a plot to no longer be in use.
|
||||
//
|
||||
// This value is somewhat arbitrary -- the idea is to keep it low enough that
|
||||
// a page with unused plots will get removed reasonably quickly, but allow it
|
||||
// to hang around for a bit in case it's needed. The assumption is that flushes
|
||||
// are rare; i.e., we are not continually refreshing the frame.
|
||||
static constexpr auto kPlotRecentlyUsedCount = 32;
|
||||
static constexpr auto kAtlasRecentlyUsedCount = 128;
|
||||
|
||||
DrawAtlas::ErrorCode DrawAtlas::addToAtlas(Recorder* recorder,
|
||||
int width, int height, const void* image,
|
||||
AtlasLocator* atlasLocator) {
|
||||
if (width > fPlotWidth || height > fPlotHeight) {
|
||||
return ErrorCode::kError;
|
||||
}
|
||||
|
||||
// Look through each page to see if we can upload without having to flush
|
||||
// We prioritize this upload to the first pages, not the most recently used, to make it easier
|
||||
// to remove unused pages in reverse page order.
|
||||
for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
|
||||
if (this->addToPage(pageIdx, width, height, image, atlasLocator)) {
|
||||
return ErrorCode::kSucceeded;
|
||||
}
|
||||
}
|
||||
|
||||
// If the above fails, then see if the least recently used plot per page has already been
|
||||
// queued for upload if we're at max page allocation, or if the plot has aged out otherwise.
|
||||
// We wait until we've grown to the full number of pages to begin evicting already queued
|
||||
// plots so that we can maximize the opportunity for reuse.
|
||||
// As before we prioritize this upload to the first pages, not the most recently used.
|
||||
if (fNumActivePages == this->maxPages()) {
|
||||
for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
|
||||
Plot* plot = fPages[pageIdx].fPlotList.tail();
|
||||
SkASSERT(plot);
|
||||
if (plot->lastUseToken() < recorder->priv().tokenTracker()->nextTokenToFlush()) {
|
||||
this->processEvictionAndResetRects(plot);
|
||||
SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image, atlasLocator);
|
||||
SkASSERT(verify);
|
||||
if (!this->updatePlot(atlasLocator, plot)) {
|
||||
return ErrorCode::kError;
|
||||
}
|
||||
return ErrorCode::kSucceeded;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If we haven't activated all the available pages, try to create a new one and add to it
|
||||
if (!this->activateNewPage(recorder)) {
|
||||
return ErrorCode::kError;
|
||||
}
|
||||
|
||||
if (this->addToPage(fNumActivePages-1, width, height, image, atlasLocator)) {
|
||||
return ErrorCode::kSucceeded;
|
||||
} else {
|
||||
// If we fail to upload to a newly activated page then something has gone terribly
|
||||
// wrong - return an error
|
||||
return ErrorCode::kError;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fNumActivePages) {
|
||||
return ErrorCode::kError;
|
||||
}
|
||||
|
||||
// All plots are currently in use by the current set of draws, so we need to fail. This
|
||||
// gives the Device a chance to snap the current set of uploads and draws, advance the draw
|
||||
// token, and call back into this function. The subsequent call will have plots available
|
||||
// for fresh uploads.
|
||||
return ErrorCode::kTryAgain;
|
||||
}
|
||||
|
||||
void DrawAtlas::compact(DrawToken startTokenForNextFlush) {
|
||||
if (fNumActivePages < 1) {
|
||||
fPrevFlushToken = startTokenForNextFlush;
|
||||
return;
|
||||
}
|
||||
|
||||
// For all plots, reset number of flushes since used if used this frame.
|
||||
PlotList::Iter plotIter;
|
||||
bool atlasUsedThisFlush = false;
|
||||
for (uint32_t pageIndex = 0; pageIndex < fNumActivePages; ++pageIndex) {
|
||||
plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
|
||||
while (Plot* plot = plotIter.get()) {
|
||||
// Reset number of flushes since used
|
||||
if (plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
|
||||
plot->resetFlushesSinceLastUsed();
|
||||
atlasUsedThisFlush = true;
|
||||
}
|
||||
|
||||
plotIter.next();
|
||||
}
|
||||
}
|
||||
|
||||
if (atlasUsedThisFlush) {
|
||||
fFlushesSinceLastUse = 0;
|
||||
} else {
|
||||
++fFlushesSinceLastUse;
|
||||
}
|
||||
|
||||
// We only try to compact if the atlas was used in the recently completed flush or
|
||||
// hasn't been used in a long time.
|
||||
// This is to handle the case where a lot of text or path rendering has occurred but then just
|
||||
// a blinking cursor is drawn.
|
||||
if (atlasUsedThisFlush || fFlushesSinceLastUse > kAtlasRecentlyUsedCount) {
|
||||
SkTArray<Plot*> availablePlots;
|
||||
uint32_t lastPageIndex = fNumActivePages - 1;
|
||||
|
||||
// For all plots but the last one, update number of flushes since used, and check to see
|
||||
// if there are any in the first pages that the last page can safely upload to.
|
||||
for (uint32_t pageIndex = 0; pageIndex < lastPageIndex; ++pageIndex) {
|
||||
if constexpr (kDumpAtlasData) {
|
||||
SkDebugf("page %d: ", pageIndex);
|
||||
}
|
||||
|
||||
plotIter.init(fPages[pageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
|
||||
while (Plot* plot = plotIter.get()) {
|
||||
// Update number of flushes since plot was last used
|
||||
// We only increment the 'sinceLastUsed' count for flushes where the atlas was used
|
||||
// to avoid deleting everything when we return to text drawing in the blinking
|
||||
// cursor case
|
||||
if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
|
||||
plot->incFlushesSinceLastUsed();
|
||||
}
|
||||
|
||||
if constexpr (kDumpAtlasData) {
|
||||
SkDebugf("%d ", plot->flushesSinceLastUsed());
|
||||
}
|
||||
|
||||
// Count plots we can potentially upload to in all pages except the last one
|
||||
// (the potential compactee).
|
||||
if (plot->flushesSinceLastUsed() > kPlotRecentlyUsedCount) {
|
||||
availablePlots.push_back() = plot;
|
||||
}
|
||||
|
||||
plotIter.next();
|
||||
}
|
||||
|
||||
if constexpr (kDumpAtlasData) {
|
||||
SkDebugf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Count recently used plots in the last page and evict any that are no longer in use.
|
||||
// Since we prioritize uploading to the first pages, this will eventually
|
||||
// clear out usage of this page unless we have a large need.
|
||||
plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
|
||||
unsigned int usedPlots = 0;
|
||||
if constexpr (kDumpAtlasData) {
|
||||
SkDebugf("page %d: ", lastPageIndex);
|
||||
}
|
||||
while (Plot* plot = plotIter.get()) {
|
||||
// Update number of flushes since plot was last used
|
||||
if (!plot->lastUseToken().inInterval(fPrevFlushToken, startTokenForNextFlush)) {
|
||||
plot->incFlushesSinceLastUsed();
|
||||
}
|
||||
|
||||
if constexpr (kDumpAtlasData) {
|
||||
SkDebugf("%d ", plot->flushesSinceLastUsed());
|
||||
}
|
||||
|
||||
// If this plot was used recently
|
||||
if (plot->flushesSinceLastUsed() <= kPlotRecentlyUsedCount) {
|
||||
usedPlots++;
|
||||
} else if (plot->lastUseToken() != DrawToken::AlreadyFlushedToken()) {
|
||||
// otherwise if aged out just evict it.
|
||||
this->processEvictionAndResetRects(plot);
|
||||
}
|
||||
plotIter.next();
|
||||
}
|
||||
|
||||
if constexpr (kDumpAtlasData) {
|
||||
SkDebugf("\n");
|
||||
}
|
||||
|
||||
// If recently used plots in the last page are using less than a quarter of the page, try
|
||||
// to evict them if there's available space in lower index pages. Since we prioritize
|
||||
// uploading to the first pages, this will eventually clear out usage of this page unless
|
||||
// we have a large need.
|
||||
if (availablePlots.count() && usedPlots && usedPlots <= fNumPlots / 4) {
|
||||
plotIter.init(fPages[lastPageIndex].fPlotList, PlotList::Iter::kHead_IterStart);
|
||||
while (Plot* plot = plotIter.get()) {
|
||||
// If this plot was used recently
|
||||
if (plot->flushesSinceLastUsed() <= kPlotRecentlyUsedCount) {
|
||||
// See if there's room in an lower index page and if so evict.
|
||||
// We need to be somewhat harsh here so that a handful of plots that are
|
||||
// consistently in use don't end up locking the page in memory.
|
||||
if (availablePlots.count() > 0) {
|
||||
this->processEvictionAndResetRects(plot);
|
||||
this->processEvictionAndResetRects(availablePlots.back());
|
||||
availablePlots.pop_back();
|
||||
--usedPlots;
|
||||
}
|
||||
if (!usedPlots || !availablePlots.count()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
plotIter.next();
|
||||
}
|
||||
}
|
||||
|
||||
// If none of the plots in the last page have been used recently, delete it.
|
||||
if (!usedPlots) {
|
||||
if constexpr (kDumpAtlasData) {
|
||||
SkDebugf("delete %d\n", fNumActivePages-1);
|
||||
}
|
||||
|
||||
this->deactivateLastPage();
|
||||
fFlushesSinceLastUse = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fPrevFlushToken = startTokenForNextFlush;
|
||||
}
|
||||
|
||||
bool DrawAtlas::createPages(AtlasGenerationCounter* generationCounter) {
|
||||
SkASSERT(SkIsPow2(fTextureWidth) && SkIsPow2(fTextureHeight));
|
||||
|
||||
int numPlotsX = fTextureWidth/fPlotWidth;
|
||||
int numPlotsY = fTextureHeight/fPlotHeight;
|
||||
|
||||
for (uint32_t i = 0; i < this->maxPages(); ++i) {
|
||||
// Proxies are uncreated at first
|
||||
fProxies[i] = nullptr;
|
||||
|
||||
// set up allocated plots
|
||||
fPages[i].fPlotArray = std::make_unique<sk_sp<Plot>[]>(numPlotsX * numPlotsY);
|
||||
|
||||
sk_sp<Plot>* currPlot = fPages[i].fPlotArray.get();
|
||||
for (int y = numPlotsY - 1, r = 0; y >= 0; --y, ++r) {
|
||||
for (int x = numPlotsX - 1, c = 0; x >= 0; --x, ++c) {
|
||||
uint32_t plotIndex = r * numPlotsX + c;
|
||||
currPlot->reset(new Plot(
|
||||
i, plotIndex, generationCounter, x, y, fPlotWidth, fPlotHeight, fColorType,
|
||||
fBytesPerPixel));
|
||||
|
||||
// build LRU list
|
||||
fPages[i].fPlotList.addToHead(currPlot->get());
|
||||
++currPlot;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DrawAtlas::activateNewPage(Recorder* recorder) {
|
||||
SkASSERT(fNumActivePages < this->maxPages());
|
||||
SkASSERT(!fProxies[fNumActivePages]);
|
||||
|
||||
auto textureInfo = recorder->priv().caps()->getDefaultSampledTextureInfo(fColorType,
|
||||
/*levelCount=*/1,
|
||||
Protected::kNo,
|
||||
Renderable::kNo);
|
||||
fProxies[fNumActivePages].reset(new TextureProxy({fTextureWidth, fTextureHeight}, textureInfo,
|
||||
SkBudgeted::kYes));
|
||||
if (!fProxies[fNumActivePages]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if constexpr (kDumpAtlasData) {
|
||||
SkDebugf("activated page#: %d\n", fNumActivePages);
|
||||
}
|
||||
|
||||
++fNumActivePages;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void DrawAtlas::deactivateLastPage() {
|
||||
SkASSERT(fNumActivePages);
|
||||
|
||||
uint32_t lastPageIndex = fNumActivePages - 1;
|
||||
|
||||
int numPlotsX = fTextureWidth/fPlotWidth;
|
||||
int numPlotsY = fTextureHeight/fPlotHeight;
|
||||
|
||||
fPages[lastPageIndex].fPlotList.reset();
|
||||
for (int r = 0; r < numPlotsY; ++r) {
|
||||
for (int c = 0; c < numPlotsX; ++c) {
|
||||
uint32_t plotIndex = r * numPlotsX + c;
|
||||
|
||||
Plot* currPlot = fPages[lastPageIndex].fPlotArray[plotIndex].get();
|
||||
currPlot->resetRects();
|
||||
currPlot->resetFlushesSinceLastUsed();
|
||||
|
||||
// rebuild the LRU list
|
||||
SkDEBUGCODE(currPlot->resetListPtrs());
|
||||
fPages[lastPageIndex].fPlotList.addToHead(currPlot);
|
||||
}
|
||||
}
|
||||
|
||||
// remove ref to the texture proxy
|
||||
fProxies[lastPageIndex].reset();
|
||||
--fNumActivePages;
|
||||
}
|
||||
|
||||
DrawAtlasConfig::DrawAtlasConfig(int maxTextureSize, size_t maxBytes) {
|
||||
static const SkISize kARGBDimensions[] = {
|
||||
{256, 256}, // maxBytes < 2^19
|
||||
{512, 256}, // 2^19 <= maxBytes < 2^20
|
||||
{512, 512}, // 2^20 <= maxBytes < 2^21
|
||||
{1024, 512}, // 2^21 <= maxBytes < 2^22
|
||||
{1024, 1024}, // 2^22 <= maxBytes < 2^23
|
||||
{2048, 1024}, // 2^23 <= maxBytes
|
||||
};
|
||||
|
||||
// Index 0 corresponds to maxBytes of 2^18, so start by dividing it by that
|
||||
maxBytes >>= 18;
|
||||
// Take the floor of the log to get the index
|
||||
int index = maxBytes > 0
|
||||
? SkTPin<int>(SkPrevLog2(maxBytes), 0, SK_ARRAY_COUNT(kARGBDimensions) - 1)
|
||||
: 0;
|
||||
|
||||
SkASSERT(kARGBDimensions[index].width() <= kMaxAtlasDim);
|
||||
SkASSERT(kARGBDimensions[index].height() <= kMaxAtlasDim);
|
||||
fARGBDimensions.set(std::min<int>(kARGBDimensions[index].width(), maxTextureSize),
|
||||
std::min<int>(kARGBDimensions[index].height(), maxTextureSize));
|
||||
fMaxTextureSize = std::min<int>(maxTextureSize, kMaxAtlasDim);
|
||||
}
|
||||
|
||||
SkISize DrawAtlasConfig::atlasDimensions(MaskFormat type) const {
|
||||
if (MaskFormat::kA8 == type) {
|
||||
// A8 is always 2x the ARGB dimensions, clamped to the max allowed texture size
|
||||
return { std::min<int>(2 * fARGBDimensions.width(), fMaxTextureSize),
|
||||
std::min<int>(2 * fARGBDimensions.height(), fMaxTextureSize) };
|
||||
} else {
|
||||
return fARGBDimensions;
|
||||
}
|
||||
}
|
||||
|
||||
SkISize DrawAtlasConfig::plotDimensions(MaskFormat type) const {
|
||||
if (MaskFormat::kA8 == type) {
|
||||
SkISize atlasDimensions = this->atlasDimensions(type);
|
||||
// For A8 we want to grow the plots at larger texture sizes to accept more of the
|
||||
// larger SDF glyphs. Since the largest SDF glyph can be 170x170 with padding, this
|
||||
// allows us to pack 3 in a 512x256 plot, or 9 in a 512x512 plot.
|
||||
|
||||
// This will give us 512x256 plots for 2048x1024, 512x512 plots for 2048x2048,
|
||||
// and 256x256 plots otherwise.
|
||||
int plotWidth = atlasDimensions.width() >= 2048 ? 512 : 256;
|
||||
int plotHeight = atlasDimensions.height() >= 2048 ? 512 : 256;
|
||||
|
||||
return { plotWidth, plotHeight };
|
||||
} else {
|
||||
// ARGB and LCD always use 256x256 plots -- this has been shown to be faster
|
||||
return { 256, 256 };
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace skgpu::graphite
|
263
src/gpu/graphite/DrawAtlas.h
Normal file
263
src/gpu/graphite/DrawAtlas.h
Normal file
@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef skgpu_graphite_DrawAtlas_DEFINED
|
||||
#define skgpu_graphite_DrawAtlas_DEFINED
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "src/core/SkIPoint16.h"
|
||||
#include "src/gpu/AtlasTypes.h"
|
||||
#include "src/gpu/RectanizerSkyline.h"
|
||||
|
||||
namespace skgpu::graphite {
|
||||
|
||||
class DrawContext;
|
||||
class Recorder;
|
||||
class TextureProxy;
|
||||
|
||||
/**
|
||||
* TODO: the process described here is tentative, and this comment revised once locked down.
|
||||
*
|
||||
* This class manages one or more atlas textures on behalf of primitive draws in Device. The
|
||||
* drawing processes that use the atlas add preceding UploadTasks when generating RenderPassTasks.
|
||||
* The class provides facilities for using DrawTokens to detect data hazards. Plots that need
|
||||
* uploads are tracked until it is impossible to add data without overwriting texels read by draws
|
||||
* that have not yet been snapped to a RenderPassTask. At that point, the atlas will attempt to
|
||||
* allocate a new atlas texture (or "page") of the same size, up to a maximum number of textures,
|
||||
* and upload to that texture. If that's not possible, then the atlas will fail to add a subimage.
|
||||
* This gives the Device the chance to end the current draw, snap a RenderpassTask, and begin a new
|
||||
* one. Additional uploads will then succeed.
|
||||
*
|
||||
* When the atlas has multiple pages, new uploads are prioritized to the lower index pages, i.e.,
|
||||
* it will try to upload to page 0 before page 1 or 2. To keep the atlas from continually using
|
||||
* excess space, periodic garbage collection is needed to shift data from the higher index pages to
|
||||
* the lower ones, and then eventually remove any pages that are no longer in use. "In use" is
|
||||
* determined by using the DrawToken system: After a DrawPass is snapped a subarea of the page, or
|
||||
* "plot" is checked to see whether it was used in that DrawPass. If less than a quarter of the
|
||||
* plots have been used recently (within kPlotRecentlyUsedCount iterations) and there are available
|
||||
* plots in lower index pages, the higher index page will be deactivated, and its glyphs will
|
||||
* gradually migrate to other pages via the usual upload system.
|
||||
*
|
||||
* Garbage collection is initiated by the DrawAtlas's client via the compact() method.
|
||||
*/
|
||||
class DrawAtlas {
|
||||
public:
|
||||
/** Is the atlas allowed to use more than one texture? */
|
||||
enum class AllowMultitexturing : bool { kNo, kYes };
|
||||
|
||||
/**
|
||||
* Returns a DrawAtlas.
|
||||
* @param ct The colorType which this atlas will store.
|
||||
* @param bpp Size in bytes of each pixel.
|
||||
* @param width Width in pixels of the atlas.
|
||||
* @param height Height in pixels of the atlas.
|
||||
* @param plotWidth The width of each plot. width/plotWidth should be an integer.
|
||||
* @param plotWidth The height of each plot. height/plotHeight should be an integer.
|
||||
* @param atlasGeneration A pointer to the context's generation counter.
|
||||
* @param allowMultitexturing Can the atlas use more than one texture.
|
||||
* @param evictor A pointer to an eviction callback class.
|
||||
*
|
||||
* @return An initialized DrawAtlas, or nullptr if creation fails.
|
||||
*/
|
||||
static std::unique_ptr<DrawAtlas> Make(SkColorType ct, size_t bpp,
|
||||
int width, int height,
|
||||
int plotWidth, int plotHeight,
|
||||
AtlasGenerationCounter* generationCounter,
|
||||
AllowMultitexturing allowMultitexturing,
|
||||
PlotEvictionCallback* evictor,
|
||||
std::string_view label);
|
||||
|
||||
/**
|
||||
* TODO: the process described here is tentative, and this comment revised once locked down.
|
||||
*
|
||||
* Adds a width x height subimage to the atlas. Upon success it returns 'kSucceeded' and returns
|
||||
* the ID and the subimage's coordinates in the backing texture. 'kTryAgain' is returned if
|
||||
* the subimage cannot fit in the atlas without overwriting texels that will be read in the
|
||||
* current list of draws. This indicates that the Device should end its current draw, snap a
|
||||
* DrawPass, and begin another before adding more data. 'kError' will be returned when some
|
||||
* unrecoverable error was encountered while trying to add the subimage. In this case the draw
|
||||
* being created should be discarded.
|
||||
*
|
||||
* This tracking does not generate UploadTasks per se. Instead, when the RenderPassTask is
|
||||
* ready to be snapped, recordUploads() will be called by the Device and that will generate the
|
||||
* necessary UploadTasks.
|
||||
*
|
||||
* NOTE: When a draw that reads from the atlas is added to the DrawList, the client using this
|
||||
* DrawAtlas must immediately call 'setLastUseToken' with the currentToken from the Recorder,
|
||||
* otherwise the next call to addToAtlas might cause the previous data to be overwritten before
|
||||
* it has been read.
|
||||
*/
|
||||
|
||||
enum class ErrorCode {
|
||||
kError,
|
||||
kSucceeded,
|
||||
kTryAgain
|
||||
};
|
||||
|
||||
ErrorCode addToAtlas(Recorder*, int width, int height, const void* image, AtlasLocator*);
|
||||
bool recordUploads(DrawContext*, Recorder*);
|
||||
|
||||
const sk_sp<TextureProxy>* getProxies() const { return fProxies; }
|
||||
|
||||
uint64_t atlasGeneration() const { return fAtlasGeneration; }
|
||||
|
||||
bool hasID(const PlotLocator& plotLocator) {
|
||||
if (!plotLocator.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t plot = plotLocator.plotIndex();
|
||||
uint32_t page = plotLocator.pageIndex();
|
||||
uint64_t plotGeneration = fPages[page].fPlotArray[plot]->genID();
|
||||
uint64_t locatorGeneration = plotLocator.genID();
|
||||
return plot < fNumPlots && page < fNumActivePages && plotGeneration == locatorGeneration;
|
||||
}
|
||||
|
||||
/** To ensure the atlas does not evict a given entry, the client must set the last use token. */
|
||||
void setLastUseToken(const AtlasLocator& atlasLocator, DrawToken token) {
|
||||
SkASSERT(this->hasID(atlasLocator.plotLocator()));
|
||||
uint32_t plotIdx = atlasLocator.plotIndex();
|
||||
SkASSERT(plotIdx < fNumPlots);
|
||||
uint32_t pageIdx = atlasLocator.pageIndex();
|
||||
SkASSERT(pageIdx < fNumActivePages);
|
||||
Plot* plot = fPages[pageIdx].fPlotArray[plotIdx].get();
|
||||
this->makeMRU(plot, pageIdx);
|
||||
plot->setLastUseToken(token);
|
||||
}
|
||||
|
||||
uint32_t numActivePages() { return fNumActivePages; }
|
||||
|
||||
void setLastUseTokenBulk(const BulkUsePlotUpdater& updater,
|
||||
DrawToken token) {
|
||||
int count = updater.count();
|
||||
for (int i = 0; i < count; i++) {
|
||||
const BulkUsePlotUpdater::PlotData& pd = updater.plotData(i);
|
||||
// it's possible we've added a plot to the updater and subsequently the plot's page
|
||||
// was deleted -- so we check to prevent a crash
|
||||
if (pd.fPageIndex < fNumActivePages) {
|
||||
Plot* plot = fPages[pd.fPageIndex].fPlotArray[pd.fPlotIndex].get();
|
||||
this->makeMRU(plot, pd.fPageIndex);
|
||||
plot->setLastUseToken(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void compact(DrawToken startTokenForNextFlush);
|
||||
|
||||
uint32_t maxPages() const {
|
||||
return fMaxPages;
|
||||
}
|
||||
|
||||
int numAllocated_TestingOnly() const;
|
||||
void setMaxPages_TestingOnly(uint32_t maxPages);
|
||||
|
||||
private:
|
||||
DrawAtlas(SkColorType, size_t bpp, int width, int height, int plotWidth, int plotHeight,
|
||||
AtlasGenerationCounter* generationCounter,
|
||||
AllowMultitexturing allowMultitexturing, std::string_view label);
|
||||
|
||||
bool updatePlot(AtlasLocator*, Plot* plot);
|
||||
|
||||
inline void makeMRU(Plot* plot, int pageIdx) {
|
||||
if (fPages[pageIdx].fPlotList.head() == plot) {
|
||||
return;
|
||||
}
|
||||
|
||||
fPages[pageIdx].fPlotList.remove(plot);
|
||||
fPages[pageIdx].fPlotList.addToHead(plot);
|
||||
|
||||
// No MRU update for pages -- since we will always try to add from
|
||||
// the front and remove from the back there is no need for MRU.
|
||||
}
|
||||
|
||||
bool addToPage(unsigned int pageIdx, int width, int height, const void* image, AtlasLocator*);
|
||||
|
||||
bool createPages(AtlasGenerationCounter*);
|
||||
bool activateNewPage(Recorder*);
|
||||
void deactivateLastPage();
|
||||
|
||||
void processEviction(PlotLocator);
|
||||
inline void processEvictionAndResetRects(Plot* plot) {
|
||||
this->processEviction(plot->plotLocator());
|
||||
plot->resetRects();
|
||||
}
|
||||
|
||||
SkColorType fColorType;
|
||||
size_t fBytesPerPixel;
|
||||
int fTextureWidth;
|
||||
int fTextureHeight;
|
||||
int fPlotWidth;
|
||||
int fPlotHeight;
|
||||
unsigned int fNumPlots;
|
||||
const std::string fLabel;
|
||||
|
||||
// A counter to track the atlas eviction state for Glyphs. Each Glyph has a PlotLocator
|
||||
// which contains its current generation. When the atlas evicts a plot, it increases
|
||||
// the generation counter. If a Glyph's generation is less than the atlas's
|
||||
// generation, then it knows it's been evicted and is either free to be deleted or
|
||||
// re-added to the atlas if necessary.
|
||||
AtlasGenerationCounter* const fGenerationCounter;
|
||||
uint64_t fAtlasGeneration;
|
||||
|
||||
// nextTokenToFlush() value at the end of the previous DrawPass
|
||||
// TODO: rename
|
||||
DrawToken fPrevFlushToken;
|
||||
|
||||
// the number of flushes since this atlas has been last used
|
||||
// TODO: rename
|
||||
int fFlushesSinceLastUse;
|
||||
|
||||
std::vector<PlotEvictionCallback*> fEvictionCallbacks;
|
||||
|
||||
struct Page {
|
||||
// allocated array of Plots
|
||||
std::unique_ptr<sk_sp<Plot>[]> fPlotArray;
|
||||
// LRU list of Plots (MRU at head - LRU at tail)
|
||||
PlotList fPlotList;
|
||||
};
|
||||
// proxies kept separate to make it easier to pass them up to client
|
||||
sk_sp<TextureProxy> fProxies[PlotLocator::kMaxMultitexturePages];
|
||||
Page fPages[PlotLocator::kMaxMultitexturePages];
|
||||
uint32_t fMaxPages;
|
||||
|
||||
uint32_t fNumActivePages;
|
||||
|
||||
SkDEBUGCODE(void validate(const AtlasLocator& atlasLocator) const;)
|
||||
};
|
||||
|
||||
// For text there are three atlases (A8, 565, ARGB) that are kept in relation with one another. In
|
||||
// general, because A8 is the most frequently used mask format its dimensions are 2x the 565 and
|
||||
// ARGB dimensions, with the constraint that an atlas size will always contain at least one plot.
|
||||
// Since the ARGB atlas takes the most space, its dimensions are used to size the other two atlases.
|
||||
class DrawAtlasConfig {
|
||||
public:
|
||||
// The capabilities of the GPU define maxTextureSize. The client provides maxBytes, and this
|
||||
// represents the largest they want a single atlas texture to be. Due to multitexturing, we
|
||||
// may expand temporarily to use more space as needed.
|
||||
DrawAtlasConfig(int maxTextureSize, size_t maxBytes);
|
||||
|
||||
SkISize atlasDimensions(MaskFormat type) const;
|
||||
SkISize plotDimensions(MaskFormat type) const;
|
||||
|
||||
private:
|
||||
// On some systems texture coordinates are represented using half-precision floating point
|
||||
// with 11 significant bits, which limits the largest atlas dimensions to 2048x2048.
|
||||
// For simplicity we'll use this constraint for all of our atlas textures.
|
||||
// This can be revisited later if we need larger atlases.
|
||||
inline static constexpr int kMaxAtlasDim = 2048;
|
||||
|
||||
SkISize fARGBDimensions;
|
||||
int fMaxTextureSize;
|
||||
};
|
||||
|
||||
} // namespace skgpu::graphite
|
||||
|
||||
#endif
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "include/gpu/graphite/Recording.h"
|
||||
#include "src/core/SkPipelineData.h"
|
||||
#include "src/gpu/AtlasTypes.h"
|
||||
#include "src/gpu/graphite/Caps.h"
|
||||
#include "src/gpu/graphite/CommandBuffer.h"
|
||||
#include "src/gpu/graphite/ContextPriv.h"
|
||||
@ -20,6 +21,8 @@
|
||||
#include "src/gpu/graphite/ResourceProvider.h"
|
||||
#include "src/gpu/graphite/TaskGraph.h"
|
||||
#include "src/gpu/graphite/UploadBufferManager.h"
|
||||
#include "src/gpu/graphite/text/AtlasManager.h"
|
||||
#include "src/text/gpu/StrikeCache.h"
|
||||
|
||||
namespace skgpu::graphite {
|
||||
|
||||
@ -29,7 +32,13 @@ Recorder::Recorder(sk_sp<Gpu> gpu, sk_sp<GlobalCache> globalCache)
|
||||
: fGpu(std::move(gpu))
|
||||
, fGraph(new TaskGraph)
|
||||
, fUniformDataCache(new UniformDataCache)
|
||||
, fTextureDataCache(new TextureDataCache) {
|
||||
, fTextureDataCache(new TextureDataCache)
|
||||
// TODO: add config to control maxTextureBytes
|
||||
, fAtlasManager(std::make_unique<AtlasManager>(this, 2048*2048,
|
||||
DrawAtlas::AllowMultitexturing::kYes,
|
||||
false))
|
||||
, fTokenTracker(std::make_unique<TokenTracker>())
|
||||
, fStrikeCache(std::make_unique<sktext::gpu::StrikeCache>()) {
|
||||
|
||||
fResourceProvider = fGpu->makeResourceProvider(std::move(globalCache), this->singleOwner());
|
||||
fDrawBufferManager.reset(new DrawBufferManager(fResourceProvider.get(),
|
||||
@ -43,6 +52,9 @@ Recorder::~Recorder() {
|
||||
for (auto& device : fTrackedDevices) {
|
||||
device->abandonRecorder();
|
||||
}
|
||||
|
||||
// TODO: needed?
|
||||
fStrikeCache->freeAll();
|
||||
}
|
||||
|
||||
std::unique_ptr<Recording> Recorder::snap() {
|
||||
|
@ -44,6 +44,18 @@ UploadBufferManager* RecorderPriv::uploadBufferManager() const {
|
||||
return fRecorder->fUploadBufferManager.get();
|
||||
}
|
||||
|
||||
AtlasManager* RecorderPriv::atlasManager() {
|
||||
return fRecorder->fAtlasManager.get();
|
||||
}
|
||||
|
||||
TokenTracker* RecorderPriv::tokenTracker() {
|
||||
return fRecorder->fTokenTracker.get();
|
||||
}
|
||||
|
||||
sktext::gpu::StrikeCache* RecorderPriv::strikeCache() {
|
||||
return fRecorder->fStrikeCache.get();
|
||||
}
|
||||
|
||||
void RecorderPriv::add(sk_sp<Task> task) {
|
||||
ASSERT_SINGLE_OWNER
|
||||
fRecorder->fGraph->add(std::move(task));
|
||||
|
@ -21,6 +21,9 @@ public:
|
||||
TextureDataCache* textureDataCache() const;
|
||||
DrawBufferManager* drawBufferManager() const;
|
||||
UploadBufferManager* uploadBufferManager() const;
|
||||
AtlasManager* atlasManager();
|
||||
TokenTracker* tokenTracker();
|
||||
sktext::gpu::StrikeCache* strikeCache();
|
||||
const Caps* caps() const;
|
||||
sk_sp<const Caps> refCaps() const;
|
||||
|
||||
|
353
src/gpu/graphite/text/AtlasManager.cpp
Normal file
353
src/gpu/graphite/text/AtlasManager.cpp
Normal file
@ -0,0 +1,353 @@
|
||||
/*
|
||||
* Copyright 2022 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/text/AtlasManager.h"
|
||||
|
||||
#include "include/core/SkColorSpace.h"
|
||||
#include "include/gpu/graphite/Recorder.h"
|
||||
#include "src/codec/SkMasks.h"
|
||||
#include "src/core/SkAutoMalloc.h"
|
||||
#include "src/core/SkDistanceFieldGen.h"
|
||||
#include "src/gpu/graphite/DrawAtlas.h"
|
||||
#include "src/gpu/graphite/RecorderPriv.h"
|
||||
#include "src/gpu/graphite/TextureProxy.h"
|
||||
#include "src/text/gpu/Glyph.h"
|
||||
#include "src/text/gpu/GlyphVector.h"
|
||||
#include "src/text/gpu/StrikeCache.h"
|
||||
|
||||
using Glyph = sktext::gpu::Glyph;
|
||||
|
||||
namespace skgpu::graphite {
|
||||
|
||||
AtlasManager::AtlasManager(Recorder* recorder,
|
||||
size_t maxTextureBytes,
|
||||
DrawAtlas::AllowMultitexturing allowMultitexturing,
|
||||
bool supportBilerpAtlas)
|
||||
: fRecorder(recorder)
|
||||
, fAllowMultitexturing{allowMultitexturing}
|
||||
, fSupportBilerpAtlas{supportBilerpAtlas}
|
||||
, fAtlasConfig{recorder->priv().caps()->maxTextureSize(), maxTextureBytes} { }
|
||||
|
||||
AtlasManager::~AtlasManager() = default;
|
||||
|
||||
void AtlasManager::freeAll() {
|
||||
for (int i = 0; i < kMaskFormatCount; ++i) {
|
||||
fAtlases[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool AtlasManager::hasGlyph(MaskFormat format, Glyph* glyph) {
|
||||
SkASSERT(glyph);
|
||||
return this->getAtlas(format)->hasID(glyph->fAtlasLocator.plotLocator());
|
||||
}
|
||||
|
||||
template <typename INT_TYPE>
|
||||
static void expand_bits(INT_TYPE* dst,
|
||||
const uint8_t* src,
|
||||
int width,
|
||||
int height,
|
||||
int dstRowBytes,
|
||||
int srcRowBytes) {
|
||||
for (int y = 0; y < height; ++y) {
|
||||
int rowWritesLeft = width;
|
||||
const uint8_t* s = src;
|
||||
INT_TYPE* d = dst;
|
||||
while (rowWritesLeft > 0) {
|
||||
unsigned mask = *s++;
|
||||
for (int x = 7; x >= 0 && rowWritesLeft; --x, --rowWritesLeft) {
|
||||
*d++ = (mask & (1 << x)) ? (INT_TYPE)(~0UL) : 0;
|
||||
}
|
||||
}
|
||||
dst = reinterpret_cast<INT_TYPE*>(reinterpret_cast<intptr_t>(dst) + dstRowBytes);
|
||||
src += srcRowBytes;
|
||||
}
|
||||
}
|
||||
|
||||
static void get_packed_glyph_image(
|
||||
const SkGlyph& glyph, int dstRB, MaskFormat expectedMaskFormat, void* dst) {
|
||||
const int width = glyph.width();
|
||||
const int height = glyph.height();
|
||||
const void* src = glyph.image();
|
||||
SkASSERT(src != nullptr);
|
||||
|
||||
MaskFormat maskFormat = Glyph::FormatFromSkGlyph(glyph.maskFormat());
|
||||
if (maskFormat == expectedMaskFormat) {
|
||||
int srcRB = glyph.rowBytes();
|
||||
// Notice this comparison is with the glyphs raw mask format, and not its MaskFormat.
|
||||
if (glyph.maskFormat() != SkMask::kBW_Format) {
|
||||
if (srcRB != dstRB) {
|
||||
const int bbp = MaskFormatBytesPerPixel(expectedMaskFormat);
|
||||
for (int y = 0; y < height; y++) {
|
||||
memcpy(dst, src, width * bbp);
|
||||
src = (const char*) src + srcRB;
|
||||
dst = (char*) dst + dstRB;
|
||||
}
|
||||
} else {
|
||||
memcpy(dst, src, dstRB * height);
|
||||
}
|
||||
} else {
|
||||
// Handle 8-bit format by expanding the mask to the expected format.
|
||||
const uint8_t* bits = reinterpret_cast<const uint8_t*>(src);
|
||||
switch (expectedMaskFormat) {
|
||||
case MaskFormat::kA8: {
|
||||
uint8_t* bytes = reinterpret_cast<uint8_t*>(dst);
|
||||
expand_bits(bytes, bits, width, height, dstRB, srcRB);
|
||||
break;
|
||||
}
|
||||
case MaskFormat::kA565: {
|
||||
uint16_t* rgb565 = reinterpret_cast<uint16_t*>(dst);
|
||||
expand_bits(rgb565, bits, width, height, dstRB, srcRB);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
SK_ABORT("Invalid MaskFormat");
|
||||
}
|
||||
}
|
||||
} else if (maskFormat == MaskFormat::kA565 &&
|
||||
expectedMaskFormat == MaskFormat::kARGB) {
|
||||
// Convert if the glyph uses a 565 mask format since it is using LCD text rendering
|
||||
// but the expected format is 8888 (will happen on Intel MacOS with Metal since that
|
||||
// combination does not support 565).
|
||||
static constexpr SkMasks masks{
|
||||
{0b1111'1000'0000'0000, 11, 5}, // Red
|
||||
{0b0000'0111'1110'0000, 5, 6}, // Green
|
||||
{0b0000'0000'0001'1111, 0, 5}, // Blue
|
||||
{0, 0, 0} // Alpha
|
||||
};
|
||||
constexpr int a565Bpp = MaskFormatBytesPerPixel(MaskFormat::kA565);
|
||||
constexpr int argbBpp = MaskFormatBytesPerPixel(MaskFormat::kARGB);
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
uint16_t color565 = 0;
|
||||
memcpy(&color565, src, a565Bpp);
|
||||
// TODO: create Graphite version of GrColorPackRGBA?
|
||||
uint32_t colorRGBA = masks.getRed(color565) |
|
||||
(masks.getGreen(color565) << 8) |
|
||||
(masks.getBlue(color565) << 16) |
|
||||
(0xFF << 24);
|
||||
memcpy(dst, &colorRGBA, argbBpp);
|
||||
src = (char*)src + a565Bpp;
|
||||
dst = (char*)dst + argbBpp;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SkUNREACHABLE;
|
||||
}
|
||||
}
|
||||
|
||||
MaskFormat AtlasManager::resolveMaskFormat(MaskFormat format) const {
|
||||
if (MaskFormat::kA565 == format &&
|
||||
!fRecorder->priv().caps()->getDefaultSampledTextureInfo(kRGB_565_SkColorType, 1,
|
||||
Protected::kNo,
|
||||
Renderable::kNo).isValid()) {
|
||||
format = MaskFormat::kARGB;
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
// Returns kSucceeded if glyph successfully added to texture atlas, kTryAgain if a RenderPassTask
|
||||
// needs to be snapped before adding the glyph, and kError if it can't be added at all.
|
||||
DrawAtlas::ErrorCode AtlasManager::addGlyphToAtlas(const SkGlyph& skGlyph,
|
||||
Glyph* glyph,
|
||||
int srcPadding) {
|
||||
SkASSERT(0 <= srcPadding && srcPadding <= SK_DistanceFieldInset);
|
||||
|
||||
if (skGlyph.image() == nullptr) {
|
||||
return DrawAtlas::ErrorCode::kError;
|
||||
}
|
||||
SkASSERT(glyph != nullptr);
|
||||
|
||||
MaskFormat glyphFormat = Glyph::FormatFromSkGlyph(skGlyph.maskFormat());
|
||||
MaskFormat expectedMaskFormat = this->resolveMaskFormat(glyphFormat);
|
||||
int bytesPerPixel = MaskFormatBytesPerPixel(expectedMaskFormat);
|
||||
|
||||
int padding;
|
||||
switch (srcPadding) {
|
||||
case 0:
|
||||
// The direct mask/image case.
|
||||
padding = 0;
|
||||
if (fSupportBilerpAtlas) {
|
||||
// Force direct masks (glyph with no padding) to have padding.
|
||||
padding = 1;
|
||||
srcPadding = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
// The transformed mask/image case.
|
||||
padding = 1;
|
||||
break;
|
||||
case SK_DistanceFieldInset:
|
||||
// The SDFT case.
|
||||
// If the srcPadding == SK_DistanceFieldInset (SDFT case) then the padding is built
|
||||
// into the image on the glyph; no extra padding needed.
|
||||
// TODO: can the SDFT glyph image in the cache be reduced by the padding?
|
||||
padding = 0;
|
||||
break;
|
||||
default:
|
||||
// The padding is not one of the know forms.
|
||||
return DrawAtlas::ErrorCode::kError;
|
||||
}
|
||||
|
||||
const int width = skGlyph.width() + 2*padding;
|
||||
const int height = skGlyph.height() + 2*padding;
|
||||
int rowBytes = width * bytesPerPixel;
|
||||
size_t size = height * rowBytes;
|
||||
|
||||
// Temporary storage for normalizing glyph image.
|
||||
SkAutoSMalloc<1024> storage(size);
|
||||
void* dataPtr = storage.get();
|
||||
if (padding > 0) {
|
||||
sk_bzero(dataPtr, size);
|
||||
// Advance in one row and one column.
|
||||
dataPtr = (char*)(dataPtr) + rowBytes + bytesPerPixel;
|
||||
}
|
||||
|
||||
get_packed_glyph_image(skGlyph, rowBytes, expectedMaskFormat, dataPtr);
|
||||
|
||||
DrawAtlas* atlas = this->getAtlas(expectedMaskFormat);
|
||||
auto errorCode = atlas->addToAtlas(fRecorder,
|
||||
width,
|
||||
height,
|
||||
storage.get(),
|
||||
&glyph->fAtlasLocator);
|
||||
|
||||
if (errorCode == DrawAtlas::ErrorCode::kSucceeded) {
|
||||
glyph->fAtlasLocator.insetSrc(srcPadding);
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
bool AtlasManager::recordUploads(DrawContext* dc) {
|
||||
for (int i = 0; i < skgpu::kMaskFormatCount; i++) {
|
||||
if (fAtlases[i] && !fAtlases[i]->recordUploads(dc, fRecorder)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AtlasManager::addGlyphToBulkAndSetUseToken(BulkUsePlotUpdater* updater,
|
||||
MaskFormat format, Glyph* glyph,
|
||||
DrawToken token) {
|
||||
SkASSERT(glyph);
|
||||
if (updater->add(glyph->fAtlasLocator)) {
|
||||
this->getAtlas(format)->setLastUseToken(glyph->fAtlasLocator, token);
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasManager::setAtlasDimensionsToMinimum_ForTesting() {
|
||||
// Delete any old atlases.
|
||||
// This should be safe to do as long as we are not in the middle of a flush.
|
||||
for (int i = 0; i < skgpu::kMaskFormatCount; i++) {
|
||||
fAtlases[i] = nullptr;
|
||||
}
|
||||
|
||||
// Set all the atlas sizes to 1x1 plot each.
|
||||
new (&fAtlasConfig) DrawAtlasConfig{2048, 0};
|
||||
}
|
||||
|
||||
bool AtlasManager::initAtlas(MaskFormat format) {
|
||||
int index = MaskFormatToAtlasIndex(format);
|
||||
if (fAtlases[index] == nullptr) {
|
||||
SkColorType colorType = MaskFormatToColorType(format);
|
||||
SkISize atlasDimensions = fAtlasConfig.atlasDimensions(format);
|
||||
SkISize plotDimensions = fAtlasConfig.plotDimensions(format);
|
||||
fAtlases[index] = DrawAtlas::Make(colorType,
|
||||
SkColorTypeBytesPerPixel(colorType),
|
||||
atlasDimensions.width(), atlasDimensions.height(),
|
||||
plotDimensions.width(), plotDimensions.height(),
|
||||
this,
|
||||
fAllowMultitexturing,
|
||||
nullptr,
|
||||
/*label=*/"TextAtlas");
|
||||
if (!fAtlases[index]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace skgpu::graphite
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace sktext::gpu {
|
||||
|
||||
using DrawAtlas = skgpu::graphite::DrawAtlas;
|
||||
|
||||
std::tuple<bool, int> GlyphVector::regenerateAtlas(int begin, int end,
|
||||
skgpu::MaskFormat maskFormat,
|
||||
int srcPadding,
|
||||
skgpu::graphite::Recorder* recorder) {
|
||||
auto atlasManager = recorder->priv().atlasManager();
|
||||
auto tokenTracker = recorder->priv().tokenTracker();
|
||||
|
||||
// TODO: this is not a great place for this -- need a better way to init atlases when needed
|
||||
unsigned int numActiveProxies;
|
||||
const sk_sp<skgpu::graphite::TextureProxy>* proxies =
|
||||
atlasManager->getProxies(maskFormat, &numActiveProxies);
|
||||
if (!proxies) {
|
||||
SkDebugf("Could not allocate backing texture for atlas\n");
|
||||
return {false, 0};
|
||||
}
|
||||
|
||||
uint64_t currentAtlasGen = atlasManager->atlasGeneration(maskFormat);
|
||||
|
||||
this->packedGlyphIDToGlyph(recorder->priv().strikeCache());
|
||||
|
||||
if (fAtlasGeneration != currentAtlasGen) {
|
||||
// Calculate the texture coordinates for the vertexes during first use (fAtlasGeneration
|
||||
// is set to kInvalidAtlasGeneration) or the atlas has changed in subsequent calls..
|
||||
fBulkUseUpdater.reset();
|
||||
|
||||
SkBulkGlyphMetricsAndImages metricsAndImages{fTextStrike->strikeSpec()};
|
||||
|
||||
// Update the atlas information in the GrStrike.
|
||||
auto glyphs = fGlyphs.subspan(begin, end - begin);
|
||||
int glyphsPlacedInAtlas = 0;
|
||||
bool success = true;
|
||||
for (const Variant& variant : glyphs) {
|
||||
Glyph* gpuGlyph = variant.glyph;
|
||||
SkASSERT(gpuGlyph != nullptr);
|
||||
|
||||
if (!atlasManager->hasGlyph(maskFormat, gpuGlyph)) {
|
||||
const SkGlyph& skGlyph = *metricsAndImages.glyph(gpuGlyph->fPackedID);
|
||||
auto code = atlasManager->addGlyphToAtlas(skGlyph, gpuGlyph, srcPadding);
|
||||
if (code != DrawAtlas::ErrorCode::kSucceeded) {
|
||||
success = code != DrawAtlas::ErrorCode::kError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
atlasManager->addGlyphToBulkAndSetUseToken(
|
||||
&fBulkUseUpdater, maskFormat, gpuGlyph,
|
||||
tokenTracker->nextDrawToken());
|
||||
glyphsPlacedInAtlas++;
|
||||
}
|
||||
|
||||
// Update atlas generation if there are no more glyphs to put in the atlas.
|
||||
if (success && begin + glyphsPlacedInAtlas == SkCount(fGlyphs)) {
|
||||
// Need to get the freshest value of the atlas' generation because
|
||||
// updateTextureCoordinates may have changed it.
|
||||
fAtlasGeneration = atlasManager->atlasGeneration(maskFormat);
|
||||
}
|
||||
|
||||
return {success, glyphsPlacedInAtlas};
|
||||
} else {
|
||||
// The atlas hasn't changed, so our texture coordinates are still valid.
|
||||
if (end == SkCount(fGlyphs)) {
|
||||
// The atlas hasn't changed and the texture coordinates are all still valid. Update
|
||||
// all the plots used to the new use token.
|
||||
atlasManager->setUseTokenBulk(fBulkUseUpdater,
|
||||
tokenTracker->nextDrawToken(),
|
||||
maskFormat);
|
||||
}
|
||||
return {true, end - begin};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sktext::gpu
|
118
src/gpu/graphite/text/AtlasManager.h
Normal file
118
src/gpu/graphite/text/AtlasManager.h
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef skgpu_graphite_AtlasManager_DEFINED
|
||||
#define skgpu_graphite_AtlasManager_DEFINED
|
||||
|
||||
#include "include/gpu/graphite/TextureInfo.h"
|
||||
#include "src/gpu/AtlasTypes.h"
|
||||
#include "src/gpu/graphite/Caps.h"
|
||||
#include "src/gpu/graphite/DrawAtlas.h"
|
||||
|
||||
namespace sktext::gpu {
|
||||
class Glyph;
|
||||
}
|
||||
class SkGlyph;
|
||||
|
||||
namespace skgpu::graphite {
|
||||
|
||||
class DrawContext;
|
||||
class Recorder;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/** The AtlasManager manages the lifetime of and access to DrawAtlases.
|
||||
*/
|
||||
class AtlasManager : public AtlasGenerationCounter {
|
||||
public:
|
||||
AtlasManager(Recorder*,
|
||||
size_t maxTextureBytes,
|
||||
DrawAtlas::AllowMultitexturing,
|
||||
bool supportBilerpAtlas);
|
||||
~AtlasManager();
|
||||
|
||||
// Used to get proxies for test rendering
|
||||
const sk_sp<TextureProxy>* getProxies(MaskFormat format,
|
||||
unsigned int* numActiveProxies) {
|
||||
format = this->resolveMaskFormat(format);
|
||||
if (this->initAtlas(format)) {
|
||||
*numActiveProxies = this->getAtlas(format)->numActivePages();
|
||||
return this->getAtlas(format)->getProxies();
|
||||
}
|
||||
*numActiveProxies = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void freeAll();
|
||||
|
||||
bool hasGlyph(MaskFormat, sktext::gpu::Glyph*);
|
||||
|
||||
DrawAtlas::ErrorCode addGlyphToAtlas(const SkGlyph&,
|
||||
sktext::gpu::Glyph*,
|
||||
int srcPadding);
|
||||
|
||||
// To ensure the DrawAtlas does not evict the Glyph Mask from its texture backing store,
|
||||
// the client must pass in the current draw token along with the sktext::gpu::Glyph.
|
||||
// A BulkUsePlotUpdater is used to manage bulk last use token updating in the Atlas.
|
||||
// For convenience, this function will also set the use token for the current glyph if required
|
||||
// NOTE: the bulk uploader is only valid if the subrun has a valid atlasGeneration
|
||||
void addGlyphToBulkAndSetUseToken(BulkUsePlotUpdater*, MaskFormat,
|
||||
sktext::gpu::Glyph*, DrawToken);
|
||||
|
||||
void setUseTokenBulk(const BulkUsePlotUpdater& updater,
|
||||
DrawToken token,
|
||||
MaskFormat format) {
|
||||
this->getAtlas(format)->setLastUseTokenBulk(updater, token);
|
||||
}
|
||||
|
||||
bool recordUploads(DrawContext* dc);
|
||||
|
||||
// Some clients may wish to verify the integrity of the texture backing store of the
|
||||
// GrDrawOpAtlas. The atlasGeneration returned below is a monotonically increasing number which
|
||||
// changes every time something is removed from the texture backing store.
|
||||
uint64_t atlasGeneration(skgpu::MaskFormat format) const {
|
||||
return this->getAtlas(format)->atlasGeneration();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Functions intended debug only
|
||||
|
||||
void setAtlasDimensionsToMinimum_ForTesting();
|
||||
void setMaxPages_TestingOnly(uint32_t maxPages);
|
||||
|
||||
private:
|
||||
bool initAtlas(MaskFormat);
|
||||
// Change an expected 565 mask format to 8888 if 565 is not supported (will happen when using
|
||||
// Metal on Intel MacOS). The actual conversion of the data is handled in
|
||||
// get_packed_glyph_image() in StrikeCache.cpp
|
||||
MaskFormat resolveMaskFormat(MaskFormat format) const;
|
||||
|
||||
// There is a 1:1 mapping between skgpu::MaskFormats and atlas indices
|
||||
static int MaskFormatToAtlasIndex(skgpu::MaskFormat format) {
|
||||
return static_cast<int>(format);
|
||||
}
|
||||
static skgpu::MaskFormat AtlasIndexToMaskFormat(int idx) {
|
||||
return static_cast<skgpu::MaskFormat>(idx);
|
||||
}
|
||||
|
||||
DrawAtlas* getAtlas(skgpu::MaskFormat format) const {
|
||||
format = this->resolveMaskFormat(format);
|
||||
int atlasIndex = MaskFormatToAtlasIndex(format);
|
||||
SkASSERT(fAtlases[atlasIndex]);
|
||||
return fAtlases[atlasIndex].get();
|
||||
}
|
||||
|
||||
Recorder* fRecorder;
|
||||
DrawAtlas::AllowMultitexturing fAllowMultitexturing;
|
||||
std::unique_ptr<DrawAtlas> fAtlases[kMaskFormatCount];
|
||||
static_assert(kMaskFormatCount == 3);
|
||||
bool fSupportBilerpAtlas;
|
||||
DrawAtlasConfig fAtlasConfig;
|
||||
};
|
||||
|
||||
} // namespace skgpu::graphite
|
||||
|
||||
#endif // skgpu_graphite_AtlasManager_DEFINED
|
@ -20,6 +20,9 @@ class SkStrikeClient;
|
||||
#if SK_SUPPORT_GPU
|
||||
class GrMeshDrawTarget;
|
||||
#endif
|
||||
#if defined(SK_GRAPHITE_ENABLED)
|
||||
namespace skgpu::graphite { class Recorder; }
|
||||
#endif
|
||||
|
||||
namespace sktext::gpu {
|
||||
|
||||
@ -63,6 +66,14 @@ public:
|
||||
GrMeshDrawTarget*);
|
||||
#endif
|
||||
|
||||
#if defined(SK_GRAPHITE_ENABLED)
|
||||
std::tuple<bool, int> regenerateAtlas(
|
||||
int begin, int end,
|
||||
skgpu::MaskFormat maskFormat,
|
||||
int srcPadding,
|
||||
skgpu::graphite::Recorder*);
|
||||
#endif
|
||||
|
||||
static size_t GlyphVectorSize(size_t count) {
|
||||
return sizeof(Variant) * count;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user