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
8 #include "include/core/SkCanvas.h"
9 #include "include/core/SkData.h"
10 #include "include/core/SkDrawable.h"
11 #include "include/core/SkExecutor.h"
12 #include "include/core/SkFont.h"
13 #include "include/core/SkFontStyle.h"
14 #include "include/core/SkMatrix.h"
15 #include "include/core/SkPaint.h"
16 #include "include/core/SkPath.h"
17 #include "include/core/SkPoint.h"
18 #include "include/core/SkRect.h"
19 #include "include/core/SkRefCnt.h"
20 #include "include/core/SkScalar.h"
21 #include "include/core/SkSurfaceProps.h"
22 #include "include/core/SkTypeface.h"
23 #include "include/core/SkTypes.h"
24 #include "include/private/base/SkMutex.h"
25 #include "include/private/base/SkTArray.h"
26 #include "include/private/base/SkTo.h"
27 #include "src/base/SkArenaAlloc.h"
28 #include "src/base/SkZip.h"
29 #include "src/core/SkGlyph.h"
30 #include "src/core/SkMask.h"
31 #include "src/core/SkReadBuffer.h"
32 #include "src/core/SkScalerContext.h"
33 #include "src/core/SkStrike.h"
34 #include "src/core/SkStrikeCache.h"
35 #include "src/core/SkStrikeSpec.h"
36 #include "src/core/SkTaskGroup.h"
37 #include "src/core/SkWriteBuffer.h"
38 #include "src/text/StrikeForGPU.h"
39 #include "tests/Test.h"
40 #include "tools/ToolUtils.h"
41 #include "tools/fonts/FontToolUtils.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
52 using namespace skia_private;
53
54 using namespace sktext;
55 using namespace skglyph;
56
57 class Barrier {
58 public:
Barrier(int threadCount)59 Barrier(int threadCount) : fThreadCount(threadCount) { }
waitForAll()60 void waitForAll() {
61 fThreadCount -= 1;
62 while (fThreadCount > 0) { }
63 }
64
65 private:
66 std::atomic<int> fThreadCount;
67 };
68
69 // This should stay in sync with the implementation from SubRunContainer.
70 static
71 std::tuple<SkZip<const SkPackedGlyphID, const SkPoint>,
72 SkZip<SkGlyphID, SkPoint>,
73 SkRect>
prepare_for_mask_drawing(StrikeForGPU * strike,SkZip<const SkGlyphID,const SkPoint> source,SkZip<SkPackedGlyphID,SkPoint> acceptedBuffer,SkZip<SkGlyphID,SkPoint> rejectedBuffer)74 prepare_for_mask_drawing(
75 StrikeForGPU* strike,
76 SkZip<const SkGlyphID, const SkPoint> source,
77 SkZip<SkPackedGlyphID, SkPoint> acceptedBuffer,
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
DEF_TEST(SkStrikeMultiThread,Reporter)109 DEF_TEST(SkStrikeMultiThread, Reporter) {
110 sk_sp<SkTypeface> typeface =
111 ToolUtils::CreatePortableTypeface("serif", SkFontStyle::Italic());
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;
131 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
132 font, defaultPaint, SkSurfaceProps(0, kUnknown_SkPixelGeometry),
133 SkScalerContextFlags::kNone, SkMatrix::I());
134
135 SkStrikeCache strikeCache;
136
137 // Make our own executor so the --threads parameter doesn't mess things up.
138 auto executor = SkExecutor::MakeFIFOThreadPool(kThreadCount);
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
163 SkZip<const SkGlyphID, const SkPoint> source = local;
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
175 class SkGlyphTestPeer {
176 public:
SetGlyph(SkGlyph * glyph)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
190 class SkStrikeTestingPeer {
191 public:
GetGlyph(SkStrike * strike,SkPackedGlyphID packedID)192 static SkGlyph* GetGlyph(SkStrike* strike, SkPackedGlyphID packedID) {
193 SkAutoMutexExclusive m{strike->fStrikeLock};
194 return strike->glyph(packedID);
195 }
196 };
197
DEF_TEST(SkStrike_FlattenByType,reporter)198 DEF_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, 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 {
249 SkPaint paint;
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};
270 SkStrikeSpec spec = SkStrikeSpec::MakeWithNoDevice(font);
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());
280 REPORTER_ASSERT(reporter, dstMaskGlyph->setImageHasBeenCalled());
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());
286 REPORTER_ASSERT(reporter, dstPathGlyph->setPathHasBeenCalled());
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());
292 REPORTER_ASSERT(reporter, dstDrawableGlyph->setDrawableHasBeenCalled());
293 REPORTER_ASSERT(reporter, dstDrawableGlyph->drawable() != nullptr);
294 }
295