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:
Konstantin Käfer
2015-04-23 11:39:12 +02:00
parent 28b9b3f9d6
commit 97a22b6f5f
13 changed files with 111 additions and 21 deletions

View File

@@ -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();

View File

@@ -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));

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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; }

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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) {

View File

@@ -14,6 +14,9 @@ RasterBucket::RasterBucket(TexturePool& texturePool, const StyleLayoutRaster& la
}
void RasterBucket::prepare() {
if (hasData()) {
raster.upload();
}
}
void RasterBucket::render(Painter& painter,

View File

@@ -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,