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