blob: b9b8b47a6ec099b00aa312ebefdfff89e1c3c1d9 [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.
*/
// -- GPU Text -------------------------------------------------------------------------------------
// Naming conventions
// * drawMatrix - the CTM from the canvas.
// * drawOrigin - the x, y location of the drawTextBlob call.
// * positionMatrix - this is the combination of the drawMatrix and the drawOrigin:
// positionMatrix = drawMatrix * TranslationMatrix(drawOrigin.x, drawOrigin.y);
//
// Note:
// In order to transform Slugs, you need to set the fSupportBilerpFromGlyphAtlas on
// GrContextOptions.
#include "src/text/gpu/TextBlob.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkScalar.h"
#include "include/private/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/GrClip.h"
#include "src/gpu/ganesh/v1/Device_v1.h"
#include "src/gpu/ganesh/v1/SurfaceDrawContext_v1.h"
#endif
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()),
translation};
}
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
return SK_ColorTRANSPARENT;
} 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),
SkColorGetG(canonicalColor),
SkColorGetB(canonicalColor));
// reduce to our finite number of bits
canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
}
return canonicalColor;
}
// -- SlugImpl -------------------------------------------------------------------------------------
class SlugImpl final : public Slug {
public:
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;
#if SK_SUPPORT_GPU
void surfaceDraw(SkCanvas*,
const GrClip* clip,
const SkMatrixProvider& viewMatrix,
const SkPaint& paint,
skgpu::v1::SurfaceDrawContext* sdc) const;
#endif
SkRect sourceBounds() const override { return fSourceBounds; }
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; }
private:
// 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} {}
#if SK_SUPPORT_GPU
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);
}
#endif
void SlugImpl::doFlatten(SkWriteBuffer& buffer) const {
buffer.writeRect(fSourceBounds);
SkPaintPriv::Flatten(fInitialPaint, buffer);
buffer.writePoint(fOrigin);
fSubRuns->flattenAllocSizeHint(buffer);
fSubRuns->flattenRuns(buffer);
}
sk_sp<Slug> SlugImpl::MakeFromBuffer(SkReadBuffer& buffer, const SkStrikeClient* client) {
SkRect sourceBounds = buffer.readRect();
SkASSERT(!sourceBounds.isEmpty());
if (!buffer.validate(!sourceBounds.isEmpty())) { return nullptr; }
SkPaint paint = buffer.readPaint();
SkPoint origin = buffer.readPoint();
int allocSizeHint = SubRunContainer::AllocSizeHintFromBuffer(buffer);
auto [initializer, _, alloc] =
SubRunAllocator::AllocateClassMemoryAndArena<SlugImpl>(allocSizeHint);
SubRunContainerOwner container = SubRunContainer::MakeFromBufferInAlloc(buffer, client, &alloc);
// Something went wrong while reading.
SkASSERT(buffer.isValid());
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] =
SubRunAllocator::AllocateClassMemoryAndArena<SlugImpl>(subRunSizeHint);
const SkMatrix positionMatrix =
position_matrix(viewMatrix.localToDevice(), glyphRunList.origin());
auto [__, subRuns] = SubRunContainer::MakeInAlloc(glyphRunList,
positionMatrix,
drawingPaint,
strikeDeviceInfo,
strikeCache,
&alloc,
SubRunContainer::kAddSubRuns,
"Make Slug");
sk_sp<SlugImpl> slug = sk_sp<SlugImpl>(initializer.initialize(
std::move(alloc),
std::move(subRuns),
glyphRunList.sourceBounds(),
initialPaint,
glyphRunList.origin()));
// 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;
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;
key.fPositionMatrix.setTranslateX(
mappedOrigin.x() - SkScalarFloorToScalar(mappedOrigin.x()));
key.fPositionMatrix.setTranslateY(
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; }
if (fPositionMatrix.hasPerspective()) {
if (fPositionMatrix[SkMatrix::kMPersp0] != that.fPositionMatrix[SkMatrix::kMPersp0] ||
fPositionMatrix[SkMatrix::kMPersp1] != that.fPositionMatrix[SkMatrix::kMPersp1] ||
fPositionMatrix[SkMatrix::kMPersp2] != that.fPositionMatrix[SkMatrix::kMPersp2]) {
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] =
SubRunAllocator::AllocateClassMemoryAndArena<TextBlob>(subRunSizeHint);
auto [someGlyphExcluded, 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),
std::move(container),
totalMemoryAllocated,
initialLuminance));
// Be sure to pass the ref to the matrix that the SubRuns will capture.
blob->fSomeGlyphsExcluded = someGlyphExcluded;
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 or some glyphs were excluded or perspective,
// then regenerate when the matrices don't match.
if ((fSubRuns->isEmpty() || fSomeGlyphsExcluded || hasPerspective()) &&
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; }
#if SK_SUPPORT_GPU
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);
}
#endif
#if defined(SK_GRAPHITE_ENABLED)
void TextBlob::draw(SkCanvas* canvas,
SkPoint drawOrigin,
const SkPaint& paint,
skgpu::graphite::Device* device) {
fSubRuns->draw(canvas, drawOrigin, paint, this, device);
}
#endif
#if GR_TEST_UTILS
struct SubRunContainerPeer {
static const AtlasSubRun* getAtlasSubRun(const SubRunContainer& subRuns) {
if (subRuns.isEmpty()) {
return nullptr;
}
return subRuns.fSubRuns.front().testingOnly_atlasSubRun();
}
};
#endif
const AtlasSubRun* TextBlob::testingOnlyFirstSubRun() const {
#if GR_TEST_UTILS
return SubRunContainerPeer::getAtlasSubRun(*fSubRuns);
#else
return nullptr;
#endif
}
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
#if SK_SUPPORT_GPU
namespace skgpu::v1 {
sk_sp<Slug>
Device::convertGlyphRunListToSlug(const sktext::GlyphRunList& glyphRunList,
const SkPaint& initialPaint,
const SkPaint& drawingPaint) {
return SlugImpl::Make(this->asMatrixProvider(),
glyphRunList,
initialPaint,
drawingPaint,
this->strikeDeviceInfo(),
SkStrikeCache::GlobalStrikeCache());
}
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);
}
#endif
slugImpl->surfaceDraw(
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
#endif