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.
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -129,6 +129,12 @@ LinePatternPos LineAtlas::addDash(const std::vector<float> &dasharray, bool roun
|
||||
return position;
|
||||
};
|
||||
|
||||
void LineAtlas::upload() {
|
||||
if (dirty) {
|
||||
bind();
|
||||
}
|
||||
}
|
||||
|
||||
void LineAtlas::bind() {
|
||||
std::lock_guard<std::recursive_mutex> lock(mtx);
|
||||
|
||||
|
||||
@@ -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<float>&, bool);
|
||||
LinePatternPos addDash(const std::vector<float> &dasharray, bool round);
|
||||
|
||||
|
||||
@@ -230,6 +230,12 @@ void SpriteAtlas::setSprite(util::ptr<Sprite> sprite_) {
|
||||
});
|
||||
}
|
||||
|
||||
void SpriteAtlas::upload() {
|
||||
if (dirty) {
|
||||
bind();
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteAtlas::bind(bool linear) {
|
||||
bool first = false;
|
||||
if (!texture) {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<RenderPass>(
|
||||
static_cast<std::underlying_type<RenderPass>::type>(RenderPass::Opaque) |
|
||||
static_cast<std::underlying_type<RenderPass>::type>(RenderPass::Translucent));
|
||||
}
|
||||
|
||||
void FillBucket::render(Painter& painter,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <mbgl/style/style_bucket.hpp>
|
||||
|
||||
#include <mbgl/geometry/sprite_atlas.hpp>
|
||||
#include <mbgl/geometry/line_atlas.hpp>
|
||||
#include <mbgl/geometry/glyph_atlas.hpp>
|
||||
|
||||
#include <mbgl/shader/pattern_shader.hpp>
|
||||
#include <mbgl/shader/plain_shader.hpp>
|
||||
@@ -23,7 +25,6 @@
|
||||
#include <mbgl/shader/dot_shader.hpp>
|
||||
#include <mbgl/shader/gaussian_shader.hpp>
|
||||
|
||||
|
||||
#include <mbgl/util/std.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/mat3.hpp>
|
||||
@@ -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<Source*> 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<RenderItem> Painter::determineRenderOrder(const Style& style) {
|
||||
|
||||
@@ -14,6 +14,9 @@ RasterBucket::RasterBucket(TexturePool& texturePool, const StyleLayoutRaster& la
|
||||
}
|
||||
|
||||
void RasterBucket::prepare() {
|
||||
if (hasData()) {
|
||||
raster.upload();
|
||||
}
|
||||
}
|
||||
|
||||
void RasterBucket::render(Painter& painter,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user