loolwsd/ClientSession.cpp | 66 +--------------- loolwsd/DocumentBroker.cpp | 106 ++++++++++++++++++++++++++ loolwsd/DocumentBroker.hpp | 3 loolwsd/LOOLKit.cpp | 178 ++++++++++++++++----------------------------- 4 files changed, 182 insertions(+), 171 deletions(-)
New commits: commit 114a6bc373a8bd8ea1af3144b8b500f92060f2d0 Author: Ashod Nakashian <ashod.nakash...@collabora.co.uk> Date: Sun May 22 11:45:28 2016 -0400 loolwsd: combined tile rendering Tiles can be rendered in large batches and sent back as combined payloads, all reduce latency and overhead significantly. Initial tests show a reduction in total latency by 2x. This without sending tiles back to the client in combined form, which will reduce latency further. Change-Id: Iee06503f2a6c741fadb2c890266ea514c809c0dc Reviewed-on: https://gerrit.libreoffice.org/25339 Reviewed-by: Ashod Nakashian <ashnak...@gmail.com> Tested-by: Ashod Nakashian <ashnak...@gmail.com> diff --git a/loolwsd/ClientSession.cpp b/loolwsd/ClientSession.cpp index 7158a97..1fb1ec7 100644 --- a/loolwsd/ClientSession.cpp +++ b/loolwsd/ClientSession.cpp @@ -337,69 +337,15 @@ bool ClientSession::sendTile(const char * /*buffer*/, int /*length*/, StringToke bool ClientSession::sendCombinedTiles(const char* /*buffer*/, int /*length*/, StringTokenizer& tokens) { - int part, pixelWidth, pixelHeight, tileWidth, tileHeight; - std::string tilePositionsX, tilePositionsY; - if (tokens.count() < 8 || - !getTokenInteger(tokens[1], "part", part) || - !getTokenInteger(tokens[2], "width", pixelWidth) || - !getTokenInteger(tokens[3], "height", pixelHeight) || - !getTokenString (tokens[4], "tileposx", tilePositionsX) || - !getTokenString (tokens[5], "tileposy", tilePositionsY) || - !getTokenInteger(tokens[6], "tilewidth", tileWidth) || - !getTokenInteger(tokens[7], "tileheight", tileHeight)) - { - return sendTextFrame("error: cmd=tilecombine kind=syntax"); - } - - if (part < 0 || pixelWidth <= 0 || pixelHeight <= 0 || - tileWidth <= 0 || tileHeight <= 0 || - tilePositionsX.empty() || tilePositionsY.empty()) - { - return sendTextFrame("error: cmd=tilecombine kind=invalid"); - } - - std::string reqTimestamp; - size_t index = 8; - if (tokens.count() > index && tokens[index].find("timestamp") == 0) - { - getTokenString(tokens[index], "timestamp", reqTimestamp); - ++index; - } - - int id = -1; - if (tokens.count() > index && tokens[index].find("id") == 0) - { - getTokenInteger(tokens[index], "id", id); - ++index; - } - - StringTokenizer positionXtokens(tilePositionsX, ",", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); - StringTokenizer positionYtokens(tilePositionsY, ",", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); - - size_t numberOfPositions = positionYtokens.count(); - - // check that number of positions for X and Y is the same - if (numberOfPositions != positionXtokens.count()) + try { - return sendTextFrame("error: cmd=tilecombine kind=invalid"); + auto tileCombined = TileCombined::parse(tokens); + _docBroker->handleTileCombinedRequest(tileCombined, shared_from_this()); } - - for (size_t i = 0; i < numberOfPositions; ++i) + catch (const std::exception& exc) { - int x = 0; - if (!stringToInteger(positionXtokens[i], x)) - { - return sendTextFrame("error: cmd=tilecombine kind=syntax"); - } - - int y = 0; - if (!stringToInteger(positionYtokens[i], y)) - { - return sendTextFrame("error: cmd=tilecombine kind=syntax"); - } - - const TileDesc tile(part, pixelWidth, pixelHeight, x, y, tileWidth, tileHeight); - _docBroker->handleTileRequest(tile, shared_from_this()); + Log::error(std::string("Failed to process tilecombine command: ") + exc.what() + "."); + return sendTextFrame("error: cmd=tile kind=invalid"); } return true; diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp index 4c69a36..62dfe60 100644 --- a/loolwsd/DocumentBroker.cpp +++ b/loolwsd/DocumentBroker.cpp @@ -403,6 +403,10 @@ bool DocumentBroker::handleInput(const std::vector<char>& payload) { handleTileResponse(payload); } + else if (command == "tilecombine:") + { + handleTileCombinedResponse(payload); + } return true; } @@ -453,6 +457,69 @@ void DocumentBroker::handleTileRequest(const TileDesc& tile, _childProcess->getWebSocket()->sendFrame(request.data(), request.size()); } +void DocumentBroker::handleTileCombinedRequest(TileCombined& tileCombined, + const std::shared_ptr<ClientSession>& session) +{ + Log::trace() << "TileCombined request for " << tileCombined.serialize() << Log::end; + + std::unique_lock<std::mutex> lock(_mutex); + + // Satisfy as many tiles from the cache. + auto& tiles = tileCombined.getTiles(); + int i = tiles.size(); + while (--i >= 0) + { + const auto& tile = tiles[i]; + std::unique_ptr<std::fstream> cachedTile = _tileCache->lookupTile(tile); + if (cachedTile) + { + //TODO: Combine. +#if ENABLE_DEBUG + const std::string response = tile.serialize("tile:") + " renderid=cached\n"; +#else + const std::string response = tile.serialize("tile:") + "\n"; +#endif + + std::vector<char> output; + output.reserve(4 * tile.getWidth() * tile.getHeight()); + output.resize(response.size()); + std::memcpy(output.data(), response.data(), response.size()); + + assert(cachedTile->is_open()); + cachedTile->seekg(0, std::ios_base::end); + size_t pos = output.size(); + std::streamsize size = cachedTile->tellg(); + output.resize(pos + size); + cachedTile->seekg(0, std::ios_base::beg); + cachedTile->read(output.data() + pos, size); + cachedTile->close(); + + session->sendBinaryFrame(output.data(), output.size()); + + // Remove. + tiles.erase(tiles.begin() + i); + } + else if (tileCache().isTileBeingRenderedIfSoSubscribe(tile, session)) + { + // Skip. + tiles.erase(tiles.begin() + i); + } + } + + if (tiles.empty()) + { + // Done. + return; + } + + const auto tileMsg = tileCombined.serialize(); + Log::debug() << "TileCombined residual request for " << tileMsg << Log::end; + + // Forward to child to render. + const std::string request = "tilecombine " + tileMsg; + _childProcess->getWebSocket()->sendFrame(request.data(), request.size()); +} + void DocumentBroker::handleTileResponse(const std::vector<char>& payload) { const std::string firstLine = getFirstLine(payload); @@ -482,6 +549,45 @@ void DocumentBroker::handleTileResponse(const std::vector<char>& payload) } } +void DocumentBroker::handleTileCombinedResponse(const std::vector<char>& payload) +{ + const std::string firstLine = getFirstLine(payload); + Log::debug("Handling tile combined: " + firstLine); + + try + { + auto tileCombined = TileCombined::parse(firstLine); + const auto buffer = payload.data(); + const auto length = payload.size(); + auto offset = firstLine.size() + 1; + + if (firstLine.size() < static_cast<std::string::size_type>(length) - 1) + { + for (const auto& tile : tileCombined.getTiles()) + { + tileCache().saveTile(tile, buffer + offset, tile.getImgSize()); + tileCache().notifyAndRemoveSubscribers(tile); + offset += tile.getImgSize(); + } + } + else + { + Log::error() << "Render request failed for " << firstLine << Log::end; + std::unique_lock<std::mutex> tileBeingRenderedLock(tileCache().getTilesBeingRenderedLock()); + for (const auto& tile : tileCombined.getTiles()) + { + tileCache().forgetTileBeingRendered(tile); + } + } + } + catch (const std::exception& exc) + { + Log::error("Failed to process tile response [" + firstLine + "]: " + exc.what() + "."); + //FIXME: Return error. + //sendTextFrame("error: cmd=tile kind=syntax"); + } +} + bool DocumentBroker::canDestroy() { std::unique_lock<std::mutex> lock(_mutex); diff --git a/loolwsd/DocumentBroker.hpp b/loolwsd/DocumentBroker.hpp index 3fa7def..b722d76 100644 --- a/loolwsd/DocumentBroker.hpp +++ b/loolwsd/DocumentBroker.hpp @@ -204,8 +204,11 @@ public: void handleTileRequest(const TileDesc& tile, const std::shared_ptr<ClientSession>& session); + void handleTileCombinedRequest(TileCombined& tileCombined, + const std::shared_ptr<ClientSession>& session); void handleTileResponse(const std::vector<char>& payload); + void handleTileCombinedResponse(const std::vector<char>& payload); // Called when the last view is going out. bool canDestroy(); diff --git a/loolwsd/LOOLKit.cpp b/loolwsd/LOOLKit.cpp index f2ba282..f594fa8 100644 --- a/loolwsd/LOOLKit.cpp +++ b/loolwsd/LOOLKit.cpp @@ -52,6 +52,7 @@ #include "Log.hpp" #include "Png.hpp" #include "QueueHandler.hpp" +#include "Rectangle.hpp" #include "TileDesc.hpp" #include "Unit.hpp" #include "UserMessages.hpp" @@ -617,79 +618,21 @@ public: ws->sendFrame(output.data(), length, WebSocket::FRAME_BINARY); } - void sendCombinedTiles(const char* /*buffer*/, int /*length*/, StringTokenizer& /*tokens*/) + void renderCombinedTiles(StringTokenizer& tokens, const std::shared_ptr<Poco::Net::WebSocket>& ws) { - // This is unnecessary at this point, since the DocumentBroker will send us individual - // tile requests (i.e. it breaks tilecombine requests). - // So unless DocumentBroker combines them again, there is no point in having this here. - // In fact, we probably want to remove this, since we always want to render individual - // tiles so that we can fetch them separately in the future. -#if 0 - int part, pixelWidth, pixelHeight, tileWidth, tileHeight; - std::string tilePositionsX, tilePositionsY; - std::string reqTimestamp; - - if (tokens.count() < 8 || - !getTokenInteger(tokens[1], "part", part) || - !getTokenInteger(tokens[2], "width", pixelWidth) || - !getTokenInteger(tokens[3], "height", pixelHeight) || - !getTokenString (tokens[4], "tileposx", tilePositionsX) || - !getTokenString (tokens[5], "tileposy", tilePositionsY) || - !getTokenInteger(tokens[6], "tilewidth", tileWidth) || - !getTokenInteger(tokens[7], "tileheight", tileHeight)) - { - //sendTextFrame("error: cmd=tilecombine kind=syntax"); - return; - } - - if (part < 0 || pixelWidth <= 0 || pixelHeight <= 0 - || tileWidth <= 0 || tileHeight <= 0 - || tilePositionsX.empty() || tilePositionsY.empty()) - { - //sendTextFrame("error: cmd=tilecombine kind=invalid"); - return; - } - - if (tokens.count() > 8) - getTokenString(tokens[8], "timestamp", reqTimestamp); - - bool makeSlow = delayAndRewritePart(part); + auto tileCombined = TileCombined::parse(tokens); + auto& tiles = tileCombined.getTiles(); Util::Rectangle renderArea; + std::vector<Util::Rectangle> tileRecs; + tileRecs.reserve(tiles.size()); - StringTokenizer positionXtokens(tilePositionsX, ",", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); - StringTokenizer positionYtokens(tilePositionsY, ",", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); - - size_t numberOfPositions = positionYtokens.count(); - // check that number of positions for X and Y is the same - if (numberOfPositions != positionYtokens.count()) + for (auto& tile : tiles) { - sendTextFrame("error: cmd=tilecombine kind=invalid"); - return; - } - - std::vector<Util::Rectangle> tiles; - tiles.reserve(numberOfPositions); + Util::Rectangle rectangle(tile.getTilePosX(), tile.getTilePosY(), + tileCombined.getTileWidth(), tileCombined.getTileHeight()); - for (size_t i = 0; i < numberOfPositions; ++i) - { - int x = 0; - if (!stringToInteger(positionXtokens[i], x)) - { - sendTextFrame("error: cmd=tilecombine kind=syntax"); - return; - } - - int y = 0; - if (!stringToInteger(positionYtokens[i], y)) - { - sendTextFrame("error: cmd=tilecombine kind=syntax"); - return; - } - - Util::Rectangle rectangle(x, y, tileWidth, tileHeight); - - if (tiles.empty()) + if (tileRecs.empty()) { renderArea = rectangle; } @@ -698,71 +641,77 @@ public: renderArea.extend(rectangle); } - tiles.push_back(rectangle); + tileRecs.push_back(rectangle); } - LibreOfficeKitTileMode mode = static_cast<LibreOfficeKitTileMode>(_loKitDocument->pClass->getTileMode(_loKitDocument)); - - int tilesByX = renderArea.getWidth() / tileWidth; - int tilesByY = renderArea.getHeight() / tileHeight; + const int tilesByX = renderArea.getWidth() / tileCombined.getTileWidth(); + const int tilesByY = renderArea.getHeight() / tileCombined.getTileHeight(); - int pixmapWidth = tilesByX * pixelWidth; - int pixmapHeight = tilesByY * pixelHeight; + const int pixmapWidth = tilesByX * tileCombined.getWidth(); + const int pixmapHeight = tilesByY * tileCombined.getHeight(); const size_t pixmapSize = 4 * pixmapWidth * pixmapHeight; std::vector<unsigned char> pixmap(pixmapSize, 0); Timestamp timestamp; - _loKitDocument->pClass->paintPartTile(_loKitDocument, pixmap.data(), part, - pixmapWidth, pixmapHeight, - renderArea.getLeft(), renderArea.getTop(), - renderArea.getWidth(), renderArea.getHeight()); + _loKitDocument->paintPartTile(pixmap.data(), tileCombined.getPart(), + pixmapWidth, pixmapHeight, + renderArea.getLeft(), renderArea.getTop(), + renderArea.getWidth(), renderArea.getHeight()); Log::debug() << "paintTile (combined) called, tile at [" << renderArea.getLeft() << ", " << renderArea.getTop() << "]" - << " (" << renderArea.getWidth() << ", " << renderArea.getHeight() << ") rendered in " - << double(timestamp.elapsed())/1000 << "ms" << Log::end; - - for (Util::Rectangle& tileRect : tiles) - { - std::string response = "tile: part=" + std::to_string(part) + - " width=" + std::to_string(pixelWidth) + - " height=" + std::to_string(pixelHeight) + - " tileposx=" + std::to_string(tileRect.getLeft()) + - " tileposy=" + std::to_string(tileRect.getTop()) + - " tilewidth=" + std::to_string(tileWidth) + - " tileheight=" + std::to_string(tileHeight); - - if (reqTimestamp != "") - response += " timestamp=" + reqTimestamp; - -#if ENABLE_DEBUG - response += " renderid=" + Util::UniqueId(); -#endif - - response += "\n"; - - std::vector<char> output; - output.reserve(pixelWidth * pixelHeight * 4 + response.size()); - output.resize(response.size()); + << " (" << renderArea.getWidth() << ", " << renderArea.getHeight() << ") rendered in " + << double(timestamp.elapsed())/1000 << " ms." << Log::end; - std::copy(response.begin(), response.end(), output.begin()); - int positionX = (tileRect.getLeft() - renderArea.getLeft()) / tileWidth; - int positionY = (tileRect.getTop() - renderArea.getTop()) / tileHeight; + std::vector<char> output; + output.reserve(pixmapWidth * pixmapHeight * 4); - if (!Util::encodeSubBufferToPNG(pixmap.data(), positionX * pixelWidth, positionY * pixelHeight, pixelWidth, pixelHeight, pixmapWidth, pixmapHeight, output, mode)) + const auto mode = static_cast<LibreOfficeKitTileMode>(_loKitDocument->getTileMode()); + size_t tileIndex = 0; + for (Util::Rectangle& tileRect : tileRecs) + { + const int positionX = (tileRect.getLeft() - renderArea.getLeft()) / tileCombined.getTileWidth(); + const int positionY = (tileRect.getTop() - renderArea.getTop()) / tileCombined.getTileHeight(); + + const auto oldSize = output.size(); + const auto pixelWidth = tileCombined.getWidth(); + const auto pixelHeight = tileCombined.getHeight(); + if (!png::encodeSubBufferToPNG(pixmap.data(), positionX * pixelWidth, positionY * pixelHeight, + pixelWidth, pixelHeight, pixmapWidth, pixmapHeight, output, mode)) { - sendTextFrame("error: cmd=tile kind=failure"); + //FIXME: Return error. + //sendTextFrame("error: cmd=tile kind=failure"); + Log::error("Failed to encode tile into PNG."); return; } - sendBinaryFrame(output.data(), output.size()); + const auto imgSize = output.size() - oldSize; + Log::trace() << "Encoded tile #" << tileIndex << " in " << imgSize << " bytes." << Log::end; + tiles[tileIndex++].setImgSize(imgSize); } - if (makeSlow) - delay(); +#if ENABLE_DEBUG + const auto tileMsg = tileCombined.serialize("tilecombine:") + " renderid=" + Util::UniqueId() + "\n"; +#else + const auto tileMsg = tileCombined.serialize("tilecombine:") + "\n"; #endif + Log::trace("Sending back painted tiles for " + tileMsg); + + std::vector<char> response; + response.resize(tileMsg.size() + output.size()); + std::copy(tileMsg.begin(), tileMsg.end(), response.begin()); + std::copy(output.begin(), output.end(), response.begin() + tileMsg.size()); + + const auto length = response.size(); + if (length > SMALL_MESSAGE_SIZE) + { + const std::string nextmessage = "nextmessage: size=" + std::to_string(length); + ws->sendFrame(nextmessage.data(), nextmessage.size()); + } + + ws->sendFrame(response.data(), length, WebSocket::FRAME_BINARY); } private: @@ -1254,6 +1203,13 @@ void lokit_main(const std::string& childRoot, document->renderTile(tokens, ws); } } + else if (tokens[0] == "tilecombine") + { + if (document) + { + document->renderCombinedTiles(tokens, ws); + } + } else if (document && document->canDiscard()) { TerminationFlag = true; _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits