Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkStrikeTest.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
12#include "include/core/SkFont.h"
16#include "include/core/SkPath.h"
18#include "include/core/SkRect.h"
28#include "src/base/SkZip.h"
29#include "src/core/SkGlyph.h"
30#include "src/core/SkMask.h"
33#include "src/core/SkStrike.h"
39#include "tests/Test.h"
40#include "tools/ToolUtils.h"
42
43#include <atomic>
44#include <cstddef>
45#include <cstdint>
46#include <functional>
47#include <initializer_list>
48#include <memory>
49#include <tuple>
50#include <vector>
51
52using namespace skia_private;
53
54using namespace sktext;
55using namespace skglyph;
56
57class Barrier {
58public:
59 Barrier(int threadCount) : fThreadCount(threadCount) { }
60 void waitForAll() {
61 fThreadCount -= 1;
62 while (fThreadCount > 0) { }
63 }
64
65private:
66 std::atomic<int> fThreadCount;
67};
68
69// This should stay in sync with the implementation from SubRunContainer.
70static
71std::tuple<SkZip<const SkPackedGlyphID, const SkPoint>,
73 SkRect>
75 StrikeForGPU* strike,
78 SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
79 SkGlyphRect boundingRect = skglyph::empty_rect();
80 int acceptedSize = 0,
81 rejectedSize = 0;
82 StrikeMutationMonitor m{strike};
83 for (auto [glyphID, pos] : source) {
84 if (!SkIsFinite(pos.x(), pos.y())) {
85 continue;
86 }
87 const SkPackedGlyphID packedID{glyphID};
88 switch (const SkGlyphDigest digest = strike->digestFor(kDirectMask, packedID);
89 digest.actionFor(kDirectMask)) {
90 case GlyphAction::kAccept: {
91 const SkGlyphRect glyphBounds = digest.bounds().offset(pos);
92 boundingRect = skglyph::rect_union(boundingRect, glyphBounds);
93 acceptedBuffer[acceptedSize++] = std::make_tuple(packedID, glyphBounds.leftTop());
94 break;
95 }
96 case GlyphAction::kReject:
97 rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
98 break;
99 default:
100 break;
101 }
102 }
103
104 return {acceptedBuffer.first(acceptedSize),
105 rejectedBuffer.first(rejectedSize),
106 boundingRect.rect()};
107}
108
109DEF_TEST(SkStrikeMultiThread, Reporter) {
110 sk_sp<SkTypeface> typeface =
112 static constexpr int kThreadCount = 4;
113
114 Barrier barrier{kThreadCount};
115
116 SkFont font;
117 font.setEdging(SkFont::Edging::kAntiAlias);
118 font.setSubpixel(true);
119 font.setTypeface(typeface);
120
121 SkGlyphID glyphs['z'];
122 SkPoint pos['z'];
123 for (int c = ' '; c < 'z'; c++) {
124 glyphs[c] = font.unicharToGlyph(c);
125 pos[c] = {30.0f * c + 30, 30.0f};
126 }
127 constexpr size_t glyphCount = 'z' - ' ';
128 auto data = SkMakeZip(glyphs, pos).subspan(SkTo<int>(' '), glyphCount);
129
130 SkPaint defaultPaint;
132 font, defaultPaint, SkSurfaceProps(0, kUnknown_SkPixelGeometry),
134
135 SkStrikeCache strikeCache;
136
137 // Make our own executor so the --threads parameter doesn't mess things up.
139 for (int tries = 0; tries < 100; tries++) {
140 SkStrike strike{&strikeCache, strikeSpec, strikeSpec.createScalerContext(), nullptr,
141 nullptr};
142
143 auto perThread = [&](int threadIndex) {
144 barrier.waitForAll();
145
146 auto local = data.subspan(threadIndex * 2, data.size() - kThreadCount * 2);
147 for (int i = 0; i < 100; i++) {
148 // Accepted buffers.
149 STArray<64, SkPackedGlyphID> acceptedPackedGlyphIDs;
150 STArray<64, SkPoint> acceptedPositions;
151 STArray<64, SkMask::Format> acceptedFormats;
152 acceptedPackedGlyphIDs.resize(glyphCount);
153 acceptedPositions.resize(glyphCount);
154 const auto acceptedBuffer = SkMakeZip(acceptedPackedGlyphIDs, acceptedPositions);
155
156 // Rejected buffers.
157 STArray<64, SkGlyphID> rejectedGlyphIDs;
158 STArray<64, SkPoint> rejectedPositions;
159 rejectedGlyphIDs.resize(glyphCount);
160 rejectedPositions.resize(glyphCount);
161 const auto rejectedBuffer = SkMakeZip(rejectedGlyphIDs, rejectedPositions);
162
164
165 auto [accepted, rejected, bounds] =
166 prepare_for_mask_drawing(&strike, source, acceptedBuffer, rejectedBuffer);
167 source = rejected;
168 }
169 };
170
171 SkTaskGroup(*executor).batch(kThreadCount, perThread);
172 }
173}
174
175class SkGlyphTestPeer {
176public:
177 static void SetGlyph(SkGlyph* glyph) {
178 // Tweak the bounds to make them unique based on glyph id.
179 const SkGlyphID uniquify = glyph->getGlyphID();
180 glyph->fAdvanceX = 10;
181 glyph->fAdvanceY = 11;
182 glyph->fLeft = -1 - uniquify;
183 glyph->fTop = -2;
184 glyph->fWidth = 8;
185 glyph->fHeight = 9;
186 glyph->fMaskFormat = SkMask::Format::kA8_Format;
187 }
188};
189
191public:
192 static SkGlyph* GetGlyph(SkStrike* strike, SkPackedGlyphID packedID) {
193 SkAutoMutexExclusive m{strike->fStrikeLock};
194 return strike->glyph(packedID);
195 }
196};
197
198DEF_TEST(SkStrike_FlattenByType, reporter) {
199 std::vector<SkGlyph> imagesToSend;
200 std::vector<SkGlyph> pathsToSend;
201 std::vector<SkGlyph> drawablesToSend;
202 SkArenaAlloc alloc{256};
203
204 // Make a mask glyph and put it in the glyphs to send.
205 const SkPackedGlyphID maskPackedGlyphID((SkGlyphID)10);
206 SkGlyph maskGlyph{maskPackedGlyphID};
207 SkGlyphTestPeer::SetGlyph(&maskGlyph);
208
209 static constexpr uint8_t X = 0xff;
210 static constexpr uint8_t O = 0x00;
211 uint8_t imageData[][8] = {
212 {X,X,X,X,X,X,X,X},
213 {X,O,O,O,O,O,O,X},
214 {X,O,O,O,O,O,O,X},
215 {X,O,O,O,O,O,O,X},
216 {X,O,O,X,X,O,O,X},
217 {X,O,O,O,O,O,O,X},
218 {X,O,O,O,O,O,O,X},
219 {X,O,O,O,O,O,O,X},
220 {X,X,X,X,X,X,X,X},
221 };
222 maskGlyph.setImage(&alloc, imageData);
223 imagesToSend.emplace_back(maskGlyph);
224
225 // Make a path glyph and put it in the glyphs to send.
226 const SkPackedGlyphID pathPackedGlyphID((SkGlyphID)11);
227 SkGlyph pathGlyph{pathPackedGlyphID};
228 SkGlyphTestPeer::SetGlyph(&pathGlyph);
229 SkPath path;
230 path.addRect(pathGlyph.rect());
231 pathGlyph.setPath(&alloc, &path, false);
232 pathsToSend.emplace_back(pathGlyph);
233
234 // Make a drawable glyph and put it in the glyphs to send.
235 const SkPackedGlyphID drawablePackedGlyphID((SkGlyphID)12);
236 SkGlyph drawableGlyph{drawablePackedGlyphID};
237 SkGlyphTestPeer::SetGlyph(&drawableGlyph);
238 class TestDrawable final : public SkDrawable {
239 public:
240 TestDrawable(SkRect rect) : fRect(rect) {}
241
242 private:
243 const SkRect fRect;
244 SkRect onGetBounds() override { return fRect; }
245 size_t onApproximateBytesUsed() override {
246 return 0;
247 }
248 void onDraw(SkCanvas* canvas) override {
250 canvas->drawRect(fRect, paint);
251 }
252 };
253
254 sk_sp<SkDrawable> drawable = sk_make_sp<TestDrawable>(drawableGlyph.rect());
255 REPORTER_ASSERT(reporter, !drawableGlyph.setDrawableHasBeenCalled());
256 drawableGlyph.setDrawable(&alloc, drawable);
257 REPORTER_ASSERT(reporter, drawableGlyph.setDrawableHasBeenCalled());
258 REPORTER_ASSERT(reporter, drawableGlyph.drawable() != nullptr);
259 drawablesToSend.emplace_back(drawableGlyph);
260
261 // Test the FlattenGlyphsByType method.
262 SkBinaryWriteBuffer writeBuffer({});
263 SkStrike::FlattenGlyphsByType(writeBuffer, imagesToSend, pathsToSend, drawablesToSend);
264 auto data = writeBuffer.snapshotAsData();
265
266 // Make a strike to merge into.
267 SkStrikeCache strikeCache;
268 auto dstTypeface = ToolUtils::CreateTestTypeface("monospace", SkFontStyle());
269 SkFont font{dstTypeface};
271 sk_sp<SkStrike> strike = spec.findOrCreateStrike(&strikeCache);
272
273 // Test the mergeFromBuffer method.
274 SkReadBuffer readBuffer{data->data(), data->size()};
275 strike->mergeFromBuffer(readBuffer);
276
277 // Check mask glyph.
278 SkGlyph* dstMaskGlyph = SkStrikeTestingPeer::GetGlyph(strike.get(), maskPackedGlyphID);
279 REPORTER_ASSERT(reporter, maskGlyph.rect() == dstMaskGlyph->rect());
281 REPORTER_ASSERT(reporter, dstMaskGlyph->image() != nullptr);
282
283 // Check path glyph.
284 SkGlyph* dstPathGlyph = SkStrikeTestingPeer::GetGlyph(strike.get(), pathPackedGlyphID);
285 REPORTER_ASSERT(reporter, pathGlyph.rect() == dstPathGlyph->rect());
287 REPORTER_ASSERT(reporter, dstPathGlyph->path() != nullptr);
288
289 // Check drawable glyph.
290 SkGlyph* dstDrawableGlyph = SkStrikeTestingPeer::GetGlyph(strike.get(),drawablePackedGlyphID);
291 REPORTER_ASSERT(reporter, drawableGlyph.rect() == dstDrawableGlyph->rect());
293 REPORTER_ASSERT(reporter, dstDrawableGlyph->drawable() != nullptr);
294}
SkRect fRect
reporter
uint16_t glyphs[5]
SkPoint pos
static bool SkIsFinite(T x, Pack... values)
static std::tuple< SkZip< const SkPackedGlyphID, const SkPoint >, SkZip< SkGlyphID, SkPoint >, SkRect > prepare_for_mask_drawing(StrikeForGPU *strike, SkZip< const SkGlyphID, const SkPoint > source, SkZip< SkPackedGlyphID, SkPoint > acceptedBuffer, SkZip< SkGlyphID, SkPoint > rejectedBuffer)
@ kUnknown_SkPixelGeometry
uint16_t SkGlyphID
Definition SkTypes.h:179
constexpr auto SkMakeZip(Ts &&... ts)
Definition SkZip.h:212
static const SkScalar X
#define DEF_TEST(name, reporter)
Definition Test.h:312
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
Barrier(int threadCount)
void waitForAll()
void drawRect(const SkRect &rect, const SkPaint &paint)
virtual SkRect onGetBounds()=0
virtual void onDraw(SkCanvas *)=0
virtual size_t onApproximateBytesUsed()
static std::unique_ptr< SkExecutor > MakeFIFOThreadPool(int threads=0, bool allowBorrowing=true)
static constexpr SkFontStyle Italic()
Definition SkFontStyle.h:72
@ kAntiAlias
may have transparent pixels on glyph edges
skglyph::GlyphAction actionFor(skglyph::ActionType actionType) const
Definition SkGlyph.h:343
SkPoint leftTop() const
Definition SkGlyph.h:275
SkRect rect() const
Definition SkGlyph.h:259
SkGlyphRect offset(SkScalar x, SkScalar y) const
Definition SkGlyph.h:262
static void SetGlyph(SkGlyph *glyph)
bool setPathHasBeenCalled() const
Definition SkGlyph.h:486
SkGlyphID getGlyphID() const
Definition SkGlyph.h:429
bool setDrawableHasBeenCalled() const
Definition SkGlyph.h:495
bool setImageHasBeenCalled() const
Definition SkGlyph.h:459
const SkPath * path() const
Definition SkGlyph.cpp:284
SkRect rect() const
Definition SkGlyph.h:506
SkDrawable * drawable() const
Definition SkGlyph.cpp:327
const void * image() const
Definition SkGlyph.h:465
static const SkMatrix & I()
static SkStrikeSpec MakeMask(const SkFont &font, const SkPaint &paint, const SkSurfaceProps &surfaceProps, SkScalerContextFlags scalerContextFlags, const SkMatrix &deviceMatrix)
static SkStrikeSpec MakeWithNoDevice(const SkFont &font, const SkPaint *paint=nullptr)
std::unique_ptr< SkScalerContext > createScalerContext() const
sk_sp< SkStrike > findOrCreateStrike() const
static SkGlyph * GetGlyph(SkStrike *strike, SkPackedGlyphID packedID)
static void FlattenGlyphsByType(SkWriteBuffer &buffer, SkSpan< SkGlyph > images, SkSpan< SkGlyph > paths, SkSpan< SkGlyph > drawables)
Definition SkStrike.cpp:87
SkGlyph * glyph(SkGlyphDigest) SK_REQUIRES(fStrikeLock)
Definition SkStrike.cpp:322
void batch(int N, std::function< void(int)> fn)
Definition SkZip.h:25
constexpr SkZip first(size_t n) const
Definition SkZip.h:86
T * get() const
Definition SkRefCnt.h:303
void resize(size_t count)
Definition SkTArray.h:418
virtual SkGlyphDigest digestFor(skglyph::ActionType, SkPackedGlyphID)=0
const Paint & paint
SkBitmap source
Definition examples.cpp:28
static constexpr uint64_t kThreadCount
sk_sp< SkTypeface > CreatePortableTypeface(const char *name, SkFontStyle style)
sk_sp< SkTypeface > CreateTestTypeface(const char *name, SkFontStyle style)
@ kA8_Format
8bits per pixel mask (e.g. antialiasing)
Definition SkMask.h:28
constexpr float y() const
constexpr float x() const