/* * Copyright 2020 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkCanvas.h" #include "include/core/SkData.h" #include "include/core/SkDrawable.h" #include "include/core/SkExecutor.h" #include "include/core/SkFont.h" #include "include/core/SkFontStyle.h" #include "include/core/SkMatrix.h" #include "include/core/SkPaint.h" #include "include/core/SkPath.h" #include "include/core/SkPoint.h" #include "include/core/SkRect.h" #include "include/core/SkRefCnt.h" #include "include/core/SkScalar.h" #include "include/core/SkSurfaceProps.h" #include "include/core/SkTypeface.h" #include "include/core/SkTypes.h" #include "include/private/base/SkMutex.h" #include "include/private/base/SkTArray.h" #include "include/private/base/SkTo.h" #include "src/base/SkArenaAlloc.h" #include "src/base/SkZip.h" #include "src/core/SkGlyph.h" #include "src/core/SkMask.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/SkTaskGroup.h" #include "src/core/SkWriteBuffer.h" #include "src/text/StrikeForGPU.h" #include "tests/Test.h" #include "tools/ToolUtils.h" #include "tools/fonts/FontToolUtils.h" #include #include #include #include #include #include #include #include using namespace skia_private; using namespace sktext; using namespace skglyph; class Barrier { public: Barrier(int threadCount) : fThreadCount(threadCount) { } void waitForAll() { fThreadCount -= 1; while (fThreadCount > 0) { } } private: std::atomic fThreadCount; }; // This should stay in sync with the implementation from SubRunContainer. static std::tuple, SkZip, SkRect> prepare_for_mask_drawing( StrikeForGPU* strike, SkZip source, SkZip acceptedBuffer, SkZip rejectedBuffer) { SkGlyphRect boundingRect = skglyph::empty_rect(); int acceptedSize = 0, rejectedSize = 0; StrikeMutationMonitor m{strike}; for (auto [glyphID, pos] : source) { if (!SkIsFinite(pos.x(), pos.y())) { continue; } const SkPackedGlyphID packedID{glyphID}; switch (const SkGlyphDigest digest = strike->digestFor(kDirectMask, packedID); digest.actionFor(kDirectMask)) { case GlyphAction::kAccept: { const SkGlyphRect glyphBounds = digest.bounds().offset(pos); boundingRect = skglyph::rect_union(boundingRect, glyphBounds); acceptedBuffer[acceptedSize++] = std::make_tuple(packedID, glyphBounds.leftTop()); break; } case GlyphAction::kReject: rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos); break; default: break; } } return {acceptedBuffer.first(acceptedSize), rejectedBuffer.first(rejectedSize), boundingRect.rect()}; } DEF_TEST(SkStrikeMultiThread, Reporter) { sk_sp typeface = ToolUtils::CreatePortableTypeface("serif", SkFontStyle::Italic()); static constexpr int kThreadCount = 4; Barrier barrier{kThreadCount}; SkFont font; font.setEdging(SkFont::Edging::kAntiAlias); font.setSubpixel(true); font.setTypeface(typeface); SkGlyphID glyphs['z']; SkPoint pos['z']; for (int c = ' '; c < 'z'; c++) { glyphs[c] = font.unicharToGlyph(c); pos[c] = {30.0f * c + 30, 30.0f}; } constexpr size_t glyphCount = 'z' - ' '; auto data = SkMakeZip(glyphs, pos).subspan(SkTo(' '), glyphCount); SkPaint defaultPaint; SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask( font, defaultPaint, SkSurfaceProps(0, kUnknown_SkPixelGeometry), SkScalerContextFlags::kNone, SkMatrix::I()); SkStrikeCache strikeCache; // Make our own executor so the --threads parameter doesn't mess things up. auto executor = SkExecutor::MakeFIFOThreadPool(kThreadCount); for (int tries = 0; tries < 100; tries++) { SkStrike strike{&strikeCache, strikeSpec, strikeSpec.createScalerContext(), nullptr, nullptr}; auto perThread = [&](int threadIndex) { barrier.waitForAll(); auto local = data.subspan(threadIndex * 2, data.size() - kThreadCount * 2); for (int i = 0; i < 100; i++) { // Accepted buffers. STArray<64, SkPackedGlyphID> acceptedPackedGlyphIDs; STArray<64, SkPoint> acceptedPositions; STArray<64, SkMask::Format> acceptedFormats; acceptedPackedGlyphIDs.resize(glyphCount); acceptedPositions.resize(glyphCount); const auto acceptedBuffer = SkMakeZip(acceptedPackedGlyphIDs, acceptedPositions); // Rejected buffers. STArray<64, SkGlyphID> rejectedGlyphIDs; STArray<64, SkPoint> rejectedPositions; rejectedGlyphIDs.resize(glyphCount); rejectedPositions.resize(glyphCount); const auto rejectedBuffer = SkMakeZip(rejectedGlyphIDs, rejectedPositions); SkZip source = local; auto [accepted, rejected, bounds] = prepare_for_mask_drawing(&strike, source, acceptedBuffer, rejectedBuffer); source = rejected; } }; SkTaskGroup(*executor).batch(kThreadCount, perThread); } } class SkGlyphTestPeer { public: static void SetGlyph(SkGlyph* glyph) { // Tweak the bounds to make them unique based on glyph id. const SkGlyphID uniquify = glyph->getGlyphID(); glyph->fAdvanceX = 10; glyph->fAdvanceY = 11; glyph->fLeft = -1 - uniquify; glyph->fTop = -2; glyph->fWidth = 8; glyph->fHeight = 9; glyph->fMaskFormat = SkMask::Format::kA8_Format; } }; class SkStrikeTestingPeer { public: static SkGlyph* GetGlyph(SkStrike* strike, SkPackedGlyphID packedID) { SkAutoMutexExclusive m{strike->fStrikeLock}; return strike->glyph(packedID); } }; DEF_TEST(SkStrike_FlattenByType, reporter) { std::vector imagesToSend; std::vector pathsToSend; std::vector drawablesToSend; SkArenaAlloc alloc{256}; // Make a mask glyph and put it in the glyphs to send. const SkPackedGlyphID maskPackedGlyphID((SkGlyphID)10); SkGlyph maskGlyph{maskPackedGlyphID}; SkGlyphTestPeer::SetGlyph(&maskGlyph); static constexpr uint8_t X = 0xff; static constexpr uint8_t O = 0x00; uint8_t imageData[][8] = { {X,X,X,X,X,X,X,X}, {X,O,O,O,O,O,O,X}, {X,O,O,O,O,O,O,X}, {X,O,O,O,O,O,O,X}, {X,O,O,X,X,O,O,X}, {X,O,O,O,O,O,O,X}, {X,O,O,O,O,O,O,X}, {X,O,O,O,O,O,O,X}, {X,X,X,X,X,X,X,X}, }; maskGlyph.setImage(&alloc, imageData); imagesToSend.emplace_back(maskGlyph); // Make a path glyph and put it in the glyphs to send. const SkPackedGlyphID pathPackedGlyphID((SkGlyphID)11); SkGlyph pathGlyph{pathPackedGlyphID}; SkGlyphTestPeer::SetGlyph(&pathGlyph); SkPath path; path.addRect(pathGlyph.rect()); pathGlyph.setPath(&alloc, &path, false, false); pathsToSend.emplace_back(pathGlyph); // Make a drawable glyph and put it in the glyphs to send. const SkPackedGlyphID drawablePackedGlyphID((SkGlyphID)12); SkGlyph drawableGlyph{drawablePackedGlyphID}; SkGlyphTestPeer::SetGlyph(&drawableGlyph); class TestDrawable final : public SkDrawable { public: TestDrawable(SkRect rect) : fRect(rect) {} private: const SkRect fRect; SkRect onGetBounds() override { return fRect; } size_t onApproximateBytesUsed() override { return 0; } void onDraw(SkCanvas* canvas) override { SkPaint paint; canvas->drawRect(fRect, paint); } }; sk_sp drawable = sk_make_sp(drawableGlyph.rect()); REPORTER_ASSERT(reporter, !drawableGlyph.setDrawableHasBeenCalled()); drawableGlyph.setDrawable(&alloc, drawable); REPORTER_ASSERT(reporter, drawableGlyph.setDrawableHasBeenCalled()); REPORTER_ASSERT(reporter, drawableGlyph.drawable() != nullptr); drawablesToSend.emplace_back(drawableGlyph); // Test the FlattenGlyphsByType method. SkBinaryWriteBuffer writeBuffer({}); SkStrike::FlattenGlyphsByType(writeBuffer, imagesToSend, pathsToSend, drawablesToSend); auto data = writeBuffer.snapshotAsData(); // Make a strike to merge into. SkStrikeCache strikeCache; auto dstTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle()); SkFont font{dstTypeface}; SkStrikeSpec spec = SkStrikeSpec::MakeWithNoDevice(font); sk_sp strike = spec.findOrCreateStrike(&strikeCache); // Test the mergeFromBuffer method. SkReadBuffer readBuffer{data->data(), data->size()}; strike->mergeFromBuffer(readBuffer); // Check mask glyph. SkGlyph* dstMaskGlyph = SkStrikeTestingPeer::GetGlyph(strike.get(), maskPackedGlyphID); REPORTER_ASSERT(reporter, maskGlyph.rect() == dstMaskGlyph->rect()); REPORTER_ASSERT(reporter, dstMaskGlyph->setImageHasBeenCalled()); REPORTER_ASSERT(reporter, dstMaskGlyph->image() != nullptr); // Check path glyph. SkGlyph* dstPathGlyph = SkStrikeTestingPeer::GetGlyph(strike.get(), pathPackedGlyphID); REPORTER_ASSERT(reporter, pathGlyph.rect() == dstPathGlyph->rect()); REPORTER_ASSERT(reporter, dstPathGlyph->setPathHasBeenCalled()); REPORTER_ASSERT(reporter, dstPathGlyph->path() != nullptr); // Check drawable glyph. SkGlyph* dstDrawableGlyph = SkStrikeTestingPeer::GetGlyph(strike.get(),drawablePackedGlyphID); REPORTER_ASSERT(reporter, drawableGlyph.rect() == dstDrawableGlyph->rect()); REPORTER_ASSERT(reporter, dstDrawableGlyph->setDrawableHasBeenCalled()); REPORTER_ASSERT(reporter, dstDrawableGlyph->drawable() != nullptr); }