We want to be able to offload rendering onto a separate thread to free up the main thread on some platforms (most notably Android and Qt). The main thread should, as much as possible, be used only for user input and drawing so that a high frame-rate can be maintained.
A lot of work is already delegated to dedicate workers, but in order to offer a smooth experience on somewhat more lightweight hardware we need to eliminate some of the processing currently done on the main thread. Most notably:
Not all platforms need/should use a separate thread for rendering, so we want to enable a model where either is possible. Also, as an additional hurdle, on some platforms we want to be able to integrate with existing platform specific solutions such as the GLSurfaceView which come with a dedicated render thread that prohibits the use of a run loop as it blocks on a mutex.
In order to support this, we probably need a model where we utilise three threads:
Eg. Something like (very high-level):

Preparation
Currently a couple of classes contain data that is needed on both the main thread (to offer our public, synchronous api) and on the would-be render thread. In preparation of supporting a render thread these classes need to be split up accordingly. In the case of Layers and Sources, we've decided to aim for a model where we have a parallel tree of Render items which have a reference to their counterpart's immutable implementation class.

Related:
Currently underway
GlyphAtlas callbacksLayer::Impl and Source::Impl subclasses immutable so they may be shared between style and render layers/sources https://github.com/mapbox/mapbox-gl-native/issues/8929add/remove/getImage and the style::Image map itself to Style, and make Style responsible for loading the sprite PNG and JSON, parceling out the individual images, and filling up the map.GlyphAtlas will belong to RenderStyle, which won't be able to propagate onGlyphsLoaded/Error messages to a parent observer. Investigate whether this is necessary/how to eliminate. (Likely do the same for onSpriteLoaded/Error for consistency, even though it could be preserved.)lastError used for? Should it belong to Style or RenderStyle exclusively, or does it need to be split?TileParameters will have a RenderStyle reference rather than a Style reference. GeometryTile will need to iterate over RenderLayers rather than Layers, obtain an immutable Impl for each, and pass them to the worker rather than a copy of the Layer.SpriteAtlas will need access to images (by immutable reference).renderSources and renderLayers (in setJSON and {add,remove}{Source,Layer}) with update-time updates based on diffing immutables.updateBatch mechanism with immutable layer diffing (#9119)Style and RenderStyle need isLoaded and dumpDebugLogs methods. In the places that call Style::isLoaded, add a call to RenderStyle::isLoaded(); likewise for dumpDebugLogs. (#9119)GlyphAtlas will need access to a glyph URL (by immutable reference). (#9119)update will move to RenderStyle. Replace direct access to sources, layers, images, transition options, classes, and light with immutable references passed via UpdateParameters. (#9119)FileSource using actors (#7678), or otherwise make DefaultFileSource threadsafeAnnotationManager to use runtime styling APIs (#6181), or otherwise make it threadsafe https://github.com/mapbox/mapbox-gl-native/pull/9220GeoJSONSource's use of GeoJSONVT and Supercluster is threadsafeRenderer/Renderer::Impl pair:std::unique_ptr<Renderer> provided by coreRenderer::Impl to hide details from public headerRenderer methods:update(const UpdateParameters&) (UpdateParameters forward declared)render(const RenderParameters&) (RenderParameters forward declared)Painter and RenderStyle into Renderer::ImplsetRenderer(std::unique_ptr<Renderer>) (might be null to "reset" renderer during shutdown)update(std::unique_ptr<UpdateParameters>)render(std::unique_ptr<RenderParameters>)Renderer either immediately (synchronously) or via sending message to rendering thread (asynchronously)Renderer to trigger a re-render when needed (animation)RenderStyleObserver/MapObserver notificationsNext steps
RenderFrontend for AndroidRenderer if we lose the context. The alternative is to keep a copy of all bucket data in main memory, so that we can recreate the GL buffers from it. This would ~2x the memory usage of maps, just to get somewhat faster reloads when the context comes back -- not worth it IMO.cc @mapbox/gl
Related (follow-up) work on Android to implement GlSurfaceView to contain the map view: https://github.com/mapbox/mapbox-gl-native/issues/5766.
Summary of the task of splitting SpriteAtlas: we want to start treating the set of images specified by the style more similarly to how we treat layers and sources. I.e. Style should own a std::vector<std::unique_ptr<Image>>. The rest of SpriteAtlas is rendering related and will belong to the RenderStyle. We'll bridge access to Images between Style and the SpriteAtlas in RenderStyle using the same mechanism as for Layers and Sources (most likely via sharing references to immutable objects).
What is not clear from this design is how something like "query rendered features" is going to work synchronously. We need the "evaluated" state for that, right?
@tmpsantos Very good question. ATM I see two options:
We can support both of course, but moving to the later would have my preference.
I did a rough dry run at splitting Style and RenderStyle just to see what will need to change to allow that to happen. Here's my list. Items marked with ℹ️ depend on having infrastructure in place for immutable objects.
add/remove/getImage and the style::Image map itself to Style, and make Style responsible for loading the sprite PNG and JSON, parceling out the individual images, and filling up the map.GlyphAtlas will belong to RenderStyle, which won't be able to propagate onGlyphsLoaded/Error messages to a parent observer. Investigate whether this is necessary/how to eliminate. (Likely do the same for onSpriteLoaded/Error for consistency, even though it could be preserved.)lastError used for? Should it belong to Style or RenderStyle exclusively, or does it need to be split?Style and RenderStyle need isLoaded and dumpDebugLogs methods. In the places that call Style::isLoaded, add a call to RenderStyle::isLoaded(); likewise for dumpDebugLogs.TileParameters will have a RenderStyle reference rather than a Style reference. GeometryTile will need to iterate over RenderLayers rather than Layers, obtain an immutable Impl for each, and pass them to the worker rather than a copy of the Layer.GlyphAtlas will need access to a glyph URL (by immutable reference).SpriteAtlas will need access to images (by immutable reference).renderSources and renderLayers (in setJSON and {add,remove}{Source,Layer}) with update-time updates based on diffing immutables.updateBatch mechanism with immutable layer diffing.update will move to RenderStyle. Replace direct access to sources, layers, images, transition options, classes, and light with immutable references passed via UpdateParameters.I'm continuing to work on prerequisites for the ℹ️ items, but I could use help on the others.
@jfirebaugh
Promote add/remove/getImage and the style::Image map itself to Style, and make Style responsible for loading the sprite PNG and JSON, parceling out the individual images, and filling up the map.
I will take care of that while looking at the SpriteAtlas
GlyphAtlas will belong to RenderStyle, which won't be able to propagate onGlyphsLoaded/Error messages to a parent observer. Investigate whether this is necessary/how to eliminate
Depending on the concurrency model / necessity, we could retain this. I was imagining re-using the actor model for communication between Style/RenderStyle. In this case we can propagate these messages back from RenderStyle to Style.
I'm finishing up the Light changes you requested now and then will start to pick up the items you list one by one.

