1/*
2 * Copyright 2011 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/gpu/GrContext.h"
9
10#include "include/core/SkDeferredDisplayList.h"
11#include "include/core/SkTraceMemoryDump.h"
12#include "include/gpu/GrBackendSemaphore.h"
13#include "include/private/SkImageInfoPriv.h"
14#include "src/core/SkMipMap.h"
15#include "src/core/SkTaskGroup.h"
16#include "src/gpu/GrClientMappedBufferManager.h"
17#include "src/gpu/GrContextPriv.h"
18#include "src/gpu/GrDrawingManager.h"
19#include "src/gpu/GrGpu.h"
20#include "src/gpu/GrMemoryPool.h"
21#include "src/gpu/GrPathRendererChain.h"
22#include "src/gpu/GrProxyProvider.h"
23#include "src/gpu/GrRenderTargetProxy.h"
24#include "src/gpu/GrResourceCache.h"
25#include "src/gpu/GrResourceProvider.h"
26#include "src/gpu/GrSemaphore.h"
27#include "src/gpu/GrShaderUtils.h"
28#include "src/gpu/GrSoftwarePathRenderer.h"
29#include "src/gpu/GrTracing.h"
30#include "src/gpu/SkGr.h"
31#include "src/gpu/ccpr/GrCoverageCountingPathRenderer.h"
32#include "src/gpu/effects/GrSkSLFP.h"
33#include "src/gpu/text/GrStrikeCache.h"
34#include "src/gpu/text/GrTextBlobCache.h"
35#include "src/gpu/text/GrTextContext.h"
36#include "src/image/SkImage_GpuBase.h"
37#include "src/image/SkSurface_Gpu.h"
38#include <atomic>
39
40#define ASSERT_OWNED_PROXY(P) \
41 SkASSERT(!(P) || !((P)->peekTexture()) || (P)->peekTexture()->getContext() == this)
42
43#define ASSERT_OWNED_RESOURCE(R) SkASSERT(!(R) || (R)->getContext() == this)
44#define ASSERT_SINGLE_OWNER \
45 SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(this->singleOwner());)
46#define RETURN_IF_ABANDONED if (this->abandoned()) { return; }
47#define RETURN_FALSE_IF_ABANDONED if (this->abandoned()) { return false; }
48#define RETURN_NULL_IF_ABANDONED if (this->abandoned()) { return nullptr; }
49
50////////////////////////////////////////////////////////////////////////////////
51
52GrContext::GrContext(GrBackendApi backend, const GrContextOptions& options, int32_t contextID)
53 : INHERITED(backend, options, contextID) {
54 fResourceCache = nullptr;
55 fResourceProvider = nullptr;
56}
57
58GrContext::~GrContext() {
59 ASSERT_SINGLE_OWNER
60
61 if (this->drawingManager()) {
62 this->drawingManager()->cleanup();
63 }
64 delete fResourceProvider;
65 delete fResourceCache;
66}
67
68bool GrContext::init(sk_sp<const GrCaps> caps) {
69 ASSERT_SINGLE_OWNER
70 SkASSERT(fThreadSafeProxy); // needs to have been initialized by derived classes
71 SkASSERT(this->proxyProvider());
72
73 if (!INHERITED::init(std::move(caps))) {
74 return false;
75 }
76
77 SkASSERT(this->caps());
78 SkASSERT(this->getTextBlobCache());
79
80 if (fGpu) {
81 fStrikeCache.reset(new GrStrikeCache{});
82 fResourceCache = new GrResourceCache(this->caps(), this->singleOwner(), this->contextID());
83 fResourceProvider = new GrResourceProvider(fGpu.get(), fResourceCache, this->singleOwner());
84 fMappedBufferManager = std::make_unique<GrClientMappedBufferManager>(this->contextID());
85 }
86
87 if (fResourceCache) {
88 fResourceCache->setProxyProvider(this->proxyProvider());
89 }
90
91 fDidTestPMConversions = false;
92
93 // DDL TODO: we need to think through how the task group & persistent cache
94 // get passed on to/shared between all the DDLRecorders created with this context.
95 if (this->options().fExecutor) {
96 fTaskGroup = std::make_unique<SkTaskGroup>(*this->options().fExecutor);
97 }
98
99 fPersistentCache = this->options().fPersistentCache;
100 fShaderErrorHandler = this->options().fShaderErrorHandler;
101 if (!fShaderErrorHandler) {
102 fShaderErrorHandler = GrShaderUtils::DefaultShaderErrorHandler();
103 }
104
105 return true;
106}
107
108sk_sp<GrContextThreadSafeProxy> GrContext::threadSafeProxy() {
109 return fThreadSafeProxy;
110}
111
112//////////////////////////////////////////////////////////////////////////////
113
114void GrContext::abandonContext() {
115 if (INHERITED::abandoned()) {
116 return;
117 }
118
119 INHERITED::abandonContext();
120
121 fStrikeCache->freeAll();
122
123 fMappedBufferManager->abandon();
124
125 fResourceProvider->abandon();
126
127 // Need to cleanup the drawing manager first so all the render targets
128 // will be released/forgotten before they too are abandoned.
129 this->drawingManager()->cleanup();
130
131 // abandon first to so destructors
132 // don't try to free the resources in the API.
133 fResourceCache->abandonAll();
134
135 fGpu->disconnect(GrGpu::DisconnectType::kAbandon);
136
137 fMappedBufferManager.reset();
138}
139
140void GrContext::releaseResourcesAndAbandonContext() {
141 if (INHERITED::abandoned()) {
142 return;
143 }
144
145 INHERITED::abandonContext();
146
147 fMappedBufferManager.reset();
148
149 fResourceProvider->abandon();
150
151 // Need to cleanup the drawing manager first so all the render targets
152 // will be released/forgotten before they too are abandoned.
153 this->drawingManager()->cleanup();
154
155 // Release all resources in the backend 3D API.
156 fResourceCache->releaseAll();
157
158 fGpu->disconnect(GrGpu::DisconnectType::kCleanup);
159}
160
161bool GrContext::abandoned() {
162 if (INHERITED::abandoned()) {
163 return true;
164 }
165
166 if (fGpu && fGpu->isDeviceLost()) {
167 this->abandonContext();
168 return true;
169 }
170 return false;
171}
172
173void GrContext::resetGLTextureBindings() {
174 if (this->abandoned() || this->backend() != GrBackendApi::kOpenGL) {
175 return;
176 }
177 fGpu->resetTextureBindings();
178}
179
180void GrContext::resetContext(uint32_t state) {
181 ASSERT_SINGLE_OWNER
182 fGpu->markContextDirty(state);
183}
184
185void GrContext::freeGpuResources() {
186 ASSERT_SINGLE_OWNER
187
188 // TODO: the glyph cache doesn't hold any GpuResources so this call should not be needed here.
189 // Some slack in the GrTextBlob's implementation requires it though. That could be fixed.
190 fStrikeCache->freeAll();
191
192 this->drawingManager()->freeGpuResources();
193
194 fResourceCache->purgeAllUnlocked();
195}
196
197void GrContext::purgeUnlockedResources(bool scratchResourcesOnly) {
198 ASSERT_SINGLE_OWNER
199
200 if (this->abandoned()) {
201 return;
202 }
203
204 fResourceCache->purgeUnlockedResources(scratchResourcesOnly);
205 fResourceCache->purgeAsNeeded();
206
207 // The textBlob Cache doesn't actually hold any GPU resource but this is a convenient
208 // place to purge stale blobs
209 this->getTextBlobCache()->purgeStaleBlobs();
210}
211
212void GrContext::performDeferredCleanup(std::chrono::milliseconds msNotUsed) {
213 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
214
215 ASSERT_SINGLE_OWNER
216
217 if (this->abandoned()) {
218 return;
219 }
220
221 fMappedBufferManager->process();
222 auto purgeTime = GrStdSteadyClock::now() - msNotUsed;
223
224 fResourceCache->purgeAsNeeded();
225 fResourceCache->purgeResourcesNotUsedSince(purgeTime);
226
227 if (auto ccpr = this->drawingManager()->getCoverageCountingPathRenderer()) {
228 ccpr->purgeCacheEntriesOlderThan(this->proxyProvider(), purgeTime);
229 }
230
231 // The textBlob Cache doesn't actually hold any GPU resource but this is a convenient
232 // place to purge stale blobs
233 this->getTextBlobCache()->purgeStaleBlobs();
234}
235
236void GrContext::purgeUnlockedResources(size_t bytesToPurge, bool preferScratchResources) {
237 ASSERT_SINGLE_OWNER
238
239 if (this->abandoned()) {
240 return;
241 }
242
243 fResourceCache->purgeUnlockedResources(bytesToPurge, preferScratchResources);
244}
245
246void GrContext::getResourceCacheUsage(int* resourceCount, size_t* resourceBytes) const {
247 ASSERT_SINGLE_OWNER
248
249 if (resourceCount) {
250 *resourceCount = fResourceCache->getBudgetedResourceCount();
251 }
252 if (resourceBytes) {
253 *resourceBytes = fResourceCache->getBudgetedResourceBytes();
254 }
255}
256
257size_t GrContext::getResourceCachePurgeableBytes() const {
258 ASSERT_SINGLE_OWNER
259 return fResourceCache->getPurgeableBytes();
260}
261
262size_t GrContext::ComputeImageSize(sk_sp<SkImage> image, GrMipMapped mipMapped, bool useNextPow2) {
263 if (!image->isTextureBacked()) {
264 return 0;
265 }
266 SkImage_GpuBase* gpuImage = static_cast<SkImage_GpuBase*>(as_IB(image.get()));
267 GrTextureProxy* proxy = gpuImage->peekProxy();
268 if (!proxy) {
269 return 0;
270 }
271
272 const GrCaps& caps = *gpuImage->context()->priv().caps();
273 int colorSamplesPerPixel = 1;
274 return GrSurface::ComputeSize(caps, proxy->backendFormat(), image->dimensions(),
275 colorSamplesPerPixel, mipMapped, useNextPow2);
276}
277
278////////////////////////////////////////////////////////////////////////////////
279
280int GrContext::maxTextureSize() const { return this->caps()->maxTextureSize(); }
281
282int GrContext::maxRenderTargetSize() const { return this->caps()->maxRenderTargetSize(); }
283
284bool GrContext::colorTypeSupportedAsImage(SkColorType colorType) const {
285 GrBackendFormat format =
286 this->caps()->getDefaultBackendFormat(SkColorTypeToGrColorType(colorType),
287 GrRenderable::kNo);
288 return format.isValid();
289}
290
291int GrContext::maxSurfaceSampleCountForColorType(SkColorType colorType) const {
292 GrBackendFormat format =
293 this->caps()->getDefaultBackendFormat(SkColorTypeToGrColorType(colorType),
294 GrRenderable::kYes);
295 return this->caps()->maxRenderTargetSampleCount(format);
296}
297
298////////////////////////////////////////////////////////////////////////////////
299
300bool GrContext::wait(int numSemaphores, const GrBackendSemaphore waitSemaphores[]) {
301 if (!fGpu || fGpu->caps()->semaphoreSupport()) {
302 return false;
303 }
304 for (int i = 0; i < numSemaphores; ++i) {
305 std::unique_ptr<GrSemaphore> sema = fResourceProvider->wrapBackendSemaphore(
306 waitSemaphores[i], GrResourceProvider::SemaphoreWrapType::kWillWait,
307 kAdopt_GrWrapOwnership);
308 fGpu->waitSemaphore(sema.get());
309 }
310 return true;
311}
312
313////////////////////////////////////////////////////////////////////////////////
314
315GrSemaphoresSubmitted GrContext::flush(const GrFlushInfo& info,
316 const GrPrepareForExternalIORequests& externalRequests) {
317 ASSERT_SINGLE_OWNER
318 if (this->abandoned()) {
319 return GrSemaphoresSubmitted::kNo;
320 }
321
322 bool submitted = false;
323 if (this->drawingManager()->flush(nullptr, 0, SkSurface::BackendSurfaceAccess::kNoAccess,
324 info, externalRequests)) {
325 bool forceSync = SkToBool(info.fFlags & kSyncCpu_GrFlushFlag);
326 submitted = this->drawingManager()->submitToGpu(forceSync);
327 }
328
329 if (!submitted || (!this->priv().caps()->semaphoreSupport() && info.fNumSemaphores)) {
330 return GrSemaphoresSubmitted::kNo;
331 }
332 return GrSemaphoresSubmitted::kYes;
333}
334
335////////////////////////////////////////////////////////////////////////////////
336
337void GrContext::checkAsyncWorkCompletion() {
338 if (fGpu) {
339 fGpu->checkFinishProcs();
340 }
341}
342
343////////////////////////////////////////////////////////////////////////////////
344
345void GrContext::storeVkPipelineCacheData() {
346 if (fGpu) {
347 fGpu->storeVkPipelineCacheData();
348 }
349}
350
351////////////////////////////////////////////////////////////////////////////////
352
353bool GrContext::supportsDistanceFieldText() const {
354 return this->caps()->shaderCaps()->supportsDistanceFieldText();
355}
356
357//////////////////////////////////////////////////////////////////////////////
358
359void GrContext::getResourceCacheLimits(int* maxResources, size_t* maxResourceBytes) const {
360 ASSERT_SINGLE_OWNER
361 if (maxResources) {
362 *maxResources = -1;
363 }
364 if (maxResourceBytes) {
365 *maxResourceBytes = this->getResourceCacheLimit();
366 }
367}
368
369size_t GrContext::getResourceCacheLimit() const {
370 ASSERT_SINGLE_OWNER
371 return fResourceCache->getMaxResourceBytes();
372}
373
374void GrContext::setResourceCacheLimits(int unused, size_t maxResourceBytes) {
375 ASSERT_SINGLE_OWNER
376 this->setResourceCacheLimit(maxResourceBytes);
377}
378
379void GrContext::setResourceCacheLimit(size_t maxResourceBytes) {
380 ASSERT_SINGLE_OWNER
381 fResourceCache->setLimit(maxResourceBytes);
382}
383
384//////////////////////////////////////////////////////////////////////////////
385void GrContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
386 ASSERT_SINGLE_OWNER
387 fResourceCache->dumpMemoryStatistics(traceMemoryDump);
388 traceMemoryDump->dumpNumericValue("skia/gr_text_blob_cache", "size", "bytes",
389 this->getTextBlobCache()->usedBytes());
390}
391
392//////////////////////////////////////////////////////////////////////////////
393GrBackendTexture GrContext::createBackendTexture(int width, int height,
394 const GrBackendFormat& backendFormat,
395 GrMipMapped mipMapped,
396 GrRenderable renderable,
397 GrProtected isProtected) {
398 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
399 if (!this->asDirectContext()) {
400 return GrBackendTexture();
401 }
402
403 if (this->abandoned()) {
404 return GrBackendTexture();
405 }
406
407 return fGpu->createBackendTexture({width, height}, backendFormat, renderable,
408 mipMapped, isProtected, nullptr);
409}
410
411GrBackendTexture GrContext::createBackendTexture(int width, int height,
412 SkColorType skColorType,
413 GrMipMapped mipMapped,
414 GrRenderable renderable,
415 GrProtected isProtected) {
416 if (!this->asDirectContext()) {
417 return GrBackendTexture();
418 }
419
420 if (this->abandoned()) {
421 return GrBackendTexture();
422 }
423
424 const GrBackendFormat format = this->defaultBackendFormat(skColorType, renderable);
425
426 return this->createBackendTexture(width, height, format, mipMapped, renderable, isProtected);
427}
428
429GrBackendTexture GrContext::createBackendTexture(const SkSurfaceCharacterization& c) {
430 if (!this->asDirectContext() || !c.isValid()) {
431 return GrBackendTexture();
432 }
433
434 if (this->abandoned()) {
435 return GrBackendTexture();
436 }
437
438 if (c.usesGLFBO0()) {
439 // If we are making the surface we will never use FBO0.
440 return GrBackendTexture();
441 }
442
443 if (c.vulkanSecondaryCBCompatible()) {
444 return {};
445 }
446
447 const GrBackendFormat format = this->defaultBackendFormat(c.colorType(), GrRenderable::kYes);
448 if (!format.isValid()) {
449 return GrBackendTexture();
450 }
451
452 GrBackendTexture result = this->createBackendTexture(c.width(), c.height(), format,
453 GrMipMapped(c.isMipMapped()),
454 GrRenderable::kYes,
455 c.isProtected());
456 SkASSERT(c.isCompatible(result));
457 return result;
458}
459
460GrBackendTexture GrContext::createBackendTexture(const SkSurfaceCharacterization& c,
461 const SkColor4f& color) {
462 if (!this->asDirectContext() || !c.isValid()) {
463 return GrBackendTexture();
464 }
465
466 if (this->abandoned()) {
467 return GrBackendTexture();
468 }
469
470 if (c.usesGLFBO0()) {
471 // If we are making the surface we will never use FBO0.
472 return GrBackendTexture();
473 }
474
475 if (c.vulkanSecondaryCBCompatible()) {
476 return {};
477 }
478
479 const GrBackendFormat format = this->defaultBackendFormat(c.colorType(), GrRenderable::kYes);
480 if (!format.isValid()) {
481 return GrBackendTexture();
482 }
483
484 GrBackendTexture result = this->createBackendTexture(c.width(), c.height(), format, color,
485 GrMipMapped(c.isMipMapped()),
486 GrRenderable::kYes,
487 c.isProtected());
488 SkASSERT(c.isCompatible(result));
489 return result;
490}
491
492GrBackendTexture GrContext::createBackendTexture(int width, int height,
493 const GrBackendFormat& backendFormat,
494 const SkColor4f& color,
495 GrMipMapped mipMapped,
496 GrRenderable renderable,
497 GrProtected isProtected) {
498 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
499 if (!this->asDirectContext()) {
500 return GrBackendTexture();
501 }
502
503 if (this->abandoned()) {
504 return GrBackendTexture();
505 }
506
507 GrGpu::BackendTextureData data(color);
508 return fGpu->createBackendTexture({width, height}, backendFormat, renderable,
509 mipMapped, isProtected, &data);
510}
511
512GrBackendTexture GrContext::createBackendTexture(int width, int height,
513 SkColorType skColorType,
514 const SkColor4f& color,
515 GrMipMapped mipMapped,
516 GrRenderable renderable,
517 GrProtected isProtected) {
518 if (!this->asDirectContext()) {
519 return GrBackendTexture();
520 }
521
522 if (this->abandoned()) {
523 return GrBackendTexture();
524 }
525
526 GrBackendFormat format = this->defaultBackendFormat(skColorType, renderable);
527 if (!format.isValid()) {
528 return GrBackendTexture();
529 }
530
531 GrColorType grColorType = SkColorTypeToGrColorType(skColorType);
532 SkColor4f swizzledColor = this->caps()->getWriteSwizzle(format, grColorType).applyTo(color);
533
534 return this->createBackendTexture(width, height, format, swizzledColor, mipMapped, renderable,
535 isProtected);
536}
537
538GrBackendTexture GrContext::createBackendTexture(const SkPixmap srcData[], int numProvidedLevels,
539 GrRenderable renderable, GrProtected isProtected) {
540 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
541
542 if (!this->asDirectContext()) {
543 return {};
544 }
545
546 if (this->abandoned()) {
547 return {};
548 }
549
550 if (!srcData || numProvidedLevels <= 0) {
551 return {};
552 }
553
554 int baseWidth = srcData[0].width();
555 int baseHeight = srcData[0].height();
556 SkColorType colorType = srcData[0].colorType();
557
558 GrMipMapped mipMapped = GrMipMapped::kNo;
559 int numExpectedLevels = 1;
560 if (numProvidedLevels > 1) {
561 numExpectedLevels = SkMipMap::ComputeLevelCount(baseWidth, baseHeight) + 1;
562 mipMapped = GrMipMapped::kYes;
563 }
564
565 if (numProvidedLevels != numExpectedLevels) {
566 return {};
567 }
568
569 GrBackendFormat backendFormat = this->defaultBackendFormat(colorType, renderable);
570
571 GrGpu::BackendTextureData data(srcData);
572 return fGpu->createBackendTexture({baseWidth, baseHeight}, backendFormat, renderable,
573 mipMapped, isProtected, &data);
574}
575
576//////////////////////////////////////////////////////////////////////////////
577
578GrBackendTexture GrContext::createCompressedBackendTexture(int width, int height,
579 const GrBackendFormat& backendFormat,
580 const SkColor4f& color,
581 GrMipMapped mipMapped,
582 GrProtected isProtected) {
583 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
584 if (!this->asDirectContext()) {
585 return GrBackendTexture();
586 }
587
588 if (this->abandoned()) {
589 return GrBackendTexture();
590 }
591
592 GrGpu::BackendTextureData data(color);
593 return fGpu->createCompressedBackendTexture({width, height}, backendFormat,
594 mipMapped, isProtected, &data);
595}
596
597GrBackendTexture GrContext::createCompressedBackendTexture(int width, int height,
598 SkImage::CompressionType compression,
599 const SkColor4f& color,
600 GrMipMapped mipMapped,
601 GrProtected isProtected) {
602 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
603 if (!this->asDirectContext()) {
604 return GrBackendTexture();
605 }
606
607 if (this->abandoned()) {
608 return GrBackendTexture();
609 }
610
611 GrBackendFormat format = this->compressedBackendFormat(compression);
612 return this->createCompressedBackendTexture(width, height, format, color,
613 mipMapped, isProtected);
614}
615
616GrBackendTexture GrContext::createCompressedBackendTexture(int width, int height,
617 const GrBackendFormat& backendFormat,
618 const void* compressedData,
619 size_t dataSize,
620 GrMipMapped mipMapped,
621 GrProtected isProtected) {
622 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
623 if (!this->asDirectContext()) {
624 return GrBackendTexture();
625 }
626
627 if (this->abandoned()) {
628 return GrBackendTexture();
629 }
630
631 GrGpu::BackendTextureData data(compressedData, dataSize);
632 return fGpu->createCompressedBackendTexture({width, height}, backendFormat,
633 mipMapped, isProtected, &data);
634}
635
636GrBackendTexture GrContext::createCompressedBackendTexture(int width, int height,
637 SkImage::CompressionType compression,
638 const void* data, size_t dataSize,
639 GrMipMapped mipMapped,
640 GrProtected isProtected) {
641 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
642 if (!this->asDirectContext()) {
643 return GrBackendTexture();
644 }
645
646 if (this->abandoned()) {
647 return GrBackendTexture();
648 }
649
650 GrBackendFormat format = this->compressedBackendFormat(compression);
651 return this->createCompressedBackendTexture(width, height, format, data, dataSize,
652 mipMapped, isProtected);
653}
654
655void GrContext::deleteBackendTexture(GrBackendTexture backendTex) {
656 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
657 // For the Vulkan backend we still must destroy the backend texture when the context is
658 // abandoned.
659 if ((this->abandoned() && this->backend() != GrBackendApi::kVulkan) || !backendTex.isValid()) {
660 return;
661 }
662
663 fGpu->deleteBackendTexture(backendTex);
664}
665
666bool GrContext::precompileShader(const SkData& key, const SkData& data) {
667 return fGpu->precompileShader(key, data);
668}
669
670#ifdef SK_ENABLE_DUMP_GPU
671#include "include/core/SkString.h"
672#include "src/utils/SkJSONWriter.h"
673SkString GrContext::dump() const {
674 SkDynamicMemoryWStream stream;
675 SkJSONWriter writer(&stream, SkJSONWriter::Mode::kPretty);
676 writer.beginObject();
677
678 writer.appendString("backend", GrBackendApiToStr(this->backend()));
679
680 writer.appendName("caps");
681 this->caps()->dumpJSON(&writer);
682
683 writer.appendName("gpu");
684 this->fGpu->dumpJSON(&writer);
685
686 // Flush JSON to the memory stream
687 writer.endObject();
688 writer.flush();
689
690 // Null terminate the JSON data in the memory stream
691 stream.write8(0);
692
693 // Allocate a string big enough to hold all the data, then copy out of the stream
694 SkString result(stream.bytesWritten());
695 stream.copyToAndReset(result.writable_str());
696 return result;
697}
698#endif
699