You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On the 16th / 17th of August (depending on timezone) 2023 a Q&A session for the third Modularization PR took place.
The first two PRs consisted of architectural changes and additions, the second PR was the integration of the new renderer and this last PR includes the shaders and the layers which use these shaders (although not directly but through the Drawable interface, making them graphics backend agnostic).
These PRs are a collaborative effort from the 'Metal team'. So called because after working on modularization the the alternate rendering backend that they are now implementing is for Apple's Metal. Note that these session notes are written by your friendly MapLibre Native maintainer Bart Louwers, so there might be errors or things I did not understood well.
Attendees
Steve Gifford (Metal team)
Alex Cristici (Metal team)
Stefan Karschti (Metal team)
Marc Wilson (Metal team)
Tim Sylvester (Metal team)
Jesse Crocker
Bart Louwers
Layers
To get a feeling for how a layer is structured, we first took a look at the Circle Layer. The first important thing to note about layers, including the Circle Layer, is that the OpenGL specific render() method was replaced by a graphics backend agnostic update() method. The various kinds of Drawables are responsible for drawing themselves, the layers just set up these drawables (or remove them when they are no longer needed).
MapLibre Native is very tile centric. So first a check is made if there are any tiles that need to be rendered, if not, all existing drawables are removed and we are done.
Something else should be noted at this point, which is that an update() method can set Drawables for multiple render passes. The circle layer happens to only to run when the translucent render pass is enabled:
if (!(mbgl::underlying_type(renderPass) & evaluatedProperties->renderPasses)) {
return;
}
Now we are ready to iterate over all the render tiles.
Buckets
Buckets contain the raw data needed for rendering. They played a bigger role previously, because they also uploaded this data to the GPU (now the Drawables are responsible for this). In the future the concept of Buckets may be revisited (don't pin me on this, but I think one reason is so we can use geometry shaders?).
In any case, we get the bucket corresponding to the tile that is being worked with. Using the bucket we can get the 'paint property binders' which are needed to set up the uniform buffer object (UBO, see next section).
During a style change, it is possible that drawables are created for an old bucket. In this case we need to remove these drawables to avoid bad behavior. That is what this section does:
if (prevBucketID != util::SimpleIdentity::Empty && prevBucketID != bucket.getID()) {
// This tile was previously set up from a different bucket, drop and re-create any drawables for it.
removeTile(renderPass, tileID);
}
setRenderTileBucketID(tileID, bucket.getID());
UBOs
Uniform buffer objects are introduced by the Metal team that allow passing a block of uniforms to one or more shaders.
The shader source code is actually generated at runtime and will differ based on which UBOs that are needed. This happens in the rendering backend specific version of ShaderGroup, a class that allows retrieving the correct (possibly cached) shader permutation. For the aforementioned shader source generation, this happens in this for-loop:
for (unsignedint i = 0; i < propertiesAsUniforms.size(); i++) {
if (propertiesAsUniforms[i].empty()) {
continue;
}
key |= 1 << i;
additionalDefines += "#define HAS_UNIFORM_u_";
additionalDefines += propertiesAsUniforms[i];
additionalDefines += "\n";
}
Lastly, if there already exist drawables for this tile they are updated, and the update() is done. Otherwise new drawables are created with the help of a DrawableBuilder.
More Complex Layers
We briefly looked at the RenderSymbolLayer and the RenderHeatmapLayer as examples of more complex layers.
Something interesting to note about the first is that it makes use of the fact that Drawables can store arbitrary data that is retrieved with getData(). This is then cast to SymbolDrawableData in the case of symbol drawables, and used to determine if a particular symbol drawable represents text or an icon.
The heatmap layer is interesting because first it sets up the drawables to render the heatmap per tile. It uses an off-screen render target for this. Then it uses another type of drawable that will gets the resulting texture on the screen.
metalOpenGLIssues related to the OpenGL renderer backend
1 participant
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
On the 16th / 17th of August (depending on timezone) 2023 a Q&A session for the third Modularization PR took place.
The first two PRs consisted of architectural changes and additions, the second PR was the integration of the new renderer and this last PR includes the shaders and the layers which use these shaders (although not directly but through the Drawable interface, making them graphics backend agnostic).
These PRs are a collaborative effort from the 'Metal team'. So called because after working on modularization the the alternate rendering backend that they are now implementing is for Apple's Metal. Note that these session notes are written by your friendly MapLibre Native maintainer Bart Louwers, so there might be errors or things I did not understood well.
Attendees
Layers
To get a feeling for how a layer is structured, we first took a look at the Circle Layer. The first important thing to note about layers, including the Circle Layer, is that the OpenGL specific
render()
method was replaced by a graphics backend agnosticupdate()
method. The various kinds ofDrawable
s are responsible for drawing themselves, the layers just set up these drawables (or remove them when they are no longer needed).So let's go over the
update()
method:maplibre-native/src/mbgl/renderer/layers/render_circle_layer.cpp
Line 286 in 6c5a5ba
MapLibre Native is very tile centric. So first a check is made if there are any tiles that need to be rendered, if not, all existing drawables are removed and we are done.
maplibre-native/src/mbgl/renderer/layers/render_circle_layer.cpp
Lines 310 to 313 in 6c5a5ba
Next, if there still are any drawables without a corresponding 'render tile' (tile that needs to be rendered), those are removed.
maplibre-native/src/mbgl/renderer/layers/render_circle_layer.cpp
Lines 322 to 323 in 6c5a5ba
Something else should be noted at this point, which is that an
update()
method can set Drawables for multiple render passes. The circle layer happens to only to run when the translucent render pass is enabled:maplibre-native/src/mbgl/renderer/layers/render_circle_layer.cpp
Lines 316 to 320 in 6c5a5ba
Now we are ready to iterate over all the render tiles.
Buckets
Buckets contain the raw data needed for rendering. They played a bigger role previously, because they also uploaded this data to the GPU (now the Drawables are responsible for this). In the future the concept of Buckets may be revisited (don't pin me on this, but I think one reason is so we can use geometry shaders?).
In any case, we get the bucket corresponding to the tile that is being worked with. Using the bucket we can get the 'paint property binders' which are needed to set up the uniform buffer object (UBO, see next section).
During a style change, it is possible that drawables are created for an old bucket. In this case we need to remove these drawables to avoid bad behavior. That is what this section does:
maplibre-native/src/mbgl/renderer/layers/render_circle_layer.cpp
Lines 340 to 345 in 6c5a5ba
UBOs
Uniform buffer objects are introduced by the Metal team that allow passing a block of uniforms to one or more shaders.
The shader source code is actually generated at runtime and will differ based on which UBOs that are needed. This happens in the rendering backend specific version of ShaderGroup, a class that allows retrieving the correct (possibly cached) shader permutation. For the aforementioned shader source generation, this happens in this
for
-loop:maplibre-native/include/mbgl/shaders/gl/shader_group_gl.hpp
Lines 28 to 36 in aac1335
Lastly, if there already exist drawables for this tile they are updated, and the
update()
is done. Otherwise new drawables are created with the help of a DrawableBuilder.More Complex Layers
We briefly looked at the
RenderSymbolLayer
and theRenderHeatmapLayer
as examples of more complex layers.Something interesting to note about the first is that it makes use of the fact that Drawables can store arbitrary data that is retrieved with
getData()
. This is then cast toSymbolDrawableData
in the case of symbol drawables, and used to determine if a particular symbol drawable represents text or an icon.maplibre-native/src/mbgl/renderer/layers/render_symbol_layer.cpp
Lines 814 to 816 in 6c5a5ba
The heatmap layer is interesting because first it sets up the drawables to render the heatmap per tile. It uses an off-screen render target for this. Then it uses another type of drawable that will gets the resulting texture on the screen.
Tweakers
Where Layers are intended for somewhat declaratively setting up the Drawables, Tweakers make it possible to 'tweak' Drawables per frame. This allows extracting logic that would otherwise clutter up the Layer. All layers have a corresponding tweaker: https://github.com/maplibre/maplibre-native/tree/6c5a5ba0ae9f2510275cd1b9c3cf59d2adc74af5/src/mbgl/renderer/layers
Beta Was this translation helpful? Give feedback.
All reactions