From 97a22b6f5fea5666025189231f5fecc4946ceb0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Thu, 23 Apr 2015 11:39:12 +0200 Subject: [PATCH] upload all GL objects before rendering a frame This moves all texture updates and buffer uploads to the beginning of every frame, before we issue any drawing commands. This means the GPU typically has much more time to upload the data in the background until we actually need it, and doesn't have to pause to store/load the framebuffer. --- src/mbgl/geometry/buffer.hpp | 1 + src/mbgl/geometry/glyph_atlas.cpp | 6 +++ src/mbgl/geometry/glyph_atlas.hpp | 5 +++ src/mbgl/geometry/line_atlas.cpp | 6 +++ src/mbgl/geometry/line_atlas.hpp | 5 +++ src/mbgl/geometry/sprite_atlas.cpp | 6 +++ src/mbgl/geometry/sprite_atlas.hpp | 7 ++- src/mbgl/renderer/debug_bucket.cpp | 3 ++ src/mbgl/renderer/fill_bucket.cpp | 8 ++++ src/mbgl/renderer/line_bucket.cpp | 5 +++ src/mbgl/renderer/painter.cpp | 66 ++++++++++++++++++++--------- src/mbgl/renderer/raster_bucket.cpp | 3 ++ src/mbgl/renderer/symbol_bucket.cpp | 11 +++++ 13 files changed, 111 insertions(+), 21 deletions(-) diff --git a/src/mbgl/geometry/buffer.hpp b/src/mbgl/geometry/buffer.hpp index 01af5df18..7e3ced442 100644 --- a/src/mbgl/geometry/buffer.hpp +++ b/src/mbgl/geometry/buffer.hpp @@ -67,6 +67,7 @@ public: return buffer; } + // Uploads the buffer to the GPU to be available when we need it. inline void upload() { if (!buffer) { bind(); diff --git a/src/mbgl/geometry/glyph_atlas.cpp b/src/mbgl/geometry/glyph_atlas.cpp index f690004b5..378664f30 100644 --- a/src/mbgl/geometry/glyph_atlas.cpp +++ b/src/mbgl/geometry/glyph_atlas.cpp @@ -140,6 +140,12 @@ void GlyphAtlas::removeGlyphs(uintptr_t tileUID) { } } +void GlyphAtlas::upload() { + if (dirty) { + bind(); + } +} + void GlyphAtlas::bind() { if (!texture) { MBGL_CHECK_ERROR(glGenTextures(1, &texture)); diff --git a/src/mbgl/geometry/glyph_atlas.hpp b/src/mbgl/geometry/glyph_atlas.hpp index a25c735a8..dfa568f0f 100644 --- a/src/mbgl/geometry/glyph_atlas.hpp +++ b/src/mbgl/geometry/glyph_atlas.hpp @@ -24,8 +24,13 @@ public: GlyphPositions&); void removeGlyphs(uintptr_t tileUID); + // Binds the atlas texture to the GPU, and uploads data if it is out of date. void bind(); + // Uploads the texture to the GPU to be available when we need it. This is a lazy operation; + // the texture is only bound when the data is out of date (=dirty). + void upload(); + const uint16_t width = 0; const uint16_t height = 0; diff --git a/src/mbgl/geometry/line_atlas.cpp b/src/mbgl/geometry/line_atlas.cpp index f64989d66..0dcaab2c4 100644 --- a/src/mbgl/geometry/line_atlas.cpp +++ b/src/mbgl/geometry/line_atlas.cpp @@ -129,6 +129,12 @@ LinePatternPos LineAtlas::addDash(const std::vector &dasharray, bool roun return position; }; +void LineAtlas::upload() { + if (dirty) { + bind(); + } +} + void LineAtlas::bind() { std::lock_guard lock(mtx); diff --git a/src/mbgl/geometry/line_atlas.hpp b/src/mbgl/geometry/line_atlas.hpp index df60a2dec..3683272f9 100644 --- a/src/mbgl/geometry/line_atlas.hpp +++ b/src/mbgl/geometry/line_atlas.hpp @@ -19,8 +19,13 @@ public: LineAtlas(uint16_t width, uint16_t height); ~LineAtlas(); + // Binds the atlas texture to the GPU, and uploads data if it is out of date. void bind(); + // Uploads the texture to the GPU to be available when we need it. This is a lazy operation; + // the texture is only bound when the data is out of date (=dirty). + void upload(); + LinePatternPos getDashPosition(const std::vector&, bool); LinePatternPos addDash(const std::vector &dasharray, bool round); diff --git a/src/mbgl/geometry/sprite_atlas.cpp b/src/mbgl/geometry/sprite_atlas.cpp index c2686e2f3..a6156febb 100644 --- a/src/mbgl/geometry/sprite_atlas.cpp +++ b/src/mbgl/geometry/sprite_atlas.cpp @@ -230,6 +230,12 @@ void SpriteAtlas::setSprite(util::ptr sprite_) { }); } +void SpriteAtlas::upload() { + if (dirty) { + bind(); + } +} + void SpriteAtlas::bind(bool linear) { bool first = false; if (!texture) { diff --git a/src/mbgl/geometry/sprite_atlas.hpp b/src/mbgl/geometry/sprite_atlas.hpp index 079c15cef..560514310 100644 --- a/src/mbgl/geometry/sprite_atlas.hpp +++ b/src/mbgl/geometry/sprite_atlas.hpp @@ -50,10 +50,13 @@ public: SpriteAtlasPosition getPosition(const std::string& name, bool repeating = false); - // Binds the image buffer of this sprite atlas to the GPU, and uploads data if it is out - // of date. + // Binds the atlas texture to the GPU, and uploads data if it is out of date. void bind(bool linear = false); + // Uploads the texture to the GPU to be available when we need it. This is a lazy operation; + // the texture is only bound when the data is out of date (=dirty). + void upload(); + inline float getWidth() const { return width; } inline float getHeight() const { return height; } inline float getTextureWidth() const { return width * pixelRatio; } diff --git a/src/mbgl/renderer/debug_bucket.cpp b/src/mbgl/renderer/debug_bucket.cpp index 7e367a95b..edd216571 100644 --- a/src/mbgl/renderer/debug_bucket.cpp +++ b/src/mbgl/renderer/debug_bucket.cpp @@ -17,6 +17,9 @@ DebugBucket::DebugBucket(DebugFontBuffer& fontBuffer_) } void DebugBucket::prepare() { + fontBuffer.upload(); + + renderPass = RenderPass::Translucent; } void DebugBucket::render(Painter& painter, const StyleLayer&, const TileID&, const mat4& matrix) { diff --git a/src/mbgl/renderer/fill_bucket.cpp b/src/mbgl/renderer/fill_bucket.cpp index f8e604ee2..39163c863 100644 --- a/src/mbgl/renderer/fill_bucket.cpp +++ b/src/mbgl/renderer/fill_bucket.cpp @@ -201,6 +201,14 @@ void FillBucket::tessellate() { } void FillBucket::prepare() { + vertexBuffer.upload(); + triangleElementsBuffer.upload(); + lineElementsBuffer.upload(); + + // From now on, we're going to render during the opaque and translucent pass. + renderPass = static_cast( + static_cast::type>(RenderPass::Opaque) | + static_cast::type>(RenderPass::Translucent)); } void FillBucket::render(Painter& painter, diff --git a/src/mbgl/renderer/line_bucket.cpp b/src/mbgl/renderer/line_bucket.cpp index 8e3a8ce9c..8ef5306d8 100644 --- a/src/mbgl/renderer/line_bucket.cpp +++ b/src/mbgl/renderer/line_bucket.cpp @@ -343,6 +343,11 @@ void LineBucket::addCurrentVertex(const Coordinate& currentVertex, } void LineBucket::prepare() { + vertexBuffer.upload(); + triangleElementsBuffer.upload(); + + // From now on, we're only going to render during the translucent pass. + renderPass = RenderPass::Translucent; } void LineBucket::render(Painter& painter, diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp index 74ad8b477..06f58f9c4 100644 --- a/src/mbgl/renderer/painter.cpp +++ b/src/mbgl/renderer/painter.cpp @@ -10,6 +10,8 @@ #include #include +#include +#include #include #include @@ -23,7 +25,6 @@ #include #include - #include #include #include @@ -210,10 +211,6 @@ void Painter::prepareTile(const Tile& tile) { void Painter::render(const Style& style, TransformState state_, TimePoint time) { state = state_; - clear(); - resize(); - changeMatrix(); - std::set sources; for (const auto& source : style.sources) { if (source->enabled) { @@ -221,6 +218,27 @@ void Painter::render(const Style& style, TransformState state_, TimePoint time) } } + // - PREPARATION PASS -------------------------------------------------------------------------- + // Uploads all required buffers and images before we do any actual rendering. + + // Figure out what buckets we have to draw and what order we have to draw them in. + const auto order = determineRenderOrder(style); + + tileStencilBuffer.upload(); + tileBorderBuffer.upload(); + spriteAtlas.upload(); + lineAtlas.upload(); + glyphAtlas.upload(); + + for (const auto& item : order) { + if (item.bucket && item.bucket->hasRenderPass(RenderPass::Prepare)) { + item.bucket->prepare(); + } + } + + // - CLIPPING MASKS ---------------------------------------------------------------------------- + // Draws the clipping masks to the stencil buffer. + // Update all clipping IDs. ClipIDGenerator generator; for (const auto& source : sources) { @@ -228,6 +246,10 @@ void Painter::render(const Style& style, TransformState state_, TimePoint time) source->updateMatrices(projMatrix, state); } + clear(); + resize(); + changeMatrix(); + drawClippingMasks(sources); frameHistory.record(time, state.getNormalizedZoom()); @@ -235,15 +257,12 @@ void Painter::render(const Style& style, TransformState state_, TimePoint time) // Actually render the layers if (debug::renderTree) { Log::Info(Event::Render, "{"); indent++; } - // Figure out what buckets we have to draw and what order we have to draw them in. - const auto order = determineRenderOrder(style); // TODO: Correctly compute the number of layers recursively beforehand. const float strata_thickness = 1.0f / (order.size() + 1); - // - FIRST PASS ------------------------------------------------------------ - // Render everything top-to-bottom by using reverse iterators. Render opaque - // objects first. + // - OPAQUE PASS ------------------------------------------------------------------------------- + // Render everything top-to-bottom by using reverse iterators. Render opaque objects first. if (debug::renderTree) { Log::Info(Event::Render, "%*s%s", indent++ * 4, "", "OPAQUE {"); @@ -253,9 +272,11 @@ void Painter::render(const Style& style, TransformState state_, TimePoint time) for (auto it = order.rbegin(), end = order.rend(); it != end; ++it, ++i) { const auto& item = *it; if (item.bucket && item.tile) { - setStrata(i * strata_thickness); - prepareTile(*item.tile); - item.bucket->render(*this, item.layer, item.tile->id, item.tile->matrix); + if (item.bucket->hasRenderPass(RenderPass::Opaque)) { + setStrata(i * strata_thickness); + prepareTile(*item.tile); + item.bucket->render(*this, item.layer, item.tile->id, item.tile->matrix); + } } else { renderBackground(item.layer); } @@ -264,9 +285,9 @@ void Painter::render(const Style& style, TransformState state_, TimePoint time) Log::Info(Event::Render, "%*s%s", --indent * 4, "", "}"); } - // - SECOND PASS ----------------------------------------------------------- - // Make a second pass, rendering translucent objects. This time, we render - // bottom-to-top. + // - TRANSLUCENT PASS -------------------------------------------------------------------------- + // Make a second pass, rendering translucent objects. This time, we render bottom-to-top. + if (debug::renderTree) { Log::Info(Event::Render, "%*s%s", indent++ * 4, "", "TRANSLUCENT {"); } @@ -275,9 +296,11 @@ void Painter::render(const Style& style, TransformState state_, TimePoint time) for (auto it = order.begin(), end = order.end(); it != end; ++it, --i) { const auto& item = *it; if (item.bucket && item.tile) { - setStrata(i * strata_thickness); - prepareTile(*item.tile); - item.bucket->render(*this, item.layer, item.tile->id, item.tile->matrix); + if (item.bucket->hasRenderPass(RenderPass::Translucent)) { + setStrata(i * strata_thickness); + prepareTile(*item.tile); + item.bucket->render(*this, item.layer, item.tile->id, item.tile->matrix); + } } } if (debug::renderTree) { @@ -293,6 +316,11 @@ void Painter::render(const Style& style, TransformState state_, TimePoint time) for (const auto& source : sources) { source->finishRender(*this); } + + // TODO: Find a better way to unbind VAOs after we're done with them without introducing + // unnecessary bind(0)/bind(N) sequences. + MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, 0)); + MBGL_CHECK_ERROR(gl::BindVertexArray(0)); } std::vector Painter::determineRenderOrder(const Style& style) { diff --git a/src/mbgl/renderer/raster_bucket.cpp b/src/mbgl/renderer/raster_bucket.cpp index 7f1f92b5c..946d81749 100644 --- a/src/mbgl/renderer/raster_bucket.cpp +++ b/src/mbgl/renderer/raster_bucket.cpp @@ -14,6 +14,9 @@ RasterBucket::RasterBucket(TexturePool& texturePool, const StyleLayoutRaster& la } void RasterBucket::prepare() { + if (hasData()) { + raster.upload(); + } } void RasterBucket::render(Painter& painter, diff --git a/src/mbgl/renderer/symbol_bucket.cpp b/src/mbgl/renderer/symbol_bucket.cpp index d520e4de2..62ede2840 100644 --- a/src/mbgl/renderer/symbol_bucket.cpp +++ b/src/mbgl/renderer/symbol_bucket.cpp @@ -37,6 +37,17 @@ SymbolBucket::~SymbolBucket() { } void SymbolBucket::prepare() { + if (hasTextData()) { + text.vertices.upload(); + text.triangles.upload(); + } + if (hasIconData()) { + icon.vertices.upload(); + icon.triangles.upload(); + } + + // From now on, we're going to render during the opaque and translucent pass. + renderPass = RenderPass::Translucent; } void SymbolBucket::render(Painter& painter,