Promote add/remove/getImage and the style::Image map itself to Style, and make Style responsible for loading the sprite PNG and JSON, parceling out the individual images, and filling up the map.
Looking into cutting up the SpriteAtlas; it seems that we would need a way for the RenderStyle-to-be to communicate back to the Style to request images since the GeometryTiles (loaded by RenderStyle) require it. Doesn't seem to be a way around this if we coordinate loading the images on the main thread as we need access to both images added through the api and images loaded through the sprite url for {add/remove/get}Image.
We should pass the RenderStyle an interface (like ImageProvider) to use when requesting images, which we could implement differently later on to abstract over the threads Style and RenderStyle live on.
we would need a way for the
RenderStyle-to-be to communicate back to theStyleto request images
No, communication is one way; there's no communication in Render ⇢ Style direction. All data will be provided in the Style ⇢ Render direction via immutable references, including for images. This means we probably need to split style::Image and style::Image::Impl, so we can provide Immutable<style::Image::Impl>s to the RenderStyle.
Like it's responsible for loading source TileJSON, Style will be responsible for loading the sprite. (In a future revision of the style specification I hope that both sprite images and source TileJSON will be mandatorily inline.)
I added a write-up of lifetimes and object ownership in GLSurfaceView over in https://github.com/mapbox/mapbox-gl-native/issues/5766#issuecomment-300180689
I've been keeping a list of potential followup changes. These are things that I've noticed when working on this that would be nice to do at some point but so far aren't directly necessary:
renderer directoryfoo_bucket.cpp, foo_render_layer.cpp, foo_program.cpp, foo_shaders.cpp all in the same directory)Transform/TransformState ⇢ Camera/Immutable<Camera::Impl>RenderLayer::{transition,evaluate,setImpl} into a single update method (like RenderSource)RenderStyle::update and RenderStyle::getRenderData?MapMode ⇢ RenderModeSourceType enum to source_type.hpp (like layer_type.hpp)getRenderSourceRenderStyle::queryRenderedFeaturesaddImage to update existing images -- should be a separate APILayer/Source/Image (related to #8882)Style a public API, remove runtime styling APIs from Map (related to #6386)