| /* | |
| * Copyright 2018 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/private/chromium/SkChromeRemoteGlyphCache.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkData.h" | |
| #include "include/core/SkDrawable.h" | |
| #include "include/core/SkFontMetrics.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkPicture.h" | |
| #include "include/core/SkPoint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkSize.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkSurfaceProps.h" | |
| #include "include/core/SkTypeface.h" | |
| #include "include/private/SkAssert.h" | |
| #include "include/private/SkDebug.h" | |
| #include "include/private/SkTFitsIn.h" | |
| #include "include/private/SkTo.h" | |
| #include "include/private/chromium/Slug.h" | |
| #include "src/core/SkArenaAlloc.h" | |
| #include "src/core/SkDescriptor.h" | |
| #include "src/core/SkDevice.h" | |
| #include "src/core/SkFontMetricsPriv.h" | |
| #include "src/core/SkGlyph.h" | |
| #include "src/core/SkReadBuffer.h" | |
| #include "src/core/SkScalerContext.h" | |
| #include "src/core/SkStrike.h" | |
| #include "src/core/SkStrikeCache.h" | |
| #include "src/core/SkStrikeSpec.h" | |
| #include "src/core/SkTHash.h" | |
| #include "src/core/SkTraceEvent.h" | |
| #include "src/core/SkTypeface_remote.h" | |
| #include "src/core/SkWriteBuffer.h" | |
| #include "src/text/GlyphRun.h" | |
| #include "src/text/StrikeForGPU.h" | |
| #include "src/text/gpu/SubRunAllocator.h" | |
| #include "src/text/gpu/SubRunContainer.h" | |
| #include "src/text/gpu/SubRunControl.h" | |
| #include "src/text/gpu/TextBlob.h" | |
| #include <cstring> | |
| #include <memory> | |
| #include <optional> | |
| #include <unordered_map> | |
| #include <utility> | |
| class SkPaint; | |
| using namespace skia_private; | |
| using namespace sktext; | |
| using namespace sktext::gpu; | |
| using namespace skglyph; | |
| namespace { | |
| // -- StrikeSpec ----------------------------------------------------------------------------------- | |
| struct StrikeSpec { | |
| StrikeSpec() = default; | |
| StrikeSpec(SkTypefaceID typefaceID, SkDiscardableHandleId discardableHandleId) | |
| : fTypefaceID{typefaceID}, fDiscardableHandleId(discardableHandleId) {} | |
| SkTypefaceID fTypefaceID = 0u; | |
| SkDiscardableHandleId fDiscardableHandleId = 0u; | |
| }; | |
| // -- RemoteStrike ---------------------------------------------------------------------------- | |
| class RemoteStrike final : public sktext::StrikeForGPU { | |
| public: | |
| // N.B. RemoteStrike is not valid until ensureScalerContext is called. | |
| RemoteStrike(const SkStrikeSpec& strikeSpec, | |
| std::unique_ptr<SkScalerContext> context, | |
| SkDiscardableHandleId discardableHandleId); | |
| ~RemoteStrike() override = default; | |
| void lock() override {} | |
| void unlock() override {} | |
| SkGlyphDigest digestFor(skglyph::ActionType, SkPackedGlyphID) override; | |
| bool prepareForImage(SkGlyph* glyph) override { | |
| this->ensureScalerContext(); | |
| glyph->setImage(&fAlloc, fContext.get()); | |
| return glyph->image() != nullptr; | |
| } | |
| bool prepareForPath(SkGlyph* glyph) override { | |
| this->ensureScalerContext(); | |
| glyph->setPath(&fAlloc, fContext.get()); | |
| return glyph->path() != nullptr; | |
| } | |
| bool prepareForDrawable(SkGlyph* glyph) override { | |
| this->ensureScalerContext(); | |
| glyph->setDrawable(&fAlloc, fContext.get()); | |
| return glyph->drawable() != nullptr; | |
| } | |
| void writePendingGlyphs(SkWriteBuffer& buffer); | |
| SkDiscardableHandleId discardableHandleId() const { return fDiscardableHandleId; } | |
| const SkDescriptor& getDescriptor() const override { | |
| return *fDescriptor.getDesc(); | |
| } | |
| void setStrikeSpec(const SkStrikeSpec& strikeSpec); | |
| const SkGlyphPositionRoundingSpec& roundingSpec() const override { | |
| return fRoundingSpec; | |
| } | |
| sktext::SkStrikePromise strikePromise() override; | |
| bool hasPendingGlyphs() const { | |
| return !fMasksToSend.empty() || !fPathsToSend.empty() || !fDrawablesToSend.empty(); | |
| } | |
| void resetScalerContext(); | |
| private: | |
| void ensureScalerContext(); | |
| const SkAutoDescriptor fDescriptor; | |
| const SkDiscardableHandleId fDiscardableHandleId; | |
| const SkGlyphPositionRoundingSpec fRoundingSpec; | |
| // The context built using fDescriptor | |
| std::unique_ptr<SkScalerContext> fContext; | |
| SkTypefaceID fStrikeSpecTypefaceId; | |
| // fStrikeSpec is set every time getOrCreateCache is called. This allows the code to maintain | |
| // the fContext as lazy as possible. | |
| const SkStrikeSpec* fStrikeSpec; | |
| // Have the metrics been sent for this strike. Only send them once. | |
| bool fHaveSentFontMetrics{false}; | |
| // The masks and paths that currently reside in the GPU process. | |
| THashTable<SkGlyphDigest, SkPackedGlyphID, SkGlyphDigest> fSentGlyphs; | |
| // 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; | |
| std::vector<SkGlyph> fDrawablesToSend; | |
| // Alloc for storing bits and pieces of paths and drawables, Cleared after diffs are serialized. | |
| SkArenaAllocWithReset fAlloc{256}; | |
| }; | |
| RemoteStrike::RemoteStrike( | |
| const SkStrikeSpec& strikeSpec, | |
| std::unique_ptr<SkScalerContext> context, | |
| uint32_t discardableHandleId) | |
| : fDescriptor{strikeSpec.descriptor()} | |
| , fDiscardableHandleId(discardableHandleId) | |
| , fRoundingSpec{context->isSubpixel(), context->computeAxisAlignmentForHText()} | |
| // N.B. context must come last because it is used above. | |
| , fContext{std::move(context)} | |
| , fStrikeSpecTypefaceId(strikeSpec.typeface().uniqueID()) { | |
| SkASSERT(fDescriptor.getDesc() != nullptr); | |
| SkASSERT(fContext != nullptr); | |
| } | |
| void RemoteStrike::writePendingGlyphs(SkWriteBuffer& buffer) { | |
| SkASSERT(this->hasPendingGlyphs()); | |
| // ScalerContext should not hold to the typeface, so we should not use its ID. | |
| // We should use StrikeSpec typeface and its ID instead. | |
| buffer.writeUInt(fStrikeSpecTypefaceId); | |
| buffer.writeUInt(fDiscardableHandleId); | |
| fDescriptor.getDesc()->flatten(buffer); | |
| buffer.writeBool(fHaveSentFontMetrics); | |
| if (!fHaveSentFontMetrics) { | |
| // Write FontMetrics if not sent before. | |
| SkFontMetrics fontMetrics; | |
| fContext->getFontMetrics(&fontMetrics); | |
| SkFontMetricsPriv::Flatten(buffer, fontMetrics); | |
| fHaveSentFontMetrics = true; | |
| } | |
| // Make sure to install all the mask data into the glyphs before sending. | |
| for (SkGlyph& glyph: fMasksToSend) { | |
| this->prepareForImage(&glyph); | |
| } | |
| // Make sure to install all the path data into the glyphs before sending. | |
| for (SkGlyph& glyph: fPathsToSend) { | |
| this->prepareForPath(&glyph); | |
| } | |
| // Make sure to install all the drawable data into the glyphs before sending. | |
| for (SkGlyph& glyph: fDrawablesToSend) { | |
| this->prepareForDrawable(&glyph); | |
| } | |
| // Send all the pending glyph information. | |
| SkStrike::FlattenGlyphsByType(buffer, fMasksToSend, fPathsToSend, fDrawablesToSend); | |
| // Reset all the sending data. | |
| fMasksToSend.clear(); | |
| fPathsToSend.clear(); | |
| fDrawablesToSend.clear(); | |
| fAlloc.reset(); | |
| } | |
| void RemoteStrike::ensureScalerContext() { | |
| if (fContext == nullptr) { | |
| fContext = fStrikeSpec->createScalerContext(); | |
| } | |
| } | |
| void RemoteStrike::resetScalerContext() { | |
| fContext = nullptr; | |
| fStrikeSpec = nullptr; | |
| } | |
| void RemoteStrike::setStrikeSpec(const SkStrikeSpec& strikeSpec) { | |
| fStrikeSpec = &strikeSpec; | |
| } | |
| SkGlyphDigest RemoteStrike::digestFor(ActionType actionType, SkPackedGlyphID packedGlyphID) { | |
| SkGlyphDigest* digestPtr = fSentGlyphs.find(packedGlyphID); | |
| if (digestPtr != nullptr && digestPtr->actionFor(actionType) != GlyphAction::kUnset) { | |
| return *digestPtr; | |
| } | |
| SkGlyph* glyph; | |
| this->ensureScalerContext(); | |
| switch (actionType) { | |
| case skglyph::kPath: { | |
| fPathsToSend.emplace_back(fContext->makeGlyph(packedGlyphID, &fAlloc)); | |
| glyph = &fPathsToSend.back(); | |
| break; | |
| } | |
| case skglyph::kDrawable: { | |
| fDrawablesToSend.emplace_back(fContext->makeGlyph(packedGlyphID, &fAlloc)); | |
| glyph = &fDrawablesToSend.back(); | |
| break; | |
| } | |
| default: { | |
| fMasksToSend.emplace_back(fContext->makeGlyph(packedGlyphID, &fAlloc)); | |
| glyph = &fMasksToSend.back(); | |
| break; | |
| } | |
| } | |
| if (digestPtr == nullptr) { | |
| digestPtr = fSentGlyphs.set(SkGlyphDigest{0, *glyph}); | |
| } | |
| digestPtr->setActionFor(actionType, glyph, this); | |
| return *digestPtr; | |
| } | |
| sktext::SkStrikePromise RemoteStrike::strikePromise() { | |
| return sktext::SkStrikePromise{*this->fStrikeSpec}; | |
| } | |
| } // namespace | |
| // -- SkStrikeServerImpl --------------------------------------------------------------------------- | |
| class SkStrikeServerImpl final : public sktext::StrikeForGPUCacheInterface { | |
| public: | |
| explicit SkStrikeServerImpl( | |
| SkStrikeServer::DiscardableHandleManager* discardableHandleManager); | |
| // SkStrikeServer API methods | |
| void writeStrikeData(std::vector<uint8_t>* memory); | |
| sk_sp<sktext::StrikeForGPU> findOrCreateScopedStrike(const SkStrikeSpec& strikeSpec) override; | |
| // Methods for testing | |
| void setMaxEntriesInDescriptorMapForTesting(size_t count); | |
| size_t remoteStrikeMapSizeForTesting() const; | |
| private: | |
| inline static constexpr size_t kMaxEntriesInDescriptorMap = 2000u; | |
| void checkForDeletedEntries(); | |
| sk_sp<RemoteStrike> getOrCreateCache(const SkStrikeSpec& strikeSpec); | |
| struct MapOps { | |
| size_t operator()(const SkDescriptor* key) const { | |
| return key->getChecksum(); | |
| } | |
| bool operator()(const SkDescriptor* lhs, const SkDescriptor* rhs) const { | |
| return *lhs == *rhs; | |
| } | |
| }; | |
| using DescToRemoteStrike = | |
| std::unordered_map<const SkDescriptor*, sk_sp<RemoteStrike>, MapOps, MapOps>; | |
| DescToRemoteStrike fDescToRemoteStrike; | |
| SkStrikeServer::DiscardableHandleManager* const fDiscardableHandleManager; | |
| THashSet<SkTypefaceID> fCachedTypefaces; | |
| size_t fMaxEntriesInDescriptorMap = kMaxEntriesInDescriptorMap; | |
| // State cached until the next serialization. | |
| THashSet<RemoteStrike*> fRemoteStrikesToSend; | |
| std::vector<SkTypefaceProxyPrototype> fTypefacesToSend; | |
| }; | |
| SkStrikeServerImpl::SkStrikeServerImpl(SkStrikeServer::DiscardableHandleManager* dhm) | |
| : fDiscardableHandleManager(dhm) { | |
| SkASSERT(fDiscardableHandleManager); | |
| } | |
| void SkStrikeServerImpl::setMaxEntriesInDescriptorMapForTesting(size_t count) { | |
| fMaxEntriesInDescriptorMap = count; | |
| } | |
| size_t SkStrikeServerImpl::remoteStrikeMapSizeForTesting() const { | |
| return fDescToRemoteStrike.size(); | |
| } | |
| void SkStrikeServerImpl::writeStrikeData(std::vector<uint8_t>* memory) { | |
| // We can use the default SkSerialProcs because we do not currently need to encode any SkImages. | |
| SkBinaryWriteBuffer buffer{nullptr, 0, {}}; | |
| // Gather statistics about what needs to be sent. | |
| size_t strikesToSend = 0; | |
| fRemoteStrikesToSend.foreach([&](RemoteStrike* strike) { | |
| if (strike->hasPendingGlyphs()) { | |
| strikesToSend++; | |
| } else { | |
| // This strike has nothing to send, so drop its scaler context to reduce memory. | |
| strike->resetScalerContext(); | |
| } | |
| }); | |
| // If there are no strikes or typefaces to send, then cleanup and return. | |
| if (strikesToSend == 0 && fTypefacesToSend.empty()) { | |
| fRemoteStrikesToSend.reset(); | |
| return; | |
| } | |
| // Send newly seen typefaces. | |
| SkASSERT_RELEASE(SkTFitsIn<int>(fTypefacesToSend.size())); | |
| buffer.writeInt(fTypefacesToSend.size()); | |
| for (const auto& typeface: fTypefacesToSend) { | |
| SkTypefaceProxyPrototype proto{typeface}; | |
| proto.flatten(buffer); | |
| } | |
| fTypefacesToSend.clear(); | |
| buffer.writeInt(strikesToSend); | |
| fRemoteStrikesToSend.foreach( | |
| [&](RemoteStrike* strike) { | |
| if (strike->hasPendingGlyphs()) { | |
| strike->writePendingGlyphs(buffer); | |
| strike->resetScalerContext(); | |
| } | |
| } | |
| ); | |
| fRemoteStrikesToSend.reset(); | |
| // Copy data into the vector. | |
| auto data = buffer.snapshotAsData(); | |
| memory->assign(data->bytes(), data->bytes() + data->size()); | |
| } | |
| sk_sp<StrikeForGPU> SkStrikeServerImpl::findOrCreateScopedStrike( | |
| const SkStrikeSpec& strikeSpec) { | |
| return this->getOrCreateCache(strikeSpec); | |
| } | |
| 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 trying to send the strike, then do not erase it. | |
| if (!fRemoteStrikesToSend.contains(strike)) { | |
| // Erase returns the iterator following the removed element. | |
| it = fDescToRemoteStrike.erase(it); | |
| continue; | |
| } | |
| } | |
| ++it; | |
| } | |
| } | |
| sk_sp<RemoteStrike> SkStrikeServerImpl::getOrCreateCache(const SkStrikeSpec& strikeSpec) { | |
| // In cases where tracing is turned off, make sure not to get an unused function warning. | |
| // Lambdaize the function. | |
| TRACE_EVENT1("skia", "RecForDesc", "rec", | |
| TRACE_STR_COPY( | |
| [&strikeSpec](){ | |
| auto ptr = | |
| strikeSpec.descriptor().findEntry(kRec_SkDescriptorTag, nullptr); | |
| SkScalerContextRec rec; | |
| std::memcpy((void*)&rec, ptr, sizeof(rec)); | |
| return rec.dump(); | |
| }().c_str() | |
| ) | |
| ); | |
| if (auto it = fDescToRemoteStrike.find(&strikeSpec.descriptor()); | |
| it != fDescToRemoteStrike.end()) | |
| { | |
| // We have processed the RemoteStrike before. Reuse it. | |
| sk_sp<RemoteStrike> strike = it->second; | |
| strike->setStrikeSpec(strikeSpec); | |
| if (fRemoteStrikesToSend.contains(strike.get())) { | |
| // 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) { | |
| fRemoteStrikesToSend.add(strike.get()); | |
| return strike; | |
| } | |
| // If it wasn't locked, then forget this strike, and build it anew below. | |
| fDescToRemoteStrike.erase(it); | |
| } | |
| const SkTypeface& typeface = strikeSpec.typeface(); | |
| // Create a new RemoteStrike. Start by processing the typeface. | |
| const SkTypefaceID typefaceId = typeface.uniqueID(); | |
| if (!fCachedTypefaces.contains(typefaceId)) { | |
| fCachedTypefaces.add(typefaceId); | |
| fTypefacesToSend.emplace_back(typeface); | |
| } | |
| auto context = strikeSpec.createScalerContext(); | |
| auto newHandle = fDiscardableHandleManager->createHandle(); // Locked on creation | |
| auto remoteStrike = sk_make_sp<RemoteStrike>(strikeSpec, std::move(context), newHandle); | |
| remoteStrike->setStrikeSpec(strikeSpec); | |
| fRemoteStrikesToSend.add(remoteStrike.get()); | |
| auto d = &remoteStrike->getDescriptor(); | |
| fDescToRemoteStrike[d] = remoteStrike; | |
| checkForDeletedEntries(); | |
| return remoteStrike; | |
| } | |
| // -- GlyphTrackingDevice -------------------------------------------------------------------------- | |
| class GlyphTrackingDevice final : public SkNoPixelsDevice { | |
| public: | |
| GlyphTrackingDevice( | |
| const SkISize& dimensions, const SkSurfaceProps& props, SkStrikeServerImpl* server, | |
| sk_sp<SkColorSpace> colorSpace, sktext::gpu::SubRunControl SubRunControl) | |
| : SkNoPixelsDevice(SkIRect::MakeSize(dimensions), props, std::move(colorSpace)) | |
| , fStrikeServerImpl(server) | |
| , fSubRunControl(SubRunControl) { | |
| SkASSERT(fStrikeServerImpl != nullptr); | |
| } | |
| sk_sp<SkDevice> createDevice(const CreateInfo& cinfo, const SkPaint*) override { | |
| const SkSurfaceProps surfaceProps = | |
| this->surfaceProps().cloneWithPixelGeometry(cinfo.fPixelGeometry); | |
| return sk_make_sp<GlyphTrackingDevice>(cinfo.fInfo.dimensions(), | |
| surfaceProps, | |
| fStrikeServerImpl, | |
| cinfo.fInfo.refColorSpace(), | |
| fSubRunControl); | |
| } | |
| SkStrikeDeviceInfo strikeDeviceInfo() const override { | |
| return {this->surfaceProps(), this->scalerContextFlags(), &fSubRunControl}; | |
| } | |
| protected: | |
| void onDrawGlyphRunList(SkCanvas*, | |
| const sktext::GlyphRunList& glyphRunList, | |
| const SkPaint& paint) override { | |
| SkMatrix drawMatrix = this->localToDevice(); | |
| drawMatrix.preTranslate(glyphRunList.origin().x(), glyphRunList.origin().y()); | |
| // Just ignore the resulting SubRunContainer. Since we're passing in a null SubRunAllocator | |
| // no SubRuns will be produced. | |
| STSubRunAllocator<sizeof(SubRunContainer), alignof(SubRunContainer)> tempAlloc; | |
| auto container = SubRunContainer::MakeInAlloc(glyphRunList, | |
| drawMatrix, | |
| paint, | |
| this->strikeDeviceInfo(), | |
| fStrikeServerImpl, | |
| &tempAlloc, | |
| SubRunContainer::kStrikeCalculationsOnly, | |
| "Cache Diff"); | |
| // Calculations only. No SubRuns. | |
| SkASSERT(container->isEmpty()); | |
| } | |
| sk_sp<sktext::gpu::Slug> convertGlyphRunListToSlug(const sktext::GlyphRunList& glyphRunList, | |
| const SkPaint& paint) override { | |
| // Full matrix for placing glyphs. | |
| SkMatrix positionMatrix = this->localToDevice(); | |
| positionMatrix.preTranslate(glyphRunList.origin().x(), glyphRunList.origin().y()); | |
| // Use the SkStrikeServer's strike cache to generate the Slug. | |
| return sktext::gpu::MakeSlug(this->localToDevice(), | |
| glyphRunList, | |
| paint, | |
| this->strikeDeviceInfo(), | |
| fStrikeServerImpl); | |
| } | |
| private: | |
| SkStrikeServerImpl* const fStrikeServerImpl; | |
| const sktext::gpu::SubRunControl fSubRunControl; | |
| }; | |
| // -- 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, | |
| bool DFTPerspSupport) { | |
| #if !defined(SK_DISABLE_SDF_TEXT) | |
| // These are copied from the defaults in GrContextOptions for historical reasons. | |
| // TODO(herb, jvanverth) pipe in parameters that can be used for both Ganesh and Graphite | |
| // backends instead of just using the defaults. | |
| constexpr float kMinDistanceFieldFontSize = 18.f; | |
| #if defined(SK_BUILD_FOR_ANDROID) | |
| constexpr float kGlyphsAsPathsFontSize = 384.f; | |
| #elif defined(SK_BUILD_FOR_MAC) | |
| constexpr float kGlyphsAsPathsFontSize = 256.f; | |
| #else | |
| constexpr float kGlyphsAsPathsFontSize = 324.f; | |
| #endif | |
| // There is no need to set forcePathAA for the remote glyph cache as that control impacts | |
| // *how* the glyphs are rendered as paths, not *when* they are rendered as paths. | |
| auto control = sktext::gpu::SubRunControl{DFTSupport, | |
| props.isUseDeviceIndependentFonts(), | |
| DFTPerspSupport, | |
| kMinDistanceFieldFontSize, | |
| kGlyphsAsPathsFontSize}; | |
| #else | |
| auto control = sktext::gpu::SubRunControl{}; | |
| #endif | |
| sk_sp<SkDevice> trackingDevice = sk_make_sp<GlyphTrackingDevice>( | |
| SkISize::Make(width, height), | |
| props, this->impl(), | |
| std::move(colorSpace), | |
| control); | |
| return std::make_unique<SkCanvas>(std::move(trackingDevice)); | |
| } | |
| void SkStrikeServer::writeStrikeData(std::vector<uint8_t>* memory) { | |
| fImpl->writeStrikeData(memory); | |
| } | |
| SkStrikeServerImpl* SkStrikeServer::impl() { return fImpl.get(); } | |
| void SkStrikeServer::setMaxEntriesInDescriptorMapForTesting(size_t count) { | |
| fImpl->setMaxEntriesInDescriptorMapForTesting(count); | |
| } | |
| size_t SkStrikeServer::remoteStrikeMapSizeForTesting() const { | |
| return fImpl->remoteStrikeMapSizeForTesting(); | |
| } | |
| // -- DiscardableStrikePinner ---------------------------------------------------------------------- | |
| class DiscardableStrikePinner : public SkStrikePinner { | |
| public: | |
| DiscardableStrikePinner(SkDiscardableHandleId discardableHandleId, | |
| sk_sp<SkStrikeClient::DiscardableHandleManager> manager) | |
| : fDiscardableHandleId(discardableHandleId), fManager(std::move(manager)) {} | |
| ~DiscardableStrikePinner() override = default; | |
| bool canDelete() override { return fManager->deleteHandle(fDiscardableHandleId); } | |
| void assertValid() override { fManager->assertHandleValid(fDiscardableHandleId); } | |
| private: | |
| const SkDiscardableHandleId fDiscardableHandleId; | |
| sk_sp<SkStrikeClient::DiscardableHandleManager> fManager; | |
| }; | |
| // -- SkStrikeClientImpl --------------------------------------------------------------------------- | |
| class SkStrikeClientImpl { | |
| public: | |
| explicit SkStrikeClientImpl(sk_sp<SkStrikeClient::DiscardableHandleManager>, | |
| bool isLogging = true, | |
| SkStrikeCache* strikeCache = nullptr); | |
| bool readStrikeData(const volatile void* memory, size_t memorySize); | |
| bool translateTypefaceID(SkAutoDescriptor* descriptor) const; | |
| sk_sp<SkTypeface> retrieveTypefaceUsingServerID(SkTypefaceID) const; | |
| private: | |
| class PictureBackedGlyphDrawable final : public SkDrawable { | |
| public: | |
| PictureBackedGlyphDrawable(sk_sp<SkPicture> self) : fSelf(std::move(self)) {} | |
| private: | |
| sk_sp<SkPicture> fSelf; | |
| SkRect onGetBounds() override { return fSelf->cullRect(); } | |
| size_t onApproximateBytesUsed() override { | |
| return sizeof(PictureBackedGlyphDrawable) + fSelf->approximateBytesUsed(); | |
| } | |
| void onDraw(SkCanvas* canvas) override { canvas->drawPicture(fSelf); } | |
| }; | |
| sk_sp<SkTypeface> addTypeface(const SkTypefaceProxyPrototype& typefaceProto); | |
| THashMap<SkTypefaceID, sk_sp<SkTypeface>> fServerTypefaceIdToTypeface; | |
| sk_sp<SkStrikeClient::DiscardableHandleManager> fDiscardableHandleManager; | |
| SkStrikeCache* const fStrikeCache; | |
| const bool fIsLogging; | |
| }; | |
| SkStrikeClientImpl::SkStrikeClientImpl( | |
| sk_sp<SkStrikeClient::DiscardableHandleManager> | |
| discardableManager, | |
| bool isLogging, | |
| SkStrikeCache* strikeCache) | |
| : fDiscardableHandleManager(std::move(discardableManager)), | |
| fStrikeCache{strikeCache ? strikeCache : SkStrikeCache::GlobalStrikeCache()}, | |
| fIsLogging{isLogging} {} | |
| bool SkStrikeClientImpl::readStrikeData(const volatile void* memory, size_t memorySize) { | |
| SkASSERT(memorySize != 0); | |
| SkASSERT(memory != nullptr); | |
| // Use a local copy to defend against volatile memory TOCTOU issues during deserialization. | |
| sk_sp<SkData> safeMemory = SkData::MakeWithCopy(const_cast<const void*>(memory), memorySize); | |
| // We do not need to set any SkDeserialProcs here because SkStrikeServerImpl::writeStrikeData | |
| // did not encode any SkImages. | |
| SkReadBuffer buffer{safeMemory->data(), safeMemory->size()}; | |
| // Limit the kinds of effects that appear in a glyph's drawable (crbug.com/1442140): | |
| buffer.setAllowSkSL(false); | |
| int curTypeface = 0, | |
| curStrike = 0; | |
| auto postError = [&](int line) { | |
| SkDebugf("Read Error Posted %s : %d", __FILE__, line); | |
| SkStrikeClient::DiscardableHandleManager::ReadFailureData data{ | |
| memorySize, | |
| buffer.offset(), | |
| SkTo<uint64_t>(curTypeface), | |
| SkTo<uint64_t>(curStrike), | |
| SkTo<uint64_t>(0), | |
| SkTo<uint64_t>(0)}; | |
| fDiscardableHandleManager->notifyReadFailure(data); | |
| }; | |
| // Read the number of typefaces sent. | |
| const int typefaceCount = buffer.readInt(); | |
| for (curTypeface = 0; curTypeface < typefaceCount; ++curTypeface) { | |
| auto proto = SkTypefaceProxyPrototype::MakeFromBuffer(buffer); | |
| if (proto) { | |
| this->addTypeface(proto.value()); | |
| } else { | |
| postError(__LINE__); | |
| return false; | |
| } | |
| } | |
| // Read the number of strikes sent. | |
| const int stirkeCount = buffer.readInt(); | |
| for (curStrike = 0; curStrike < stirkeCount; ++curStrike) { | |
| const SkTypefaceID serverTypefaceID = buffer.readUInt(); | |
| if (serverTypefaceID == 0 && !buffer.isValid()) { | |
| postError(__LINE__); | |
| return false; | |
| } | |
| const SkDiscardableHandleId discardableHandleID = buffer.readUInt(); | |
| if (discardableHandleID == 0 && !buffer.isValid()) { | |
| postError(__LINE__); | |
| return false; | |
| } | |
| std::optional<SkAutoDescriptor> serverDescriptor = SkAutoDescriptor::MakeFromBuffer(buffer); | |
| if (!buffer.validate(serverDescriptor.has_value())) { | |
| postError(__LINE__); | |
| return false; | |
| } | |
| const bool fontMetricsInitialized = buffer.readBool(); | |
| if (!fontMetricsInitialized && !buffer.isValid()) { | |
| postError(__LINE__); | |
| return false; | |
| } | |
| std::optional<SkFontMetrics> fontMetrics; | |
| if (!fontMetricsInitialized) { | |
| fontMetrics = SkFontMetricsPriv::MakeFromBuffer(buffer); | |
| if (!fontMetrics || !buffer.isValid()) { | |
| postError(__LINE__); | |
| return false; | |
| } | |
| } | |
| auto* clientTypeface = fServerTypefaceIdToTypeface.find(serverTypefaceID); | |
| if (clientTypeface == nullptr) { | |
| postError(__LINE__); | |
| return false; | |
| } | |
| if (!this->translateTypefaceID(&serverDescriptor.value())) { | |
| postError(__LINE__); | |
| return false; | |
| } | |
| SkDescriptor* clientDescriptor = serverDescriptor->getDesc(); | |
| auto strike = fStrikeCache->findStrike(*clientDescriptor); | |
| if (strike == nullptr) { | |
| // Metrics are only sent the first time. If creating a new strike, then the metrics | |
| // are not initialized. | |
| if (fontMetricsInitialized) { | |
| postError(__LINE__); | |
| return false; | |
| } | |
| SkStrikeSpec strikeSpec{*clientDescriptor, *clientTypeface}; | |
| strike = fStrikeCache->createStrike( | |
| strikeSpec, &fontMetrics.value(), | |
| std::make_unique<DiscardableStrikePinner>( | |
| discardableHandleID, fDiscardableHandleManager)); | |
| } | |
| // Make sure this strike is pinned on the GPU side. | |
| strike->verifyPinnedStrike(); | |
| if (!strike->mergeFromBuffer(buffer)) { | |
| postError(__LINE__); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool SkStrikeClientImpl::translateTypefaceID(SkAutoDescriptor* toChange) const { | |
| SkDescriptor& descriptor = *toChange->getDesc(); | |
| // Rewrite the typefaceID in the rec. | |
| { | |
| uint32_t size; | |
| // findEntry returns a const void*, remove the const in order to update in place. | |
| void* ptr = const_cast<void *>(descriptor.findEntry(kRec_SkDescriptorTag, &size)); | |
| SkScalerContextRec rec; | |
| if (!ptr || size != sizeof(rec)) { return false; } | |
| std::memcpy((void*)&rec, ptr, size); | |
| // Get the local typeface from remote typefaceID. | |
| auto* tfPtr = fServerTypefaceIdToTypeface.find(rec.fTypefaceID); | |
| // Received a strike for a typeface which doesn't exist. | |
| if (!tfPtr) { return false; } | |
| // Update the typeface id to work with the client side. | |
| rec.fTypefaceID = tfPtr->get()->uniqueID(); | |
| std::memcpy(ptr, &rec, size); | |
| } | |
| descriptor.computeChecksum(); | |
| return true; | |
| } | |
| sk_sp<SkTypeface> SkStrikeClientImpl::retrieveTypefaceUsingServerID(SkTypefaceID typefaceID) const { | |
| auto* tfPtr = fServerTypefaceIdToTypeface.find(typefaceID); | |
| return tfPtr != nullptr ? *tfPtr : nullptr; | |
| } | |
| sk_sp<SkTypeface> SkStrikeClientImpl::addTypeface(const SkTypefaceProxyPrototype& typefaceProto) { | |
| sk_sp<SkTypeface>* typeface = | |
| fServerTypefaceIdToTypeface.find(typefaceProto.serverTypefaceID()); | |
| // We already have the typeface. | |
| if (typeface != nullptr) { | |
| return *typeface; | |
| } | |
| auto newTypeface = sk_make_sp<SkTypefaceProxy>( | |
| typefaceProto, fDiscardableHandleManager, fIsLogging); | |
| fServerTypefaceIdToTypeface.set(typefaceProto.serverTypefaceID(), newTypeface); | |
| return 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::retrieveTypefaceUsingServerIDForTest( | |
| SkTypefaceID typefaceID) const { | |
| return fImpl->retrieveTypefaceUsingServerID(typefaceID); | |
| } | |
| bool SkStrikeClient::translateTypefaceID(SkAutoDescriptor* descriptor) const { | |
| return fImpl->translateTypefaceID(descriptor); | |
| } | |
| sk_sp<sktext::gpu::Slug> SkStrikeClient::deserializeSlugForTest(const void* data, | |
| size_t size) const { | |
| return sktext::gpu::Slug::Deserialize(data, size, this); | |
| } |