blob: e597c9f2a2e317ef519b1f35cd1468737ddf346f [file] [log] [blame]
* Copyright 2015 Google Inc.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
#include "src/text/gpu/TextBlob.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkScalar.h"
#include "include/private/base/SkTemplates.h"
#include "include/private/chromium/SkChromeRemoteGlyphCache.h"
#include "include/private/chromium/Slug.h"
#include "src/core/SkFontPriv.h"
#include "src/core/SkMaskFilterBase.h"
#include "src/core/SkMatrixProvider.h"
#include "src/core/SkPaintPriv.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkRectPriv.h"
#include "src/core/SkStrikeCache.h"
#include "src/core/SkWriteBuffer.h"
#include "src/text/GlyphRun.h"
#include "src/text/gpu/SubRunAllocator.h"
#include "src/text/gpu/SubRunContainer.h"
#if SK_SUPPORT_GPU // Ganesh Support
#include "src/gpu/ganesh/Device_v1.h"
#include "src/gpu/ganesh/GrClip.h"
#include "src/gpu/ganesh/GrRecordingContextPriv.h"
#include "src/gpu/ganesh/SurfaceDrawContext.h"
using namespace sktext::gpu;
namespace {
SkMatrix position_matrix(const SkMatrix& drawMatrix, SkPoint drawOrigin) {
SkMatrix position_matrix = drawMatrix;
return position_matrix.preTranslate(drawOrigin.x(), drawOrigin.y());
// Check for integer translate with the same 2x2 matrix.
// Returns the translation, and true if the change from initial matrix to the position matrix
// support using direct glyph masks.
std::tuple<bool, SkVector> can_use_direct(
const SkMatrix& initialPositionMatrix, const SkMatrix& positionMatrix) {
// The existing direct glyph info can be used if the initialPositionMatrix, and the
// positionMatrix have the same 2x2, and the translation between them is integer.
// Calculate the translation in source space to a translation in device space by mapping
// (0, 0) through both the initial position matrix and the position matrix; take the difference.
SkVector translation = positionMatrix.mapOrigin() - initialPositionMatrix.mapOrigin();
return {initialPositionMatrix.getScaleX() == positionMatrix.getScaleX() &&
initialPositionMatrix.getScaleY() == positionMatrix.getScaleY() &&
initialPositionMatrix.getSkewX() == positionMatrix.getSkewX() &&
initialPositionMatrix.getSkewY() == positionMatrix.getSkewY() &&
SkScalarIsInt(translation.x()) && SkScalarIsInt(translation.y()),
static SkColor compute_canonical_color(const SkPaint& paint, bool lcd) {
SkColor canonicalColor = SkPaintPriv::ComputeLuminanceColor(paint);
if (lcd) {
// This is the correct computation for canonicalColor, but there are tons of cases where LCD
// can be modified. For now we just regenerate if any run in a textblob has LCD.
// TODO figure out where all of these modifications are and see if we can incorporate that
// logic at a higher level *OR* use sRGB
//canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);
// TODO we want to figure out a way to be able to use the canonical color on LCD text,
// see the note above. We pick a placeholder value for LCD text to ensure we always match
// the same key
} else {
// A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
// gamma corrected masks anyways, nor color
U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
// reduce to our finite number of bits
canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
return canonicalColor;
// -- SlugImpl -------------------------------------------------------------------------------------
class SlugImpl final : public Slug {
SlugImpl(SubRunAllocator&& alloc,
SubRunContainerOwner subRuns,
SkRect sourceBounds,
const SkPaint& paint,
SkPoint origin);
~SlugImpl() override = default;
static sk_sp<SlugImpl> Make(const SkMatrixProvider& viewMatrix,
const sktext::GlyphRunList& glyphRunList,
const SkPaint& initialPaint,
const SkPaint& drawingPaint,
SkStrikeDeviceInfo strikeDeviceInfo,
sktext::StrikeForGPUCacheInterface* strikeCache);
static sk_sp<Slug> MakeFromBuffer(SkReadBuffer& buffer,
const SkStrikeClient* client);
void doFlatten(SkWriteBuffer& buffer) const override;
void surfaceDraw(SkCanvas*,
const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkPaint& paint,
skgpu::v1::SurfaceDrawContext* sdc) const;
SkRect sourceBounds() const override { return fSourceBounds; }
SkRect sourceBoundsWithOrigin() const override { return fSourceBounds.makeOffset(fOrigin); }
const SkPaint& initialPaint() const override { return fInitialPaint; }
const SkMatrix& initialPositionMatrix() const { return fSubRuns->initialPosition(); }
SkPoint origin() const { return fOrigin; }
// Change memory management to handle the data after Slug, but in the same allocation
// of memory. Only allow placement new.
void operator delete(void* p) { ::operator delete(p); }
void* operator new(size_t) { SK_ABORT("All slugs are created by placement new."); }
void* operator new(size_t, void* p) { return p; }
// The allocator must come first because it needs to be destroyed last. Other fields of this
// structure may have pointers into it.
SubRunAllocator fAlloc;
SubRunContainerOwner fSubRuns;
const SkRect fSourceBounds;
const SkPaint fInitialPaint;
const SkMatrix fInitialPositionMatrix;
const SkPoint fOrigin;
SlugImpl::SlugImpl(SubRunAllocator&& alloc,
SubRunContainerOwner subRuns,
SkRect sourceBounds,
const SkPaint& paint,
SkPoint origin)
: fAlloc {std::move(alloc)}
, fSubRuns(std::move(subRuns))
, fSourceBounds{sourceBounds}
, fInitialPaint{paint}
, fOrigin{origin} {}
void SlugImpl::surfaceDraw(SkCanvas* canvas, const GrClip* clip, const SkMatrixProvider& viewMatrix,
const SkPaint& drawingPaint, skgpu::v1::SurfaceDrawContext* sdc) const {
fSubRuns->draw(canvas, clip, viewMatrix, fOrigin, drawingPaint, this, sdc);
void SlugImpl::doFlatten(SkWriteBuffer& buffer) const {
SkPaintPriv::Flatten(fInitialPaint, buffer);
sk_sp<Slug> SlugImpl::MakeFromBuffer(SkReadBuffer& buffer, const SkStrikeClient* client) {
SkRect sourceBounds = buffer.readRect();
if (!buffer.validate(!sourceBounds.isEmpty())) { return nullptr; }
SkPaint paint = buffer.readPaint();
SkPoint origin = buffer.readPoint();
int allocSizeHint = SubRunContainer::AllocSizeHintFromBuffer(buffer);
auto [initializer, _, alloc] =
SubRunContainerOwner container = SubRunContainer::MakeFromBufferInAlloc(buffer, client, &alloc);
// Something went wrong while reading.
if (!buffer.isValid()) { return nullptr;}
return sk_sp<SlugImpl>(initializer.initialize(
std::move(alloc), std::move(container), sourceBounds, paint, origin));
sk_sp<SlugImpl> SlugImpl::Make(const SkMatrixProvider& viewMatrix,
const sktext::GlyphRunList& glyphRunList,
const SkPaint& initialPaint,
const SkPaint& drawingPaint,
SkStrikeDeviceInfo strikeDeviceInfo,
sktext::StrikeForGPUCacheInterface* strikeCache) {
size_t subRunSizeHint = SubRunContainer::EstimateAllocSize(glyphRunList);
auto [initializer, _, alloc] =
const SkMatrix positionMatrix =
position_matrix(viewMatrix.localToDevice(), glyphRunList.origin());
auto subRuns = SubRunContainer::MakeInAlloc(glyphRunList,
"Make Slug");
sk_sp<SlugImpl> slug = sk_sp<SlugImpl>(initializer.initialize(
// There is nothing to draw here. This is particularly a problem with RSX form blobs where a
// single space becomes a run with no glyphs.
if (slug->fSubRuns->isEmpty()) { return nullptr; }
return slug;
} // namespace
namespace sktext::gpu {
// -- TextBlob::Key ------------------------------------------------------------------------------
auto TextBlob::Key::Make(const GlyphRunList& glyphRunList,
const SkPaint& paint,
const SkMatrix& drawMatrix,
const SkStrikeDeviceInfo& strikeDevice) -> std::tuple<bool, Key> {
SkASSERT(strikeDevice.fSDFTControl != nullptr);
SkMaskFilterBase::BlurRec blurRec;
// It might be worth caching these things, but its not clear at this time
// TODO for animated mask filters, this will fill up our cache. We need a safeguard here
const SkMaskFilter* maskFilter = paint.getMaskFilter();
bool canCache = glyphRunList.canCache() &&
!(paint.getPathEffect() ||
(maskFilter && !as_MFB(maskFilter)->asABlur(&blurRec)));
TextBlob::Key key;
if (canCache) {
bool hasLCD = glyphRunList.anyRunsLCD();
// We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
SkPixelGeometry pixelGeometry = hasLCD ? strikeDevice.fSurfaceProps.pixelGeometry()
: kUnknown_SkPixelGeometry;
SkColor canonicalColor = compute_canonical_color(paint, hasLCD);
key.fPixelGeometry = pixelGeometry;
key.fUniqueID = glyphRunList.uniqueID();
key.fStyle = paint.getStyle();
if (key.fStyle != SkPaint::kFill_Style) {
key.fFrameWidth = paint.getStrokeWidth();
key.fMiterLimit = paint.getStrokeMiter();
key.fJoin = paint.getStrokeJoin();
key.fHasBlur = maskFilter != nullptr;
if (key.fHasBlur) {
key.fBlurRec = blurRec;
key.fCanonicalColor = canonicalColor;
key.fScalerContextFlags = SkTo<uint32_t>(strikeDevice.fScalerContextFlags);
// Do any runs use direct drawing types?.
key.fHasSomeDirectSubRuns = false;
SkPoint glyphRunListLocation = glyphRunList.sourceBoundsWithOrigin().center();
for (auto& run : glyphRunList) {
SkScalar approximateDeviceTextSize =
SkFontPriv::ApproximateTransformedTextSize(run.font(), drawMatrix,
key.fHasSomeDirectSubRuns |=
strikeDevice.fSDFTControl->isDirect(approximateDeviceTextSize, paint,
if (key.fHasSomeDirectSubRuns) {
// Store the fractional offset of the position. We know that the matrix can't be
// perspective at this point.
SkPoint mappedOrigin = drawMatrix.mapOrigin();
key.fPositionMatrix = drawMatrix;
mappedOrigin.x() - SkScalarFloorToScalar(mappedOrigin.x()));
mappedOrigin.y() - SkScalarFloorToScalar(mappedOrigin.y()));
} else {
// For path and SDFT, the matrix doesn't matter.
key.fPositionMatrix = SkMatrix::I();
return {canCache, key};
bool TextBlob::Key::operator==(const TextBlob::Key& that) const {
if (fUniqueID != that.fUniqueID) { return false; }
if (fCanonicalColor != that.fCanonicalColor) { return false; }
if (fStyle != that.fStyle) { return false; }
if (fStyle != SkPaint::kFill_Style) {
if (fFrameWidth != that.fFrameWidth ||
fMiterLimit != that.fMiterLimit ||
fJoin != that.fJoin) {
return false;
if (fPixelGeometry != that.fPixelGeometry) { return false; }
if (fHasBlur != that.fHasBlur) { return false; }
if (fHasBlur) {
if (fBlurRec.fStyle != that.fBlurRec.fStyle || fBlurRec.fSigma != that.fBlurRec.fSigma) {
return false;
if (fScalerContextFlags != that.fScalerContextFlags) { return false; }
// DirectSubRuns do not support perspective when used with a TextBlob. SDFT, Transformed,
// Path, and Drawable do support perspective.
if (fPositionMatrix.hasPerspective() && fHasSomeDirectSubRuns) { return false; }
if (fHasSomeDirectSubRuns != that.fHasSomeDirectSubRuns) { return false; }
if (fHasSomeDirectSubRuns) {
auto [compatible, _] = can_use_direct(fPositionMatrix, that.fPositionMatrix);
return compatible;
return true;
// -- TextBlob -----------------------------------------------------------------------------------
void TextBlob::operator delete(void* p) { ::operator delete(p); }
void* TextBlob::operator new(size_t) { SK_ABORT("All blobs are created by placement new."); }
void* TextBlob::operator new(size_t, void* p) { return p; }
TextBlob::~TextBlob() = default;
sk_sp<TextBlob> TextBlob::Make(const GlyphRunList& glyphRunList,
const SkPaint& paint,
const SkMatrix& positionMatrix,
SkStrikeDeviceInfo strikeDeviceInfo,
StrikeForGPUCacheInterface* strikeCache) {
size_t subRunSizeHint = SubRunContainer::EstimateAllocSize(glyphRunList);
auto [initializer, totalMemoryAllocated, alloc] =
auto container = SubRunContainer::MakeInAlloc(
glyphRunList, positionMatrix, paint,
strikeDeviceInfo, strikeCache, &alloc, SubRunContainer::kAddSubRuns, "TextBlob");
SkColor initialLuminance = SkPaintPriv::ComputeLuminanceColor(paint);
sk_sp<TextBlob> blob = sk_sp<TextBlob>(initializer.initialize(std::move(alloc),
return blob;
void TextBlob::addKey(const Key& key) {
fKey = key;
bool TextBlob::hasPerspective() const {
return fSubRuns->initialPosition().hasPerspective();
bool TextBlob::canReuse(const SkPaint& paint, const SkMatrix& positionMatrix) const {
// A singular matrix will create a TextBlob with no SubRuns, but unknown glyphs can also
// cause empty runs. If there are no subRuns, then regenerate when the matrices don't match.
if (fSubRuns->isEmpty() && fSubRuns->initialPosition() != positionMatrix) {
return false;
// If we have LCD text then our canonical color will be set to transparent, in this case we have
// to regenerate the blob on any color change
// We use the grPaint to get any color filter effects
if (fKey.fCanonicalColor == SK_ColorTRANSPARENT &&
fInitialLuminance != SkPaintPriv::ComputeLuminanceColor(paint)) {
return false;
return fSubRuns->canReuse(paint, positionMatrix);
const TextBlob::Key& TextBlob::key() const { return fKey; }
void TextBlob::draw(SkCanvas* canvas,
const GrClip* clip,
const SkMatrixProvider& viewMatrix,
SkPoint drawOrigin,
const SkPaint& paint,
skgpu::v1::SurfaceDrawContext* sdc) {
fSubRuns->draw(canvas, clip, viewMatrix, drawOrigin, paint, this, sdc);
void TextBlob::draw(SkCanvas* canvas,
SkPoint drawOrigin,
const SkPaint& paint,
skgpu::graphite::Device* device) {
fSubRuns->draw(canvas, drawOrigin, paint, this, device);
struct SubRunContainerPeer {
static const AtlasSubRun* getAtlasSubRun(const SubRunContainer& subRuns) {
if (subRuns.isEmpty()) {
return nullptr;
return subRuns.fSubRuns.front().testingOnly_atlasSubRun();
const AtlasSubRun* TextBlob::testingOnlyFirstSubRun() const {
return SubRunContainerPeer::getAtlasSubRun(*fSubRuns);
return nullptr;
TextBlob::TextBlob(SubRunAllocator&& alloc,
SubRunContainerOwner subRuns,
int totalMemorySize,
SkColor initialLuminance)
: fAlloc{std::move(alloc)}
, fSubRuns{std::move(subRuns)}
, fSize(totalMemorySize)
, fInitialLuminance{initialLuminance} { }
sk_sp<Slug> SkMakeSlugFromBuffer(SkReadBuffer& buffer, const SkStrikeClient* client) {
return SlugImpl::MakeFromBuffer(buffer, client);
} // namespace sktext::gpu
namespace skgpu::v1 {
Device::convertGlyphRunListToSlug(const sktext::GlyphRunList& glyphRunList,
const SkPaint& initialPaint,
const SkPaint& drawingPaint) {
return SlugImpl::Make(this->asMatrixProvider(),
void Device::drawSlug(SkCanvas* canvas, const Slug* slug, const SkPaint& drawingPaint) {
const SlugImpl* slugImpl = static_cast<const SlugImpl*>(slug);
auto matrixProvider = this->asMatrixProvider();
#if defined(SK_DEBUG)
if (!fContext->priv().options().fSupportBilerpFromGlyphAtlas) {
// We can draw a slug if the atlas has padding or if the creation matrix and the
// drawing matrix are the same. If they are the same, then the Slug will use the direct
// drawing code and not use bi-lerp.
SkMatrix slugMatrix = slugImpl->initialPositionMatrix();
SkMatrix positionMatrix = matrixProvider.localToDevice();
positionMatrix.preTranslate(slugImpl->origin().x(), slugImpl->origin().y());
SkASSERT(slugMatrix == positionMatrix);
canvas, this->clip(), matrixProvider, drawingPaint, fSurfaceDrawContext.get());
sk_sp<Slug> MakeSlug(const SkMatrixProvider& drawMatrix,
const sktext::GlyphRunList& glyphRunList,
const SkPaint& initialPaint,
const SkPaint& drawingPaint,
SkStrikeDeviceInfo strikeDeviceInfo,
sktext::StrikeForGPUCacheInterface* strikeCache) {
return SlugImpl::Make(
drawMatrix, glyphRunList, initialPaint, drawingPaint, strikeDeviceInfo, strikeCache);
} // namespace skgpu::v1