diff options
author | Herb Derby <herb@google.com> | 2018-04-05 16:57:25 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-04-06 18:18:34 +0000 |
commit | 4961a938586e3c46cf80f15ecb70a011324bfe59 (patch) | |
tree | 3bd002a6ecbbe934207e54aa170f0b71a2f3aca8 | |
parent | c5a1c1f92f67c3f2b54250d040d3d3cd51e2feb7 (diff) |
Remove all notion of transport from the API.
There is only a need to pass buffers in and out of the system.
All transport is external to the system.
Change-Id: I26dfc8e0b4cce9969395c96d5230078e7dca3f3d
Reviewed-on: https://skia-review.googlesource.com/119062
Commit-Queue: Herb Derby <herb@google.com>
Reviewed-by: Khushal Sagar <khushalsagar@google.com>
-rw-r--r-- | src/core/SkRemoteGlyphCache.cpp | 353 | ||||
-rw-r--r-- | src/core/SkRemoteGlyphCache.h | 35 | ||||
-rw-r--r-- | tools/remote_demo.cpp | 98 |
3 files changed, 201 insertions, 285 deletions
diff --git a/src/core/SkRemoteGlyphCache.cpp b/src/core/SkRemoteGlyphCache.cpp index 200f86a118..7ee9d504fb 100644 --- a/src/core/SkRemoteGlyphCache.cpp +++ b/src/core/SkRemoteGlyphCache.cpp @@ -43,77 +43,6 @@ private: size_t fSize; }; -// -- SkRemoteStrikeTransport ---------------------------------------------------------------------- - -static SkRemoteStrikeTransport::IOResult write_data( - size_t size, const uint8_t* data, SkRemoteStrikeTransport* t) { - - if (t->write(&size, sizeof(size)) == SkRemoteStrikeTransport::kFail) { - return SkRemoteStrikeTransport::kFail; - } - - if (t->write(data, size) == SkRemoteStrikeTransport::kFail) { - return SkRemoteStrikeTransport::kFail; - } - - return SkRemoteStrikeTransport::kSuccess; -} - -static SkRemoteStrikeTransport::IOResult read_data( - size_t size, uint8_t* data, SkRemoteStrikeTransport* t) { - - size_t totalRead = 0; - while (totalRead < size) { - size_t sizeRead; - SkRemoteStrikeTransport::IOResult result; - std::tie(sizeRead, result) = t->read(&data[totalRead], size - totalRead); - if (result == SkRemoteStrikeTransport::kFail || sizeRead == 0) { - return SkRemoteStrikeTransport::kFail; - } - totalRead += sizeRead; - } - - return SkRemoteStrikeTransport::kSuccess; -} - -SkRemoteStrikeTransport::IOResult SkRemoteStrikeTransport::writeSkData(const SkData& data) { - return write_data(data.size(), (uint8_t*)data.data(), this); -} - -sk_sp<SkData> SkRemoteStrikeTransport::readSkData() { - size_t size; - if(std::get<1>(this->read(&size, sizeof(size))) == kFail) { - return nullptr; - } - - auto data = std::unique_ptr<uint8_t[]>{new uint8_t[size]}; - if (read_data(size, data.get(), this) == kFail) { - return nullptr; - } - - return SkData::MakeWithCopy(data.get(), size); -} - -SkRemoteStrikeTransport::IOResult SkRemoteStrikeTransport::writeVector( - const std::vector<uint8_t>& vector) { - return write_data(vector.size(), vector.data(), this); -} - -SkRemoteStrikeTransport::IOResult SkRemoteStrikeTransport::readVector( - std::vector<uint8_t>* vector) { - size_t vectorSize = 0; - size_t readSize = 0; - SkRemoteStrikeTransport::IOResult result; - std::tie(readSize, result) = this->read(&vectorSize, sizeof(vectorSize)); - if(result == kFail || readSize == 0) { - return kFail; - } - - vector->resize(vectorSize); - - return read_data(vectorSize, vector->data(), this); -} - // -- Serializer ---------------------------------------------------------------------------------- static size_t pad(size_t size, size_t alignment) { @@ -162,18 +91,20 @@ private: class Deserializer { public: - Deserializer(const std::vector<uint8_t>& buffer) : fBuffer{buffer} { } + Deserializer(const SkData& buffer) : fBuffer{buffer} { } template <typename T> T* read() { size_t padded = pad(fCursor, alignof(T)); fCursor = padded + sizeof(T); - return (T*)&fBuffer[padded]; + auto data = (uint8_t*)fBuffer.data(); + return (T*)&data[padded]; } SkDescriptor* readDescriptor() { size_t padded = pad(fCursor, alignof(SkDescriptor)); - SkDescriptor* result = (SkDescriptor*)&fBuffer[padded]; + auto data = (uint8_t*)fBuffer.data(); + SkDescriptor* result = (SkDescriptor*)&data[padded]; fCursor = padded + result->getLength(); return result; } @@ -182,7 +113,8 @@ public: ArraySlice<T> readArray(int count) { size_t padded = pad(fCursor, alignof(T)); size_t size = count * sizeof(T); - const T* base = (const T*)&fBuffer[padded]; + auto data = (uint8_t*)fBuffer.data(); + const T* base = (const T*)&data[padded]; ArraySlice<T> result = ArraySlice<T>{base, (uint32_t)count}; fCursor = padded + size; return result; @@ -191,8 +123,8 @@ public: size_t size() {return fCursor;} private: - const std::vector<uint8_t>& fBuffer; - size_t fCursor{0}; + const SkData& fBuffer; + size_t fCursor{0}; }; // -- TrackLayerDevice ----------------------------------------------------------------------------- @@ -473,14 +405,6 @@ public: }; }; -class Result { - union { - SkPaint::FontMetrics fontMetrics; - SkGlyph glyph; - StrikeDiffHeader strikeDiffHeader; - }; -}; - size_t SkStrikeCacheDifferenceSpec::sizeBytes() const { size_t sum = sizeof(Op) + sizeof(StrikeDiffHeader); for (auto& pair : fDescriptorToDifferencesMap) { @@ -577,85 +501,67 @@ static void update_caches_from_strikes_data(SkStrikeClient *client, } // -- SkStrikeServer ------------------------------------------------------------------------------- -SkStrikeServer::SkStrikeServer(SkRemoteStrikeTransport* transport) - : fTransport{transport} { } +SkStrikeServer::SkStrikeServer() { } SkStrikeServer::~SkStrikeServer() { printf("Strike server - ops: %d\n", fOpCount); } -int SkStrikeServer::serve() { +void SkStrikeServer::serve(const SkData& inBuffer, std::vector<uint8_t>* outBuffer) { - std::vector<uint8_t> inBuffer; - std::vector<uint8_t> outBuffer; + fOpCount += 1; - while (true) { - inBuffer.clear(); - auto result = fTransport->readVector(&inBuffer); - if (result == SkRemoteStrikeTransport::kFail) { + Serializer serializer{outBuffer}; + Deserializer deserializer{inBuffer}; + Op* op = deserializer.read<Op>(); + + switch (op->opCode) { + case OpCode::kFontMetrics : { + auto scaler = this->generateScalerContext(op->descriptor, op->typefaceId); + SkPaint::FontMetrics metrics; + scaler->getFontMetrics(&metrics); + serializer.push_back<SkPaint::FontMetrics>(metrics); break; } - - Deserializer deserializer{inBuffer}; - Op* op = deserializer.read<Op>(); - - fOpCount += 1; - - outBuffer.clear(); - Serializer serializer{&outBuffer}; - - switch (op->opCode) { - case OpCode::kFontMetrics : { - auto sc = this->generateScalerContext(op->descriptor, op->typefaceId); - SkPaint::FontMetrics metrics; - sc->getFontMetrics(&metrics); - serializer.push_back<SkPaint::FontMetrics>(metrics); - break; - } - case OpCode::kGlyphPath : { - auto sc = this->generateScalerContext(op->descriptor, op->typefaceId); - // TODO: check for buffer overflow. - SkPath path; - if (sc->getPath(op->glyphID, &path)) { - size_t pathSize = path.writeToMemory(nullptr); - serializer.push_back<size_t>(pathSize); - auto pathData = serializer.allocateArray<uint8_t>(pathSize); - path.writeToMemory(pathData); - } - break; - } - case OpCode::kGlyphMetricsAndImage : { - auto scaler = this->generateScalerContext(op->descriptor, op->typefaceId); - - auto glyph = serializer.emplace_back<SkGlyph>(); - // TODO: check for buffer overflow. - glyph->initWithGlyphID(op->glyphID); - scaler->getMetrics(glyph); - auto imageSize = glyph->computeImageSize(); - glyph->fPathData = nullptr; - glyph->fImage = nullptr; - - if (imageSize > 0) { - // Since the allocateArray can move glyph, make one that stays in one place. - SkGlyph stationaryGlyph = *glyph; - stationaryGlyph.fImage = serializer.allocateArray<uint8_t>(imageSize); - scaler->getImage(stationaryGlyph); - } - break; - } - case OpCode::kPrepopulateCache : { - read_strikes_spec_write_strikes_data( - &deserializer, &serializer, this); - break; + case OpCode::kGlyphPath : { + auto sc = this->generateScalerContext(op->descriptor, op->typefaceId); + // TODO: check for buffer overflow. + SkPath path; + if (sc->getPath(op->glyphID, &path)) { + size_t pathSize = path.writeToMemory(nullptr); + serializer.push_back<size_t>(pathSize); + auto pathData = serializer.allocateArray<uint8_t>(pathSize); + path.writeToMemory(pathData); } + break; + } + case OpCode::kGlyphMetricsAndImage : { + auto scaler = this->generateScalerContext(op->descriptor, op->typefaceId); - default: - SK_ABORT("Bad op"); + auto glyph = serializer.emplace_back<SkGlyph>(); + // TODO: check for buffer overflow. + glyph->initWithGlyphID(op->glyphID); + scaler->getMetrics(glyph); + auto imageSize = glyph->computeImageSize(); + glyph->fPathData = nullptr; + glyph->fImage = nullptr; + if (imageSize > 0) { + // Since the allocateArray can move glyph, make one that stays in one place. + SkGlyph stationaryGlyph = *glyph; + stationaryGlyph.fImage = serializer.allocateArray<uint8_t>(imageSize); + scaler->getImage(stationaryGlyph); + } + break; + } + case OpCode::kPrepopulateCache : { + read_strikes_spec_write_strikes_data( + &deserializer, &serializer, this); + break; } - fTransport->writeVector(outBuffer); + default: + SK_ABORT("Bad op"); } - return 0; } void SkStrikeServer::prepareSerializeProcs(SkSerialProcs* procs) { @@ -704,105 +610,88 @@ sk_sp<SkData> SkStrikeServer::encodeTypeface(SkTypeface* tf) { } // -- SkStrikeClient ------------------------------------------------------------------------------- - -SkStrikeClient::SkStrikeClient(SkRemoteStrikeTransport* transport) - : fTransport{transport} { } +SkStrikeClient::SkStrikeClient(SkStrikeCacheClientRPC clientRPC) + : fClientRPC{clientRPC} { } void SkStrikeClient::generateFontMetrics( - const SkTypefaceProxy& typefaceProxy, - const SkScalerContextRec& rec, - SkPaint::FontMetrics* metrics) { - // Send generateFontMetrics - { - fBuffer.clear(); - Serializer serializer{&fBuffer}; - serializer.emplace_back<Op>(OpCode::kFontMetrics, typefaceProxy.remoteTypefaceID(), rec); - fTransport->writeVector(fBuffer); - } + const SkTypefaceProxy& typefaceProxy, + const SkScalerContextRec& rec, + SkPaint::FontMetrics* metrics) +{ + fBuffer.clear(); - // Receive generateFontMetrics - { - fBuffer.clear(); - fTransport->readVector(&fBuffer); - Deserializer deserializer(fBuffer); - *metrics = *deserializer.read<SkPaint::FontMetrics>(); - } + Serializer serializer{&fBuffer}; + serializer.emplace_back<Op>(OpCode::kFontMetrics, typefaceProxy.remoteTypefaceID(), rec); + + auto outBuffer = SkData::MakeWithoutCopy(fBuffer.data(), fBuffer.size()); + auto inbuffer = fClientRPC(*outBuffer); + Deserializer deserializer(*inbuffer); + *metrics = *deserializer.read<SkPaint::FontMetrics>(); } void SkStrikeClient::generateMetricsAndImage( - const SkTypefaceProxy& typefaceProxy, - const SkScalerContextRec& rec, - SkArenaAlloc* alloc, - SkGlyph* glyph) { - { + const SkTypefaceProxy& typefaceProxy, + const SkScalerContextRec& rec, + SkArenaAlloc* alloc, + SkGlyph* glyph) +{ fBuffer.clear(); Serializer serializer(&fBuffer); Op *op = serializer.emplace_back<Op>( OpCode::kGlyphMetricsAndImage, typefaceProxy.remoteTypefaceID(), rec); - op->glyphID = glyph->getPackedID(); - fTransport->writeVector(fBuffer); - } - - // Receive generateMetricsAndImage - { - fBuffer.clear(); - fTransport->readVector(&fBuffer); - Deserializer deserializer(fBuffer); - *glyph = *deserializer.read<SkGlyph>(); - auto imageSize = glyph->computeImageSize(); - glyph->fPathData = nullptr; - glyph->fImage = nullptr; - if (imageSize > 0) { - auto image = deserializer.readArray<uint8_t>(imageSize); - SkASSERT(imageSize == image.size()); - glyph->allocImage(alloc); - memcpy(glyph->fImage, image.data(), imageSize); - } + op->glyphID = glyph->getPackedID(); + + auto outBuffer = SkData::MakeWithoutCopy(fBuffer.data(), fBuffer.size()); + auto inbuffer = fClientRPC(*outBuffer); + Deserializer deserializer(*inbuffer); + *glyph = *deserializer.read<SkGlyph>(); + auto imageSize = glyph->computeImageSize(); + glyph->fPathData = nullptr; + glyph->fImage = nullptr; + if (imageSize > 0) { + auto image = deserializer.readArray<uint8_t>(imageSize); + SkASSERT(imageSize == image.size()); + glyph->allocImage(alloc); + memcpy(glyph->fImage, image.data(), imageSize); } - } -bool SkStrikeClient::generatePath( - const SkTypefaceProxy& typefaceProxy, - const SkScalerContextRec& rec, - SkGlyphID glyphID, - SkPath* path) { - { - fBuffer.clear(); - Serializer serializer{&fBuffer}; - Op *op = serializer.emplace_back<Op>( - OpCode::kGlyphPath, typefaceProxy.remoteTypefaceID(), rec); - op->glyphID = glyphID; - fTransport->writeVector(fBuffer); - } - { - fBuffer.clear(); - fTransport->readVector(&fBuffer); - Deserializer deserializer{fBuffer}; - size_t pathSize = *deserializer.read<size_t>(); - if (pathSize == 0) { - return false; - } - auto rawPath = deserializer.readArray<uint8_t>(pathSize); - path->readFromMemory(rawPath.data(), rawPath.size()); - } +bool SkStrikeClient::generatePath( + const SkTypefaceProxy& typefaceProxy, + const SkScalerContextRec& rec, + SkGlyphID glyphID, + SkPath* path) +{ + fBuffer.clear(); + + Serializer serializer{&fBuffer}; + Op *op = serializer.emplace_back<Op>( + OpCode::kGlyphPath, typefaceProxy.remoteTypefaceID(), rec); + op->glyphID = glyphID; + + auto outBuffer = SkData::MakeWithoutCopy(fBuffer.data(), fBuffer.size()); + auto inbuffer = fClientRPC(*outBuffer); + Deserializer deserializer(*inbuffer); + size_t pathSize = *deserializer.read<size_t>(); + if (pathSize == 0) { + return false; + } + auto rawPath = deserializer.readArray<uint8_t>(pathSize); + path->readFromMemory(rawPath.data(), rawPath.size()); return true; } void SkStrikeClient::primeStrikeCache(const SkStrikeCacheDifferenceSpec& strikeDifferences) { - { - fBuffer.clear(); - fBuffer.reserve(strikeDifferences.sizeBytes()); - Serializer serializer{&fBuffer}; - write_strikes_spec(strikeDifferences, &serializer); - fTransport->writeVector(fBuffer); - } - { - fBuffer.clear(); - fTransport->readVector(&fBuffer); - Deserializer deserializer{fBuffer}; - update_caches_from_strikes_data(this, &deserializer); - } + fBuffer.clear(); + fBuffer.reserve(strikeDifferences.sizeBytes()); + + Serializer serializer{&fBuffer}; + write_strikes_spec(strikeDifferences, &serializer); + + auto outBuffer = SkData::MakeWithoutCopy(fBuffer.data(), fBuffer.size()); + auto inbuffer = fClientRPC(*outBuffer); + Deserializer deserializer(*inbuffer); + update_caches_from_strikes_data(this, &deserializer); } void SkStrikeClient::prepareDeserializeProcs(SkDeserialProcs* procs) { diff --git a/src/core/SkRemoteGlyphCache.h b/src/core/SkRemoteGlyphCache.h index 3dfa8c28e5..04d72a083b 100644 --- a/src/core/SkRemoteGlyphCache.h +++ b/src/core/SkRemoteGlyphCache.h @@ -19,26 +19,15 @@ #include "SkGlyphCache.h" #include "SkMakeUnique.h" #include "SkNoDrawCanvas.h" +#include "SkRefCnt.h" #include "SkSerialProcs.h" #include "SkTextBlobRunIterator.h" #include "SkTHash.h" #include "SkTypeface.h" #include "SkTypeface_remote.h" -class SkScalerContextRecDescriptor; - -class SkRemoteStrikeTransport { -public: - enum IOResult : bool {kFail = false, kSuccess = true}; - - virtual ~SkRemoteStrikeTransport() {} - virtual IOResult write(const void*, size_t) = 0; - virtual std::tuple<size_t, IOResult> read(void*, size_t) = 0; - IOResult writeSkData(const SkData&); - sk_sp<SkData> readSkData(); - IOResult writeVector(const std::vector<uint8_t>&); - IOResult readVector(std::vector<uint8_t>*); -}; +// The client uses a SkStrikeCacheClientRPC to send and receive data. +using SkStrikeCacheClientRPC = std::function<sk_sp<SkData>(const SkData&)>; class SkScalerContextRecDescriptor { public: @@ -94,7 +83,7 @@ private: } fDescriptor; }; -class SkStrikeCacheDifferenceSpec { +class SK_API SkStrikeCacheDifferenceSpec { class StrikeDifferences; public: @@ -131,7 +120,7 @@ private: DescMap fDescriptorToDifferencesMap{16, DescHash(), DescEq()}; }; -class SkTextBlobCacheDiffCanvas : public SkNoDrawCanvas { +class SK_API SkTextBlobCacheDiffCanvas : public SkNoDrawCanvas { public: SkTextBlobCacheDiffCanvas(int width, int height, const SkMatrix& deviceMatrix, @@ -164,13 +153,14 @@ private: SkStrikeCacheDifferenceSpec* const fStrikeCacheDiff; }; -class SkStrikeServer { +class SK_API SkStrikeServer { public: - SkStrikeServer(SkRemoteStrikeTransport* transport); + SkStrikeServer(); ~SkStrikeServer(); // embedding clients call these methods - int serve(); // very negotiable + void serve(const SkData&, std::vector<uint8_t>*); + void prepareSerializeProcs(SkSerialProcs* procs); // mostly called internally by Skia @@ -185,14 +175,13 @@ private: sk_sp<SkData> encodeTypeface(SkTypeface* tf); int fOpCount = 0; - SkRemoteStrikeTransport* const fTransport; SkTHashMap<SkFontID, sk_sp<SkTypeface>> fTypefaceMap; DescriptorToContextMap fScalerContextMap; }; -class SkStrikeClient { +class SK_API SkStrikeClient { public: - SkStrikeClient(SkRemoteStrikeTransport*); + SkStrikeClient(SkStrikeCacheClientRPC); // embedding clients call these methods void primeStrikeCache(const SkStrikeCacheDifferenceSpec&); @@ -213,7 +202,7 @@ private: // TODO: Figure out how to manage the entries for the following maps. SkTHashMap<SkFontID, sk_sp<SkTypefaceProxy>> fMapIdToTypeface; - SkRemoteStrikeTransport* const fTransport; + SkStrikeCacheClientRPC fClientRPC; std::vector<uint8_t> fBuffer; }; diff --git a/tools/remote_demo.cpp b/tools/remote_demo.cpp index a5e0a367de..8fc96cadb8 100644 --- a/tools/remote_demo.cpp +++ b/tools/remote_demo.cpp @@ -25,35 +25,52 @@ static bool gUseGpu = true; static bool gPurgeFontCaches = true; static bool gUseProcess = true; -class ReadWriteTransport : public SkRemoteStrikeTransport { -public: - ReadWriteTransport(int readFd, int writeFd) : fReadFd{readFd}, fWriteFd{writeFd} {} - ~ReadWriteTransport() override { - close(fWriteFd); - close(fReadFd); +static bool write_SkData(int fd, const SkData& data) { + size_t size = data.size(); + ssize_t bytesWritten = ::write(fd, &size, sizeof(size)); + if (bytesWritten < 0) { + err(1,"Failed write %zu", size); + return false; } - IOResult write(const void* buffer, size_t size) override { - ssize_t writeSize = ::write(fWriteFd, buffer, size); - if (writeSize < 0) { - err(1,"Failed write %zu", size); - return kFail; - } - return kSuccess; + + bytesWritten = ::write(fd, data.data(), data.size()); + if (bytesWritten < 0) { + err(1,"Failed write %zu", size); + return false; } - std::tuple<size_t, IOResult> read(void* buffer, size_t size) override { - ssize_t readSize = ::read(fReadFd, buffer, size); + return true; +} + +static sk_sp<SkData> read_SkData(int fd) { + + size_t size; + ssize_t readSize = ::read(fd, &size, sizeof(size)); + if (readSize <= 0) { if (readSize < 0) { - err(1,"Failed read %zu", size); - return {size, kFail}; + err(1, "Failed read %zu", size); } - return {readSize, kSuccess}; + return nullptr; } -private: - const int fReadFd, - fWriteFd; -}; + auto out = SkData::MakeUninitialized(size); + auto data = (uint8_t*)out->data(); + + size_t totalRead = 0; + while (totalRead < size) { + ssize_t sizeRead; + sizeRead = ::read(fd, &data[totalRead], size - totalRead); + if (sizeRead <= 0) { + if (readSize < 0) { + err(1, "Failed read %zu", size); + } + return nullptr; + } + totalRead += sizeRead; + } + + return out; +} class Timer { public: @@ -133,14 +150,17 @@ static void final_draw(std::string outFilename, static void gpu(int readFd, int writeFd) { if (gUseGpu) { - ReadWriteTransport rwTransport{readFd, writeFd}; + auto clientRPC = [readFd, writeFd](const SkData& inBuffer) { + write_SkData(writeFd, inBuffer); + return read_SkData(readFd); + }; - auto picData = rwTransport.readSkData(); + auto picData = read_SkData(readFd); if (picData == nullptr) { return; } - SkStrikeClient client{&rwTransport}; + SkStrikeClient client{clientRPC}; SkDeserialProcs procs; client.prepareDeserializeProcs(&procs); @@ -148,14 +168,20 @@ static void gpu(int readFd, int writeFd) { final_draw("test.png", &procs, picData.get(), &client); } + ::close(writeFd); + ::close(readFd); + printf("GPU is exiting\n"); } static int renderer( const std::string& skpName, int readFd, int writeFd) { - ReadWriteTransport rwTransport{readFd, writeFd}; - SkStrikeServer server{&rwTransport}; + SkStrikeServer server{}; + auto closeAll = [readFd, writeFd]() { + ::close(writeFd); + ::close(readFd); + }; auto skpData = SkData::MakeFromFileName(skpName.c_str()); std::cout << "skp stream is " << skpData->size() << " bytes long " << std::endl; @@ -167,16 +193,28 @@ static int renderer( server.prepareSerializeProcs(&procs); stream = pic->serialize(&procs); - if (rwTransport.writeSkData(*stream) == SkRemoteStrikeTransport::kFail) { + if (!write_SkData(writeFd, *stream)) { + closeAll(); return 1; } - std::cout << "Waiting for scaler context ops." << std::endl; + std::vector<uint8_t> tmpBuffer; + while (true) { + auto inBuffer = read_SkData(readFd); + if (inBuffer == nullptr) { + closeAll(); + return 0; + } - return server.serve(); + tmpBuffer.clear(); + server.serve(*inBuffer, &tmpBuffer); + auto outBuffer = SkData::MakeWithoutCopy(tmpBuffer.data(), tmpBuffer.size()); + write_SkData(writeFd, *outBuffer); + } } else { stream = skpData; final_draw("test-correct.png", nullptr, stream.get(), nullptr); + closeAll(); return 0; } } |