[Qgis-developer] QGIS Multi-threaded Rendering

A Huarte ahuarte47 at yahoo.es
Thu Dec 12 05:15:26 PST 2013

Great work Martin!

I am working to speed up to vector rendering https://github.com/qgis/QGIS/pull/980 (http://hub.qgis.org/issues/8725) using current master branch as base. I wonder if I should better develop my patch using your branch as base.

Anyway, cheers!

> De: Nathan Woodrow <madmanwoo at gmail.com>
>Para: Martin Dobias <wonder.sk at gmail.com> 
>CC: qgis-dev <qgis-developer at lists.osgeo.org> 
>Enviado: Jueves 12 de diciembre de 2013 13:50
>Asunto: Re: [Qgis-developer] QGIS Multi-threaded Rendering
>Great work Martin.  I have been using this branch as my normal QGIS for the last couple of days and it feels loads better.
>I think the idea of the new classes and splitting the responsibly of what each one does up more.
>Myself, or Tamas, will update the MS SQL driver just after Christmas so that is ready to go for the merge.
>- Nathan 
>On Thu, Dec 12, 2013 at 10:14 PM, Martin Dobias <wonder.sk at gmail.com> wrote:
>Hi everyone!
>>[attention: long text ahead]
>>In recent weeks I have been working on moving map rendering into
>>background worker threads and all related infrastructure changes.
>>There is still quite a lot of work to do, but finally I think it is a
>>time for a preview and a broader discussion about the whole thing. Not
>>every little QGIS feature is working yet, but things work fine with
>>most commonly used data sources (GDAL/OGR, PostGIS, SpatiaLite).
>>Please give it a try! The code is available in my QGIS repository on
>>GitHub, the branch is called threading-revival:
>>The plan is to continue working on the project in the following weeks
>>to reintroduce support for features and data providers currently not
>>supported (e.g. WMS, WFS). Hopefully by the time of feature freeze in
>>late January the code will in condition to be merged to master, so the
>>multi-threaded rendering can appear in QGIS 2.2 release.
>>The project has already quite some history: it started as my GSoC
>>project in summer of 2010, unfortunately it was not merged back to
>>master branch because the code never get into production level
>>quality. The scope of the project is not just about moving rendering
>>into background: it is mostly about updating various pieces of QGIS
>>core library and data providers to behave correctly in the case that
>>more threads simultaneously try to access the same resource - until
>>now the assumption always was that there was only one thread. Back in
>>2010, QGIS code was much less ready to change those assumptions. Now,
>>after the release of 2.0, the code is much closer to what we need for
>>multi-threaded rendering: both vector and raster layer code went
>>through a major overhaul in the preparation for 2.0.
>>What to expect from the project:
>>1. better user experience. Browsing the map in canvas gets much
>>snappier - pan and zoom map smoothly with instant preview, without
>>having to wait until rendering of the previous view is finished,
>>without flickers or other annyoances. Even if the map takes longer to
>>render, you are free to do any actions in the meanwhile. It is a bit
>>hard to describe the difference of the overall feel, one needs to try
>>it out :)
>>2. faster rendering of projects with more layers. Finally, it is
>>possible to use the full power of your CPU. The rendering of map
>>layers can be done in parallel: layers will be rendered separately at
>>the same time and then composited together to form the final map
>>image. In theory, rendering of two layers can get twice as fast. The
>>speedup depends a lot on your data.
>>3. starting point for more asynchronous operations. With safe access
>>to map layers from worker threads, more user actions could be
>>processed in background without blocking GUI, e.g. opening of
>>attribute table, running analyses, layer identification or change of
>>What not to expect from the project:
>>- faster rendering of one individual layer. A project with one layer
>>that took five seconds to render will still take five seconds to
>>render. The parallelization happens at the level of map layers. With
>>one map layer QGIS will still use just one core. Optimizing the
>>rendering performance of an individual layer is outside of the scope
>>of this project.
>>What to expect from the project *right now*: things should generally
>>work, except for the following:
>>- data providers: delimited text, gpx, grass, mssql, sql anywhere, wfs, wms, wcs
>>- QGIS server
>>- point displacement renderer
>>For testing, simply use QGIS as you would usually do and see if you
>>feel a difference when browsing the map. In Options dialog, Rendering
>>tab, there are few new configuration options for you to play with: 1.
>>parallel or sequential rendering, 2. map update interval. The parallel
>>rendering may use all your CPU power, while sequential (currently
>>being the default) will use just one CPU core. The default map preview
>>update interval is now set to 250ms - feel free to experiment with
>>other values. Lower values will bring faster updates, at the expense
>>of wasting more time doing just updates instead of real work. Parallel
>>rendering can be switched on/off also directly in the map canvas by
>>pressing 'P' key - useful when you want to quickly compare the
>>difference between sequential and parallel rendering. There is another
>>magical shortcut, 'S' key, that will show very simple stats about the
>>rendering (currently just total rendering time), so you can again
>>quickly compare the impact of various factors (antialiasing, parallel
>>rendering, caching etc). These shortcuts are likely to be removed from
>>the final version, so make sure to use them while they are still
>>Now, it is time for some details about the design decisions I took and
>>their justifications. Non-developers can happily stop reading now,
>>developers are encouraged to read that thoroughly :-) I would be very
>>happy to hear what other devs think about the changes. Nothing is set
>>into stone yet and any critical review will help.
>>- QgsMapRenderer class got deprecated (but do not worry, it still
>>works). The problem with the class is that does two things at once: it
>>stores configuration of the map and it also acts as a rendering
>>engine. This is impractical, because very often it is just necessary
>>to query or change the configuration without actually using the
>>rendering engine. Another problem is the fact that the rendering is
>>started by passing a pointer to arbitrary QPainter - it is fine for
>>sequential rendering, but not for parallel rendering where the
>>rendering happens to temporary images which are composited at any
>>point later. My solution was moving the map configuration (extent,
>>size, DPI, layers, ...) to a new class called QgsMapSettings. The
>>rendering engine got abstracted into a new class QgsMapRendererJob -
>>it is a base class with three implementations (sequential and parallel
>>rendering to QImage, sequential rendering to any QPainter). The class
>>has asynchronous API: after calling start(), the rendering will start
>>in the background and emit finished() signal once done. The client can
>>cancel() the job at any time, or call waitForFinished() to block until
>>the rendering is done.
>>- render caching has been modified. Cached images of layers used to be
>>stored directly in the QgsMapLayer class, however there was no context
>>about the stored images (what extent etc). Also, the solution does not
>>scale if there is more than one map renderer. Now there is a new
>>class, QgsMapRendererCache which keeps all cached images inside and
>>can be used by map renderer jobs. This encapsulation should also allow
>>easier modifications to the way how caching of rendered layers is
>>- map canvas uses the new map renderer job API. Anytime the background
>>rendering is started, it will start periodically update the preview of
>>the new map (before the updates were achieved by calls to
>>qApp->processEvents() while rendering, with various ugly side effects
>>and hacks). The canvas item showing the map has become ordinary canvas
>>item that just stores the rendered georeferenced map image. The map
>>configuration is internally kept in QgsMapSettings class, which is
>>accessible from API. It is still possible to access QgsMapRenderer
>>from map canvas - there is a compatibility layer that keeps
>>QgsMapSettings and QgsMapRenderer in sync, so all plugins should still
>>- rendering of a map layer has changed. Previously, it would be done
>>by calling QgsMapLayer::draw(...). I have found this insufficient for
>>safe rendering in worker thread. The issue is that during the
>>rendering, the user may do some changes to the internal state of the
>>layer which would cause fatal problems to the whole application. For
>>example, by changing vector layer's renderer, the old renderer would
>>get deleted while the worker thread is still using it. There are
>>generally two ways of avoiding such situations: 1. protect the shared
>>resource from simultaneous access by locking or 2. make a copy of the
>>resource. I have decided to go for the latter because: 1. there are
>>potentially many small resources to protect, 2. locking/waiting may
>>severely degrade the performance, 3. it is easy to get the locking
>>wrong, ending up with deadlocks or crashes, 4. copying of resources
>>does not need to be memory and time consuming, especially when using
>>implicit sharing of data (copy-on-write). I have created a new class
>>called QgsMapLayerRenderer. Its use case is following: when the
>>rendering is starting, QgsMapLayer::createMapRenderer() is called
>>(still in the main thread) and it will return a new instance of
>>QgsMapLayerRenderer. The instance has to store any data of the layer
>>that are required by the rendering routine. Then, in a worker thread,
>>its render() method is called that will do the actual rendering. Like
>>this, any intermediate changes to the state of the layer (or its
>>provider) will not affect the rendering.
>>- concept of feature sources. For rendering of vectors in worker
>>thread, we need to make sure that any data used by feature iterators
>>stay unchanged. For example, if the user changes the provider's query
>>or encoding, we are in a trouble. Feature sources abstract providers:
>>they represent anything that can return QgsFeatureIterator (after
>>being given QgsFeatureRequest). A vector data provider is able to
>>return an implementation of a feature source which is a snapshot of
>>information (stored within the provider class) required to iterate
>>over features. For example, in OGR provider, that is layer's file
>>name, encoding, subset string etc, in PostGIS it is connection
>>information, primary key column, geometry column and other stuff.
>>Feature iterators of vector data providers have been updated to deal
>>with provider feature source instead of provider itself. Even if the
>>provider is deleted while we are iterating over its data in a
>>different thread, everything is still working smoothly because the
>>iterator's source is independent from the provider. Vector layer map
>>renderer class therefore creates vector layer's feature source, which
>>in turn creates a copy of layer's edit buffer and creates a provider
>>feature source. From that point, QgsVectorLayer class is not used
>>anywhere during the rendering of the layer. Remember that most of the
>>copied stuff are either small bits of data or classes supporting
>>copy-on-write technique, so there should not be any noticeable
>>performance hit resulting from the copying.
>>- rendering of raster layers is handled by first cloning their raster
>>pipe and then using the cloned raster pipe for rendering. Any changes
>>to the raster layer state will not affect the rendering in progress.
>>- update to scale factors. I have always found the "scale" and "raster
>>scale" factors from QgsRenderContext confusing and did not properly
>>understand their real meaning because in various contexts (composer vs
>>canvas) they had different meaning and value. There were also various
>>rendering bugs due to wrong or no usage of these factors. By scaling
>>of painter before rendering and setup of correct DPI, these factors
>>are now always equal to one. In the future, we will be able to remove
>>them altogether.
>>- composer has also been updated to use QgsMapSettings + QgsMapRendererJob.
>>- labeling engine has seen some changes: it is created when starting
>>rendering and deleted when rendering has finished. The final labeling
>>is stored in a new QgsLabelingResults class, which is then propagated
>>(up to map canvas). Also, it is possible to cancel computation and
>>drawing of labeling.
>>- the API remained the same with only tiny changes within the
>>internals of labeling, diagrams, renderers and symbols, mainly due to
>>the fact that QgsVectorLayer is not used in the vector rendering
>>pipeline anymore. Callers should not see any difference (unless using
>>some exotic calls).
>>Finally, some further thoughts/questions:
>>- rasters - currently we do not have API to cancel requests for raster
>>blocks. This means that currently we need to wait until the raster
>>block is fully read even when we cancel the rendering job. GDAL has
>>some support for asynchronous requests - anyone has some experience
>>with it?
>>- rasters (again) - there are no intermediate updates of the raster
>>layer when rendering. What that means is that until the raster layer
>>is fully rendered, the preview is completely blank. There is a way to
>>constrain the raster block requests to smaller tiles, but what would
>>be the performance consequences? I am not that familiar with the way
>>how raster drivers are implemented in GDAL... anyone to bring some
>>- PostGIS - I had to disable reuse of connections to servers because
>>it is not safe use one connection from multiple threads. If reusing of
>>the connections is an important optimization, we will probably need to
>>implement some kind of connection pool from which connections would be
>>taken and then returned.
>>Okay, I think that's it. Sorry for the long mail to all the people who
>>read it until the end. Please give it a try - I will welcome any
>>feedback from the testing.
>>Qgis-developer mailing list
>>Qgis-developer at lists.osgeo.org
>Qgis-developer mailing list
>Qgis-developer at lists.osgeo.org
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osgeo.org/pipermail/qgis-developer/attachments/20131212/9e6e5161/attachment-0001.html>

More information about the Qgis-developer mailing list