blob: 9c2e7bb05b41611151c630323570c3589ab11bf0 [file] [log] [blame]
* Copyright 2018 Google Inc.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
#include "src/core/SkRemoteGlyphCache.h"
#include <bitset>
#include <iterator>
#include <memory>
#include <new>
#include <string>
#include <tuple>
#include "include/core/SkSerialProcs.h"
#include "include/core/SkSpan.h"
#include "include/core/SkTypeface.h"
#include "include/private/SkChecksum.h"
#include "include/private/SkTHash.h"
#include "src/core/SkDevice.h"
#include "src/core/SkDraw.h"
#include "src/core/SkEnumerate.h"
#include "src/core/SkGlyphRun.h"
#include "src/core/SkScalerCache.h"
#include "src/core/SkStrikeCache.h"
#include "src/core/SkStrikeForGPU.h"
#include "src/core/SkTLazy.h"
#include "src/core/SkTraceEvent.h"
#include "src/core/SkTypeface_remote.h"
#include "include/gpu/GrContextOptions.h"
#include "src/gpu/GrDrawOpAtlas.h"
#include "src/gpu/text/GrSDFTControl.h"
static SkDescriptor* auto_descriptor_from_desc(const SkDescriptor* source_desc,
SkFontID font_id,
SkAutoDescriptor* ad) {
auto* desc = ad->getDesc();
// Rec.
uint32_t size;
auto ptr = source_desc->findEntry(kRec_SkDescriptorTag, &size);
SkScalerContextRec rec;
std::memcpy((void*)&rec, ptr, size);
rec.fFontID = font_id;
desc->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec);
// Effects.
uint32_t size;
auto ptr = source_desc->findEntry(kEffects_SkDescriptorTag, &size);
if (ptr) { desc->addEntry(kEffects_SkDescriptorTag, size, ptr); }
return desc;
static const SkDescriptor* create_descriptor(
const SkPaint& paint, const SkFont& font, const SkMatrix& m,
const SkSurfaceProps& props, SkScalerContextFlags flags,
SkAutoDescriptor* ad, SkScalerContextEffects* effects) {
SkScalerContextRec rec;
SkScalerContext::MakeRecAndEffects(font, paint, props, flags, m, &rec, effects);
return SkScalerContext::AutoDescriptorGivenRecAndEffects(rec, *effects, ad);
// -- Serializer -----------------------------------------------------------------------------------
size_t pad(size_t size, size_t alignment) { return (size + (alignment - 1)) & ~(alignment - 1); }
// Alignment between x86 and x64 differs for some types, in particular
// int64_t and doubles have 4 and 8-byte alignment, respectively.
// Be consistent even when writing and reading across different architectures.
template<typename T>
size_t serialization_alignment() {
return sizeof(T) == 8 ? 8 : alignof(T);
class Serializer {
explicit Serializer(std::vector<uint8_t>* buffer) : fBuffer{buffer} {}
template <typename T, typename... Args>
T* emplace(Args&&... args) {
auto result = allocate(sizeof(T), serialization_alignment<T>());
return new (result) T{std::forward<Args>(args)...};
template <typename T>
void write(const T& data) {
T* result = (T*)allocate(sizeof(T), serialization_alignment<T>());
memcpy(result, &data, sizeof(T));
template <typename T>
T* allocate() {
T* result = (T*)allocate(sizeof(T), serialization_alignment<T>());
return result;
void writeDescriptor(const SkDescriptor& desc) {
auto result = allocate(desc.getLength(), alignof(SkDescriptor));
memcpy(result, &desc, desc.getLength());
void* allocate(size_t size, size_t alignment) {
size_t aligned = pad(fBuffer->size(), alignment);
fBuffer->resize(aligned + size);
return &(*fBuffer)[aligned];
std::vector<uint8_t>* fBuffer;
// -- Deserializer -------------------------------------------------------------------------------
// Note that the Deserializer is reading untrusted data, we need to guard against invalid data.
class Deserializer {
Deserializer(const volatile char* memory, size_t memorySize)
: fMemory(memory), fMemorySize(memorySize) {}
template <typename T>
bool read(T* val) {
auto* result = this->ensureAtLeast(sizeof(T), serialization_alignment<T>());
if (!result) return false;
memcpy(val, const_cast<const char*>(result), sizeof(T));
return true;
bool readDescriptor(SkAutoDescriptor* ad) {
uint32_t descLength = 0u;
if (!read<uint32_t>(&descLength)) return false;
if (descLength < sizeof(SkDescriptor)) return false;
if (descLength != SkAlign4(descLength)) return false;
auto* result = this->ensureAtLeast(descLength, alignof(SkDescriptor));
if (!result) return false;
memcpy(ad->getDesc(), const_cast<const char*>(result), descLength);
if (ad->getDesc()->getLength() > descLength) return false;
return ad->getDesc()->isValid();
const volatile void* read(size_t size, size_t alignment) {
return this->ensureAtLeast(size, alignment);
size_t bytesRead() const { return fBytesRead; }
const volatile char* ensureAtLeast(size_t size, size_t alignment) {
size_t padded = pad(fBytesRead, alignment);
// Not enough data.
if (padded > fMemorySize) return nullptr;
if (size > fMemorySize - padded) return nullptr;
auto* result = fMemory + padded;
fBytesRead = padded + size;
return result;
// Note that we read each piece of memory only once to guard against TOCTOU violations.
const volatile char* fMemory;
size_t fMemorySize;
size_t fBytesRead = 0u;
bool SkFuzzDeserializeSkDescriptor(sk_sp<SkData> bytes, SkAutoDescriptor* ad) {
auto d = Deserializer(reinterpret_cast<const volatile char*>(bytes->data()), bytes->size());
return d.readDescriptor(ad);
// Paths use a SkWriter32 which requires 4 byte alignment.
static const size_t kPathAlignment = 4u;
// -- StrikeSpec -----------------------------------------------------------------------------------
struct StrikeSpec {
StrikeSpec() = default;
StrikeSpec(SkFontID typefaceID_, SkDiscardableHandleId discardableHandleId_)
: typefaceID{typefaceID_}, discardableHandleId(discardableHandleId_) {}
SkFontID typefaceID = 0u;
SkDiscardableHandleId discardableHandleId = 0u;
/* desc */
/* n X (glyphs ids) */
// Represent a set of x sub-pixel-position glyphs with a glyph id < kMaxGlyphID and
// y sub-pxiel-position must be 0. Most sub-pixel-positioned glyphs have been x-axis aligned
// forcing the y sub-pixel position to be zero. We can organize the SkPackedGlyphID to check that
// the glyph id and the y position == 0 with a single compare in the following way:
// <y-sub-pixel-position>:2 | <glyphid:16> | <x-sub-pixel-position>:2
// This organization allows a single check of a packed-id to be:
// packed-id < kMaxGlyphID * possible-x-sub-pixel-positions
// where possible-x-sub-pixel-positions == 4.
class LowerRangeBitVector {
bool test(SkPackedGlyphID packedID) const {
uint32_t bit = packedID.value();
return bit < kMaxIndex && fBits.test(bit);
void setIfLower(SkPackedGlyphID packedID) {
uint32_t bit = packedID.value();
if (bit < kMaxIndex) {
using GID = SkPackedGlyphID;
static_assert(GID::kSubPixelX < GID::kGlyphID && GID::kGlyphID < GID::kSubPixelY,
"SkPackedGlyphID must be organized: sub-y | glyph id | sub-x");
static constexpr int kMaxGlyphID = 128;
static constexpr int kMaxIndex = kMaxGlyphID * (1u << GID::kSubPixelPosLen);
std::bitset<kMaxIndex> fBits;
// -- MapOps ---------------------------------------------------------------------------------------
struct MapOps {
size_t operator()(const SkDescriptor* key) const;
bool operator()(const SkDescriptor* lhs, const SkDescriptor* rhs) const;
size_t MapOps::operator()(const SkDescriptor* key) const {
return key->getChecksum();
bool MapOps::operator()(const SkDescriptor* lhs, const SkDescriptor* rhs) const {
return *lhs == *rhs;
// -- RemoteStrike ----------------------------------------------------------------------------
class RemoteStrike final : public SkStrikeForGPU {
// N.B. RemoteStrike is not valid until ensureScalerContext is called.
RemoteStrike(const SkDescriptor& descriptor,
std::unique_ptr<SkScalerContext> context,
SkDiscardableHandleId discardableHandleId);
~RemoteStrike() override = default;
void writePendingGlyphs(Serializer* serializer);
SkDiscardableHandleId discardableHandleId() const { return fDiscardableHandleId; }
const SkDescriptor& getDescriptor() const override {
return *fDescriptor.getDesc();
void setTypefaceAndEffects(const SkTypeface* typeface, SkScalerContextEffects effects);
const SkGlyphPositionRoundingSpec& roundingSpec() const override {
return fRoundingSpec;
void prepareForMaskDrawing(
SkDrawableGlyphBuffer* drawables, SkSourceGlyphBuffer* rejects) override;
void prepareForSDFTDrawing(
SkDrawableGlyphBuffer* drawables, SkSourceGlyphBuffer* rejects) override;
void prepareForPathDrawing(
SkDrawableGlyphBuffer* drawables, SkSourceGlyphBuffer* rejects) override;
void onAboutToExitScope() override {}
bool hasPendingGlyphs() const {
return !fMasksToSend.empty() || !fPathsToSend.empty();
void resetScalerContext();
template <typename Rejector>
void commonMaskLoop(
SkDrawableGlyphBuffer* drawables, SkSourceGlyphBuffer* rejects, Rejector&& reject);
// Keep track of if the glyph draw has been totally satisfied. It could be that this
// strike can not draw the glyph, and it must be rejected to be handled by fallback.
// For example, if a glyph has canDrawAsMask sent, then that data is on the GPU, and this
// strike totally satisfies this result. If canDrawAsMask is false, then this glyph must be
// rejected, and handled by a later stage using a latter strike.
struct MaskSummary {
static_assert(SkPackedGlyphID::kMaskAll < (1u << 30), "SkPackedGlyphID is too big.");
uint32_t packedID:30;
uint32_t canDrawAsMask:1;
uint32_t canDrawAsSDFT:1;
struct MaskSummaryTraits {
static SkPackedGlyphID GetKey(MaskSummary summary) {
return SkPackedGlyphID{summary.packedID};
static uint32_t Hash(SkPackedGlyphID packedID) {
return packedID.hash();
// Same thing as MaskSummary, but for paths.
struct PathSummary {
constexpr static uint16_t kIsPath = 0;
SkGlyphID glyphID;
// If drawing glyphID can be done with a path, this is 0, otherwise it is the max
// dimension of the glyph.
uint16_t maxDimensionOrPath;
struct PathSummaryTraits {
static SkGlyphID GetKey(PathSummary summary) {
return summary.glyphID;
static uint32_t Hash(SkGlyphID packedID) {
return SkChecksum::CheapMix(packedID);
void writeGlyphPath(const SkGlyph& glyph, Serializer* serializer) const;
void ensureScalerContext();
const SkAutoDescriptor fDescriptor;
const SkDiscardableHandleId fDiscardableHandleId;
const SkGlyphPositionRoundingSpec fRoundingSpec;
// The context built using fDescriptor
std::unique_ptr<SkScalerContext> fContext;
// These fields are set every time getOrCreateCache. This allows the code to maintain the
// fContext as lazy as possible.
const SkTypeface* fTypeface{nullptr};
SkScalerContextEffects fEffects;
// Have the metrics been sent for this strike. Only send them once.
bool fHaveSentFontMetrics{false};
LowerRangeBitVector fSentLowGlyphIDs;
// The masks and paths that currently reside in the GPU process.
SkTHashTable<MaskSummary, SkPackedGlyphID, MaskSummaryTraits> fSentGlyphs;
SkTHashTable<PathSummary, SkGlyphID, PathSummaryTraits> fSentPaths;
// The Masks, SDFT Mask, and Paths that need to be sent to the GPU task for the processed
// TextBlobs. Cleared after diffs are serialized.
std::vector<SkGlyph> fMasksToSend;
std::vector<SkGlyph> fPathsToSend;
// Alloc for storing bits and pieces of paths, Cleared after diffs are serialized.
SkArenaAllocWithReset fPathAlloc{256};
const SkDescriptor& descriptor,
std::unique_ptr<SkScalerContext> context,
uint32_t discardableHandleId)
: fDescriptor{descriptor}
, fDiscardableHandleId(discardableHandleId)
, fRoundingSpec{context->isSubpixel(), context->computeAxisAlignmentForHText()}
// N.B. context must come last because it is used above.
, fContext{std::move(context)}
, fSentLowGlyphIDs{} {
SkASSERT(fDescriptor.getDesc() != nullptr);
SkASSERT(fContext != nullptr);
// No need to write fForceBW because it is a flag private to SkScalerContext_DW, which will never
// be called on the GPU side.
static void writeGlyph(const SkGlyph& glyph, Serializer* serializer) {
void RemoteStrike::writePendingGlyphs(Serializer* serializer) {
// Write the desc.
serializer->emplace<StrikeSpec>(fContext->getTypeface()->uniqueID(), fDiscardableHandleId);
if (!fHaveSentFontMetrics) {
// Write FontMetrics if not sent before.
SkFontMetrics fontMetrics;
fHaveSentFontMetrics = true;
// Write mask glyphs
for (SkGlyph& glyph : fMasksToSend) {
writeGlyph(glyph, serializer);
auto imageSize = glyph.imageSize();
if (imageSize > 0 && FitsInAtlas(glyph)) {
glyph.fImage = serializer->allocate(imageSize, glyph.formatAlignment());
// Write glyphs paths.
for (SkGlyph& glyph : fPathsToSend) {
writeGlyph(glyph, serializer);
writeGlyphPath(glyph, serializer);
void RemoteStrike::ensureScalerContext() {
if (fContext == nullptr) {
fContext = fTypeface->createScalerContext(fEffects, fDescriptor.getDesc());
void RemoteStrike::resetScalerContext() {
fTypeface = nullptr;
void RemoteStrike::setTypefaceAndEffects(
const SkTypeface* typeface, SkScalerContextEffects effects) {
fTypeface = typeface;
fEffects = effects;
void RemoteStrike::writeGlyphPath(
const SkGlyph& glyph, Serializer* serializer) const {
if (glyph.isColor() || glyph.isEmpty()) {
const SkPath* path = glyph.path();
if (path == nullptr) {
size_t pathSize = path->writeToMemory(nullptr);
path->writeToMemory(serializer->allocate(pathSize, kPathAlignment));
template <typename Rejector>
void RemoteStrike::commonMaskLoop(
SkDrawableGlyphBuffer* drawables, SkSourceGlyphBuffer* rejects, Rejector&& reject) {
[&](size_t i, SkPackedGlyphID packedID, SkPoint position) {
MaskSummary* summary = fSentGlyphs.find(packedID);
if (summary == nullptr) {
// Put the new SkGlyph in the glyphs to send.
SkGlyph* glyph = &fMasksToSend.back();
MaskSummary newSummary =
{packedID.value(), CanDrawAsMask(*glyph), CanDrawAsSDFT(*glyph)};
summary = fSentGlyphs.set(newSummary);
// Reject things that are too big.
if (reject(*summary)) {
void RemoteStrike::prepareForMaskDrawing(
SkDrawableGlyphBuffer* drawables, SkSourceGlyphBuffer* rejects) {
for (auto [i, variant, _] : SkMakeEnumerate(drawables->input())) {
SkPackedGlyphID packedID = variant.packedID();
if (fSentLowGlyphIDs.test(packedID)) {
#ifdef SK_DEBUG
MaskSummary* summary = fSentGlyphs.find(packedID);
SkASSERT(summary != nullptr);
SkASSERT(summary->canDrawAsMask && summary->canDrawAsSDFT);
MaskSummary* summary = fSentGlyphs.find(packedID);
if (summary == nullptr) {
// Put the new SkGlyph in the glyphs to send.
SkGlyph* glyph = &fMasksToSend.back();
MaskSummary newSummary =
{packedID.value(), CanDrawAsMask(*glyph), CanDrawAsSDFT(*glyph)};
summary = fSentGlyphs.set(newSummary);
if (summary->canDrawAsMask && summary->canDrawAsSDFT) {
// Reject things that are too big.
if (!summary->canDrawAsMask) {
void RemoteStrike::prepareForSDFTDrawing(
SkDrawableGlyphBuffer* drawables, SkSourceGlyphBuffer* rejects) {
this->commonMaskLoop(drawables, rejects,
[](MaskSummary summary){return !summary.canDrawAsSDFT;});
void RemoteStrike::prepareForPathDrawing(
SkDrawableGlyphBuffer* drawables, SkSourceGlyphBuffer* rejects) {
[&](size_t i, SkPackedGlyphID packedID, SkPoint position) {
SkGlyphID glyphID = packedID.glyphID();
PathSummary* summary = fSentPaths.find(glyphID);
if (summary == nullptr) {
// Put the new SkGlyph in the glyphs to send.
SkGlyph* glyph = &fPathsToSend.back();
uint16_t maxDimensionOrPath = glyph->maxDimension();
// Only try to get the path if the glyphs is not color.
if (!glyph->isColor() && !glyph->isEmpty()) {
glyph->setPath(&fPathAlloc, fContext.get());
if (glyph->path() != nullptr) {
maxDimensionOrPath = PathSummary::kIsPath;
PathSummary newSummary = {glyph->getGlyphID(), maxDimensionOrPath};
summary = fSentPaths.set(newSummary);
if (summary->maxDimensionOrPath != PathSummary::kIsPath) {
rejects->reject(i, (int)summary->maxDimensionOrPath);
// -- WireTypeface ---------------------------------------------------------------------------------
struct WireTypeface {
WireTypeface() = default;
WireTypeface(SkFontID typeface_id, int glyph_count, SkFontStyle style, bool is_fixed)
: typefaceID(typeface_id), glyphCount(glyph_count), style(style), isFixed(is_fixed) {}
SkFontID typefaceID{0};
int glyphCount{0};
SkFontStyle style;
bool isFixed{false};
// -- SkStrikeServerImpl ---------------------------------------------------------------------------
class SkStrikeServerImpl final : public SkStrikeForGPUCacheInterface {
explicit SkStrikeServerImpl(
SkStrikeServer::DiscardableHandleManager* discardableHandleManager);
// SkStrikeServer API methods
sk_sp<SkData> serializeTypeface(SkTypeface*);
void writeStrikeData(std::vector<uint8_t>* memory);
// Methods for SkStrikeForGPUCacheInterface
RemoteStrike* getOrCreateCache(const SkPaint&,
const SkFont& font,
const SkSurfaceProps&,
const SkMatrix&,
SkScalerContextFlags flags,
SkScalerContextEffects* effects);
SkScopedStrikeForGPU findOrCreateScopedStrike(const SkDescriptor& desc,
const SkScalerContextEffects& effects,
const SkTypeface& typeface) override;
// Methods for testing
void setMaxEntriesInDescriptorMapForTesting(size_t count);
size_t remoteStrikeMapSizeForTesting() const;
static constexpr size_t kMaxEntriesInDescriptorMap = 2000u;
void checkForDeletedEntries();
RemoteStrike* getOrCreateCache(const SkDescriptor& desc,
const SkTypeface& typeface,
SkScalerContextEffects effects);
using DescToRemoteStrike =
std::unordered_map<const SkDescriptor*, std::unique_ptr<RemoteStrike>, MapOps, MapOps>;
DescToRemoteStrike fDescToRemoteStrike;
SkStrikeServer::DiscardableHandleManager* const fDiscardableHandleManager;
SkTHashSet<SkFontID> fCachedTypefaces;
size_t fMaxEntriesInDescriptorMap = kMaxEntriesInDescriptorMap;
// Cached serialized typefaces.
SkTHashMap<SkFontID, sk_sp<SkData>> fSerializedTypefaces;
// State cached until the next serialization.
SkTHashSet<RemoteStrike*> fRemoteStrikesToSend;
std::vector<WireTypeface> fTypefacesToSend;
SkStrikeServerImpl::SkStrikeServerImpl(SkStrikeServer::DiscardableHandleManager* dhm)
: fDiscardableHandleManager(dhm) {
void SkStrikeServerImpl::setMaxEntriesInDescriptorMapForTesting(size_t count) {
fMaxEntriesInDescriptorMap = count;
size_t SkStrikeServerImpl::remoteStrikeMapSizeForTesting() const {
return fDescToRemoteStrike.size();
sk_sp<SkData> SkStrikeServerImpl::serializeTypeface(SkTypeface* tf) {
auto* data = fSerializedTypefaces.find(SkTypeface::UniqueID(tf));
if (data) {
return *data;
WireTypeface wire(SkTypeface::UniqueID(tf), tf->countGlyphs(), tf->fontStyle(),
data = fSerializedTypefaces.set(SkTypeface::UniqueID(tf),
SkData::MakeWithCopy(&wire, sizeof(wire)));
return *data;
void SkStrikeServerImpl::writeStrikeData(std::vector<uint8_t>* memory) {
SkString msg;
msg.appendf("\nBegin send strike differences\n");
size_t strikesToSend = 0;
fRemoteStrikesToSend.foreach ([&](RemoteStrike* strike) {
if (strike->hasPendingGlyphs()) {
} else {
if (strikesToSend == 0 && fTypefacesToSend.empty()) {
Serializer serializer(memory);
for (const auto& tf : fTypefacesToSend) {
fRemoteStrikesToSend.foreach (
#ifdef SK_DEBUG
[&](RemoteStrike* strike) {
if (strike->hasPendingGlyphs()) {
auto it = fDescToRemoteStrike.find(&strike->getDescriptor());
SkASSERT(it != fDescToRemoteStrike.end());
SkASSERT(it->second.get() == strike);
[&serializer](RemoteStrike* strike) {
if (strike->hasPendingGlyphs()) {
msg.appendf("End send strike differences");
SkDebugf("%s\n", msg.c_str());
RemoteStrike* SkStrikeServerImpl::getOrCreateCache(
const SkPaint& paint,
const SkFont& font,
const SkSurfaceProps& props,
const SkMatrix& matrix,
SkScalerContextFlags flags,
SkScalerContextEffects* effects) {
SkAutoDescriptor descStorage;
auto desc = create_descriptor(paint, font, matrix, props, flags, &descStorage, effects);
return this->getOrCreateCache(*desc, *font.getTypefaceOrDefault(), *effects);
SkScopedStrikeForGPU SkStrikeServerImpl::findOrCreateScopedStrike(const SkDescriptor& desc,
const SkScalerContextEffects& effects,
const SkTypeface& typeface) {
return SkScopedStrikeForGPU{this->getOrCreateCache(desc, typeface, effects)};
void SkStrikeServerImpl::checkForDeletedEntries() {
auto it = fDescToRemoteStrike.begin();
while (fDescToRemoteStrike.size() > fMaxEntriesInDescriptorMap &&
it != fDescToRemoteStrike.end()) {
RemoteStrike* strike = it->second.get();
if (fDiscardableHandleManager->isHandleDeleted(strike->discardableHandleId())) {
// If we are removing the strike, we better not be trying to send it at the same time.
it = fDescToRemoteStrike.erase(it);
} else {
RemoteStrike* SkStrikeServerImpl::getOrCreateCache(
const SkDescriptor& desc, const SkTypeface& typeface, SkScalerContextEffects effects) {
// In cases where tracing is turned off, make sure not to get an unused function warning.
// Lambdaize the function.
TRACE_EVENT1("skia", "RecForDesc", "rec",
auto ptr = desc.findEntry(kRec_SkDescriptorTag, nullptr);
SkScalerContextRec rec;
std::memcpy((void*)&rec, ptr, sizeof(rec));
return rec.dump();
auto it = fDescToRemoteStrike.find(&desc);
if (it != fDescToRemoteStrike.end()) {
// We have processed the RemoteStrike before. Reuse it.
RemoteStrike* strike = it->second.get();
strike->setTypefaceAndEffects(&typeface, effects);
if (fRemoteStrikesToSend.contains(strike)) {
// Already tracking
return strike;
// Strike is in unknown state on GPU. Start tracking strike on GPU by locking it.
bool locked = fDiscardableHandleManager->lockHandle(it->second->discardableHandleId());
if (locked) {
return strike;
// Create a new RemoteStrike. Start by processing the typeface.
const SkFontID typefaceId = typeface.uniqueID();
if (!fCachedTypefaces.contains(typefaceId)) {
fTypefacesToSend.emplace_back(typefaceId, typeface.countGlyphs(),
auto context = typeface.createScalerContext(effects, &desc);
auto newHandle = fDiscardableHandleManager->createHandle(); // Locked on creation
auto remoteStrike = std::make_unique<RemoteStrike>(desc, std::move(context), newHandle);
remoteStrike->setTypefaceAndEffects(&typeface, effects);
auto remoteStrikePtr = remoteStrike.get();
auto d = &remoteStrike->getDescriptor();
fDescToRemoteStrike[d] = std::move(remoteStrike);
// Be sure we can build glyphs with this RemoteStrike.
remoteStrikePtr->setTypefaceAndEffects(&typeface, effects);
return remoteStrikePtr;
// -- GlyphTrackingDevice --------------------------------------------------------------------------
class GlyphTrackingDevice final : public SkNoPixelsDevice {
const SkISize& dimensions, const SkSurfaceProps& props, SkStrikeServerImpl* server,
sk_sp<SkColorSpace> colorSpace, bool DFTSupport)
: SkNoPixelsDevice(SkIRect::MakeSize(dimensions), props, std::move(colorSpace))
, fStrikeServerImpl(server)
, fDFTSupport(DFTSupport)
, fPainter{props, kUnknown_SkColorType, imageInfo().colorSpace(), fStrikeServerImpl} {
SkASSERT(fStrikeServerImpl != nullptr);
SkBaseDevice* onCreateDevice(const CreateInfo& cinfo, const SkPaint*) override {
const SkSurfaceProps surfaceProps(this->surfaceProps().flags(), cinfo.fPixelGeometry);
return new GlyphTrackingDevice(cinfo.fInfo.dimensions(), surfaceProps, fStrikeServerImpl,
cinfo.fInfo.refColorSpace(), fDFTSupport);
void onDrawGlyphRunList(const SkGlyphRunList& glyphRunList, const SkPaint& paint) override {
GrContextOptions ctxOptions;
GrSDFTControl control =
SkMatrix drawMatrix = this->localToDevice();
drawMatrix.preTranslate(glyphRunList.origin().x(), glyphRunList.origin().y());
for (auto& glyphRun : glyphRunList) {
"Cache Diff");
#endif // SK_SUPPORT_GPU
SkStrikeServerImpl* const fStrikeServerImpl;
const bool fDFTSupport{false};
SkGlyphRunListPainter fPainter;
// -- SkStrikeServer -------------------------------------------------------------------------------
SkStrikeServer::SkStrikeServer(DiscardableHandleManager* dhm)
: fImpl(new SkStrikeServerImpl{dhm}) { }
SkStrikeServer::~SkStrikeServer() = default;
std::unique_ptr<SkCanvas> SkStrikeServer::makeAnalysisCanvas(int width, int height,
const SkSurfaceProps& props,
sk_sp<SkColorSpace> colorSpace,
bool DFTSupport) {
sk_sp<SkBaseDevice> trackingDevice(new GlyphTrackingDevice(SkISize::Make(width, height),
props, this->impl(),
return std::make_unique<SkCanvas>(std::move(trackingDevice));
sk_sp<SkData> SkStrikeServer::serializeTypeface(SkTypeface* tf) {
return fImpl->serializeTypeface(tf);
void SkStrikeServer::writeStrikeData(std::vector<uint8_t>* memory) {
SkStrikeServerImpl* SkStrikeServer::impl() { return fImpl.get(); }
void SkStrikeServer::setMaxEntriesInDescriptorMapForTesting(size_t count) {
size_t SkStrikeServer::remoteStrikeMapSizeForTesting() const {
return fImpl->remoteStrikeMapSizeForTesting();
// -- DiscardableStrikePinner ----------------------------------------------------------------------
class DiscardableStrikePinner : public SkStrikePinner {
DiscardableStrikePinner(SkDiscardableHandleId discardableHandleId,
sk_sp<SkStrikeClient::DiscardableHandleManager> manager)
: fDiscardableHandleId(discardableHandleId), fManager(std::move(manager)) {}
~DiscardableStrikePinner() override = default;
bool canDelete() override { return fManager->deleteHandle(fDiscardableHandleId); }
const SkDiscardableHandleId fDiscardableHandleId;
sk_sp<SkStrikeClient::DiscardableHandleManager> fManager;
// -- SkStrikeClientImpl ---------------------------------------------------------------------------
class SkStrikeClientImpl {
explicit SkStrikeClientImpl(sk_sp<SkStrikeClient::DiscardableHandleManager>,
bool isLogging = true,
SkStrikeCache* strikeCache = nullptr);
sk_sp<SkTypeface> deserializeTypeface(const void* data, size_t length);
bool readStrikeData(const volatile void* memory, size_t memorySize);
static bool ReadGlyph(SkTLazy<SkGlyph>& glyph, Deserializer* deserializer);
sk_sp<SkTypeface> addTypeface(const WireTypeface& wire);
SkTHashMap<SkFontID, sk_sp<SkTypeface>> fRemoteFontIdToTypeface;
sk_sp<SkStrikeClient::DiscardableHandleManager> fDiscardableHandleManager;
SkStrikeCache* const fStrikeCache;
const bool fIsLogging;
bool isLogging,
SkStrikeCache* strikeCache)
: fDiscardableHandleManager(std::move(discardableManager)),
fStrikeCache{strikeCache ? strikeCache : SkStrikeCache::GlobalStrikeCache()},
fIsLogging{isLogging} {}
// No need to read fForceBW because it is a flag private to SkScalerContext_DW, which will never
// be called on the GPU side.
bool SkStrikeClientImpl::ReadGlyph(SkTLazy<SkGlyph>& glyph, Deserializer* deserializer) {
SkPackedGlyphID glyphID;
if (!deserializer->read<SkPackedGlyphID>(&glyphID)) return false;
if (!deserializer->read<float>(&glyph->fAdvanceX)) return false;
if (!deserializer->read<float>(&glyph->fAdvanceY)) return false;
if (!deserializer->read<uint16_t>(&glyph->fWidth)) return false;
if (!deserializer->read<uint16_t>(&glyph->fHeight)) return false;
if (!deserializer->read<int16_t>(&glyph->fTop)) return false;
if (!deserializer->read<int16_t>(&glyph->fLeft)) return false;
uint8_t maskFormat;
if (!deserializer->read<uint8_t>(&maskFormat)) return false;
if (!SkMask::IsValidFormat(maskFormat)) return false;
glyph->fMaskFormat = static_cast<SkMask::Format>(maskFormat);
return true;
#define READ_FAILURE \
{ \
SkDebugf("Bad font data serialization line: %d", __LINE__); \
SkStrikeClient::DiscardableHandleManager::ReadFailureData data = { \
memorySize, deserializer.bytesRead(), typefaceSize, \
strikeCount, glyphImagesCount, glyphPathsCount}; \
fDiscardableHandleManager->notifyReadFailure(data); \
return false; \
bool SkStrikeClientImpl::readStrikeData(const volatile void* memory, size_t memorySize) {
SkASSERT(memorySize != 0u);
Deserializer deserializer(static_cast<const volatile char*>(memory), memorySize);
uint64_t typefaceSize = 0;
uint64_t strikeCount = 0;
uint64_t glyphImagesCount = 0;
uint64_t glyphPathsCount = 0;
if (!<uint64_t>(&typefaceSize)) READ_FAILURE
for (size_t i = 0; i < typefaceSize; ++i) {
WireTypeface wire;
if (!<WireTypeface>(&wire)) READ_FAILURE
// TODO(khushalsagar): The typeface no longer needs a reference to the
// SkStrikeClient, since all needed glyphs must have been pushed before
// raster.
SkString msg;
msg.appendf("\nBegin receive strike differences\n");
if (!<uint64_t>(&strikeCount)) READ_FAILURE
for (size_t i = 0; i < strikeCount; ++i) {
StrikeSpec spec;
if (!<StrikeSpec>(&spec)) READ_FAILURE
SkAutoDescriptor sourceAd;
if (!deserializer.readDescriptor(&sourceAd)) READ_FAILURE
msg.appendf(" Received descriptor:\n%s", sourceAd.getDesc()->dumpRec().c_str());
bool fontMetricsInitialized;
SkFontMetrics fontMetrics{};
if (!fontMetricsInitialized) {
if (!<SkFontMetrics>(&fontMetrics)) READ_FAILURE
// Get the local typeface from remote fontID.
auto* tfPtr = fRemoteFontIdToTypeface.find(spec.typefaceID);
// Received strikes for a typeface which doesn't exist.
if (!tfPtr) READ_FAILURE
auto* tf = tfPtr->get();
// Replace the ContextRec in the desc from the server to create the client
// side descriptor.
// TODO: Can we do this in-place and re-compute checksum? Instead of a complete copy.
SkAutoDescriptor ad;
auto* client_desc = auto_descriptor_from_desc(sourceAd.getDesc(), tf->uniqueID(), &ad);
msg.appendf(" Mapped descriptor:\n%s", client_desc->dumpRec().c_str());
auto strike = fStrikeCache->findStrike(*client_desc);
// Metrics are only sent the first time. If the metrics are not initialized, there must
// be an existing strike.
if (fontMetricsInitialized && strike == nullptr) READ_FAILURE
if (strike == nullptr) {
// Note that we don't need to deserialize the effects since we won't be generating any
// glyphs here anyway, and the desc is still correct since it includes the serialized
// effects.
SkScalerContextEffects effects;
auto scaler = tf->createScalerContext(effects, client_desc);
strike = fStrikeCache->createStrike(
*client_desc, std::move(scaler), &fontMetrics,
spec.discardableHandleId, fDiscardableHandleManager));
if (!<uint64_t>(&glyphImagesCount)) READ_FAILURE
for (size_t j = 0; j < glyphImagesCount; j++) {
SkTLazy<SkGlyph> glyph;
if (!ReadGlyph(glyph, &deserializer)) READ_FAILURE
if (!glyph->isEmpty() && SkStrikeForGPU::FitsInAtlas(*glyph)) {
const volatile void* image =>imageSize(), glyph->formatAlignment());
if (!image) READ_FAILURE
glyph->fImage = (void*)image;
strike->mergeGlyphAndImage(glyph->getPackedID(), *glyph);
if (!<uint64_t>(&glyphPathsCount)) READ_FAILURE
for (size_t j = 0; j < glyphPathsCount; j++) {
SkTLazy<SkGlyph> glyph;
if (!ReadGlyph(glyph, &deserializer)) READ_FAILURE
SkGlyph* allocatedGlyph = strike->mergeGlyphAndImage(glyph->getPackedID(), *glyph);
SkPath* pathPtr = nullptr;
SkPath path;
uint64_t pathSize = 0u;
if (!<uint64_t>(&pathSize)) READ_FAILURE
if (pathSize > 0) {
auto* pathData =, kPathAlignment);
if (!pathData) READ_FAILURE
if (!path.readFromMemory(const_cast<const void*>(pathData), pathSize)) READ_FAILURE
pathPtr = &path;
strike->mergePath(allocatedGlyph, pathPtr);
msg.appendf("End receive strike differences");
SkDebugf("%s\n", msg.c_str());
return true;
sk_sp<SkTypeface> SkStrikeClientImpl::deserializeTypeface(const void* buf, size_t len) {
WireTypeface wire;
if (len != sizeof(wire)) return nullptr;
memcpy(&wire, buf, sizeof(wire));
return this->addTypeface(wire);
sk_sp<SkTypeface> SkStrikeClientImpl::addTypeface(const WireTypeface& wire) {
auto* typeface = fRemoteFontIdToTypeface.find(wire.typefaceID);
if (typeface) return *typeface;
auto newTypeface = sk_make_sp<SkTypefaceProxy>(
wire.typefaceID, wire.glyphCount,, wire.isFixed,
fDiscardableHandleManager, fIsLogging);
fRemoteFontIdToTypeface.set(wire.typefaceID, newTypeface);
return std::move(newTypeface);
// SkStrikeClient ----------------------------------------------------------------------------------
SkStrikeClient::SkStrikeClient(sk_sp<DiscardableHandleManager> discardableManager,
bool isLogging,
SkStrikeCache* strikeCache)
: fImpl{new SkStrikeClientImpl{std::move(discardableManager), isLogging, strikeCache}} {}
SkStrikeClient::~SkStrikeClient() = default;
bool SkStrikeClient::readStrikeData(const volatile void* memory, size_t memorySize) {
return fImpl->readStrikeData(memory, memorySize);
sk_sp<SkTypeface> SkStrikeClient::deserializeTypeface(const void* buf, size_t len) {
return fImpl->deserializeTypeface(buf, len);