[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:
Jim Van Verth 2022-06-14 08:52:15 -04:00 committed by SkCQ
parent b0912790c6
commit 265b53bc76
11 changed files with 1289 additions and 1 deletions

View File

@ -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 = [

View File

@ -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?

View File

@ -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();

View 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

View 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

View File

@ -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() {

View File

@ -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));

View File

@ -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;

View 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

View 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

View File

@ -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;
}