From 3a6c8bbccbe868ecd190b0959badd5320ae1885c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 23 Dec 2016 14:54:31 -0800 Subject: [PATCH] [core] Unify update and render into a single step Update only when, and just prior to, rendering, giving no opportunity to interleave unexpected state changes. This means that every time anything about the state is changed, we'll have to attempt a render to reflect that change. In the case of continuous rendering this has happened before this change as well, but it leaves no room for time to pass between an update and a render. In the case of still image rendering, a render call will only actually paint something to the view when all resources have been loaded. --- src/mbgl/map/map.cpp | 170 +++++++++++++++++++++------------------- src/mbgl/map/update.hpp | 3 +- 2 files changed, 89 insertions(+), 84 deletions(-) diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index d76044cd47c..e9d4d9e2475 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -63,8 +63,8 @@ class Map::Impl : public style::Observer { void onStyleError() override; void onResourceError(std::exception_ptr) override; - void update(); void render(View&); + void renderStill(); void loadStyleJSON(const std::string&); @@ -83,7 +83,6 @@ class Map::Impl : public style::Observer { MapDebugOptions debugOptions { MapDebugOptions::NoDebug }; Update updateFlags = Update::Nothing; - util::AsyncTask asyncUpdate; std::unique_ptr annotationManager; std::unique_ptr painter; @@ -96,10 +95,11 @@ class Map::Impl : public style::Observer { std::unique_ptr styleRequest; - std::unique_ptr stillImageRequest; size_t sourceCacheSize; - TimePoint timePoint; bool loading = false; + + util::AsyncTask asyncInvalidate; + std::unique_ptr stillImageRequest; }; Map::Map(Backend& backend, @@ -142,8 +142,14 @@ Map::Impl::Impl(Map& map_, mode(mode_), contextMode(contextMode_), pixelRatio(pixelRatio_), - asyncUpdate([this] { update(); }), - annotationManager(std::make_unique(pixelRatio)) { + annotationManager(std::make_unique(pixelRatio)), + asyncInvalidate([this] { + if (mode == MapMode::Continuous) { + backend.invalidate(); + } else { + renderStill(); + } + }) { } Map::~Map() { @@ -185,64 +191,37 @@ void Map::renderStill(View& view, StillImageCallback callback) { } impl->stillImageRequest = std::make_unique(view, std::move(callback)); - impl->updateFlags |= Update::RenderStill; - impl->asyncUpdate.send(); + impl->onUpdate(Update::Repaint); } -void Map::render(View& view) { - if (!impl->style) { +void Map::Impl::renderStill() { + if (!stillImageRequest) { return; } - if (impl->renderState == RenderState::Never) { - impl->backend.notifyMapChange(MapChangeWillStartRenderingMap); - } - - impl->backend.notifyMapChange(MapChangeWillStartRenderingFrame); - - const Update flags = impl->transform.updateTransitions(Clock::now()); - - impl->render(view); - - impl->backend.notifyMapChange(isFullyLoaded() ? - MapChangeDidFinishRenderingFrameFullyRendered : - MapChangeDidFinishRenderingFrame); - - if (!isFullyLoaded()) { - impl->renderState = RenderState::Partial; - } else if (impl->renderState != RenderState::Fully) { - impl->renderState = RenderState::Fully; - impl->backend.notifyMapChange(MapChangeDidFinishRenderingMapFullyRendered); - if (impl->loading) { - impl->loading = false; - impl->backend.notifyMapChange(MapChangeDidFinishLoadingMap); - } - } - - // Triggers an asynchronous update, that eventually triggers a view - // invalidation, causing renderSync to be called again if in transition. - if (flags != Update::Nothing) { - impl->onUpdate(flags); - } + // TODO: determine whether we need activate/deactivate + BackendScope guard(backend); + render(stillImageRequest->view); } void Map::triggerRepaint() { impl->backend.invalidate(); } -void Map::Impl::update() { - if (!style) { - updateFlags = Update::Nothing; - } +void Map::render(View& view) { + impl->render(view); +} - if (updateFlags == Update::Nothing || (mode == MapMode::Still && !stillImageRequest)) { +void Map::Impl::render(View& view) { + if (!style) { return; } - // This time point is used to: - // - Calculate style property transitions; - // - Hint style sources to notify when all its tiles are loaded; - timePoint = Clock::now(); + TimePoint timePoint = Clock::now(); + + auto flags = transform.updateTransitions(timePoint); + + updateFlags |= flags; if (style->loaded && updateFlags & Update::AnnotationStyle) { annotationManager->updateStyle(*style); @@ -276,46 +255,74 @@ void Map::Impl::update() { style->updateTiles(parameters); - if (mode == MapMode::Continuous) { - backend.invalidate(); - } else if (stillImageRequest && style->isLoaded()) { - // TODO: determine whether we need activate/deactivate - BackendScope guard(backend); - render(stillImageRequest->view); - } - updateFlags = Update::Nothing; -} -void Map::Impl::render(View& view) { if (!painter) { painter = std::make_unique(backend.getContext(), transform.getState(), pixelRatio); } - FrameData frameData { timePoint, - pixelRatio, - mode, - contextMode, - debugOptions }; + if (mode == MapMode::Continuous) { + if (renderState == RenderState::Never) { + backend.notifyMapChange(MapChangeWillStartRenderingMap); + } + + backend.notifyMapChange(MapChangeWillStartRenderingFrame); + + FrameData frameData { timePoint, + pixelRatio, + mode, + contextMode, + debugOptions }; + + painter->render(*style, + frameData, + view, + annotationManager->getSpriteAtlas()); + + painter->cleanup(); + + backend.notifyMapChange(style->isLoaded() ? + MapChangeDidFinishRenderingFrameFullyRendered : + MapChangeDidFinishRenderingFrame); + + if (!style->isLoaded()) { + renderState = RenderState::Partial; + } else if (renderState != RenderState::Fully) { + renderState = RenderState::Fully; + backend.notifyMapChange(MapChangeDidFinishRenderingMapFullyRendered); + if (loading) { + loading = false; + backend.notifyMapChange(MapChangeDidFinishLoadingMap); + } + } - painter->render(*style, - frameData, - view, - annotationManager->getSpriteAtlas()); + if (style->hasTransitions()) { + flags |= Update::RecalculateStyle; + } else if (painter->needsAnimation()) { + flags |= Update::Repaint; + } + + // Only schedule an update if we need to paint another frame due to transitions or + // animations that are still in progress + if (flags != Update::Nothing) { + onUpdate(flags); + } + } else if (stillImageRequest && style->isLoaded()) { + FrameData frameData { timePoint, + pixelRatio, + mode, + contextMode, + debugOptions }; + + painter->render(*style, + frameData, + view, + annotationManager->getSpriteAtlas()); - if (mode == MapMode::Still) { auto request = std::move(stillImageRequest); request->callback(nullptr); - } - painter->cleanup(); - - if (style->hasTransitions()) { - updateFlags |= Update::RecalculateStyle; - asyncUpdate.send(); - } else if (painter->needsAnimation()) { - updateFlags |= Update::Repaint; - asyncUpdate.send(); + painter->cleanup(); } } @@ -399,8 +406,7 @@ void Map::Impl::loadStyleJSON(const std::string& json) { map.setPitch(map.getDefaultPitch()); } - updateFlags |= Update::Classes | Update::RecalculateStyle | Update::AnnotationStyle; - asyncUpdate.send(); + onUpdate(Update::Classes | Update::RecalculateStyle | Update::AnnotationStyle); } std::string Map::getStyleURL() const { @@ -1075,9 +1081,9 @@ void Map::Impl::onSourceAttributionChanged(style::Source&, const std::string&) { void Map::Impl::onUpdate(Update flags) { updateFlags |= flags; - asyncUpdate.send(); + asyncInvalidate.send(); } - + void Map::Impl::onStyleLoaded() { backend.notifyMapChange(MapChangeDidFinishLoadingStyle); } diff --git a/src/mbgl/map/update.hpp b/src/mbgl/map/update.hpp index dc383b819e0..74ab22dd8af 100644 --- a/src/mbgl/map/update.hpp +++ b/src/mbgl/map/update.hpp @@ -6,10 +6,9 @@ namespace mbgl { enum class Update { Nothing = 0, + Repaint = 1 << 0, Classes = 1 << 2, RecalculateStyle = 1 << 3, - RenderStill = 1 << 4, - Repaint = 1 << 5, AnnotationStyle = 1 << 6, AnnotationData = 1 << 7, Layout = 1 << 8