Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Qt Text Rendering is extremely slow... any way to speed up?



  • I am converting a program from C# on the .NET platform (using the normal System.Drawing methods) to Qt. As soon as I added text to the drawing process for my control (widget), the time it took to render the control went up from a smooth scrolling, to taking several seconds to draw a single frame before the scroll continues. The amount of time the text is taking is insane.

    I have researched this issue on the internet and while I realize there may be some difference due to the exact way the text is drawn. the difference pointed out in this post of a test is still staggering: http://www.amigans.net/modules/xforum/viewtopic.php?forum=38&topic_id=5184&order=

    Is there any way to speed up the process? The only difference between the old and the new way of doing things (from my end) is that I am now using a callback to get the needed data rather than a large switch case statement. (The text that is actually drawn depends on the text "mode" because there are several points of data that need to be available).

    Here is my widget's paint method:

    void LabMapWidget::paintEvent(QPaintEvent* event)
    {
    	// final layer
    	QPainter widgetPainter(this);
    	QSize size = mFormat->mapSize(mMap->width(), mMap->height());
    	this->setMinimumSize(size);
    	this->resize(size);
    	
    	GlobalData::setPainter(&widgetPainter);
    	mMainMode->renderData();
    
    	// this line draws the text when appropriate
    	if ((GlobalData::scale() >= MIN_SPRITE_ZOOM) && mDataMode)
    		mDataMode->renderData();
    	GlobalData::clearPainter();
    }
    

    The "mDataMode" is a class derived from my data mode interface. Here is an example method that actually draws the text (since there are several text/data modes)

    #define SURFACE GlobalData::painter()
    ...
    void Coordinates::renderData()
    {
    	SURFACE->setFont(QFont("Microsoft Sans Serif", 7, QFont::Bold));
    	for (int x = 0; x < MAP()->width(); ++x)
    	{
    		for (int y = 0; y < MAP()->height(); ++y)
    		{
    			QString string = QString::number(x) + ",\n" + QString::number(y);
    			SURFACE->setPen(Black);
    			SURFACE->drawText(LabMapWidget::cellFormat()->getSpriteRect(x, y), Qt::AlignCenter, string);
    		}
    	}
    }
    

  • Lifetime Qt Champion

    Hi,

    Small details but do you really need to set the pen at each iteration ?

    Why not retrieve the with and height of MAP at the beginning of the function before the loop ?

    What size is that MAP object ?



    1. Each cell is drawn as an individual unit as a square or hex (depending on the map type). Eventually the plan is to adapt the pen to the background as some map cells are dark colored and some are light colored... so yes.

    2. Sure, I can do that. But it isn't what is causing the delay here. The method for drawing the tiles themselves use the same set of changes for each cell... so that isn't what is causing the time here.

    3. The "size" is the map size in cells. The widget itself is a rendering of all these cells. Maps can be of any size but currently they do not go above somewhere around 200 x 120.


  • Lifetime Qt Champion

    I'm was looking to eliminate the unneeded time consuming stuff.

    How many maps can there be ?

    I'm also wondering why you resize the widget in the paint event. Sounds like you might be losing time there depending on the refresh speed.


  • Qt Champions 2017

    @primem0ver

    To add to @SGaist, resizing will cause implicit update() to be issued, so you'll get additional paint event(s). Also, to be absolutely frank, that code you've posted looks terrible. It's all coupled and tangled up, and that's leaving aside the macro(s) and the global(s) ...



  • All your points are non issues. They were all being done before the addition of the text. Without the text, the scrolling runs smoothly and it doesn't take much time to render. But to answer your questions anyway...

    1. There is only one map loaded at a time.

    2. Resizing the control is necessary because it is on a scrollable surface. If the current zoom is low enough that the map is entirely visible, no scrolling should take place. Otherwise, the control must be resized to fit the entire map so that the scroll bar is sized appropriately. EDIT: I suppose I could just disable scrolling if the maps size is too small... but I would prefer it not be there; and as I said earlier... resizing is not causing an issue.

    3. Please explain to me how it is "terrible." I really hope this isn't a comment on how the code looks... because 1) it is entirely irrelevant to the problem 2) that is your opinion, and to be frank... 3) I have been told that my coding is generally much easier to read that other peoples... so to each his own. If you mean that it looks like a bad algorithm, then please justify/explain your statement.



  • On which os are you working ?

    On OS X you can start XCode Developer Tool called Instruments (Time Profiler).
    There you can exactly see whats is responsible for the slow execution time.

    On the first look i'm also @kshegunov opinion. Resizing the widget in the paint event should never be done !!

    As he mentioned it causes implicit an update of your widget which then results in another paint event.
    Did you get that ? In your case there are 3 times more paint events than normal (which of course makes the execution slower)

    So try it out...without the setMinimumSize and resize calls it will be faster !


  • Qt Champions 2017

    @primem0ver

    Resizing the control is necessary because it is on a scrollable surface.

    It may very well be necessary, but it shouldn't happen when drawing. Issue the resize from outside (i.e. the containing object) and in the paintEvent override only do the painting.

    Please explain to me how it is "terrible".

    Nothing to do with the looks. Your code style is good and clearly understandable, your design choice is what I was referring to. From the short snippet you provided it appears you've jumped through all kinds of hoops to couple you objects up. For one, they all use something called GlobalData which is suspicious by itself. What's the reason for it? Why are Coordinates painting, it doesn't make sense? Are Coordinates coordinates, or are they CoordinatesRenderer, or something else? And why Coordinates have any knowledge that there's such a thing as LabMapWidget?! Same reasoning for modes, a mode shouldn't care at all that there's such a thing as painting, much less do the painting ...



  • @kshegunov
    LOL. Ok. Now I get it. The reason for your objection is because you don't understand the nature of the application. FYI... this is NOT the big application I am talking about in my post in the general lounge about the commercial cost. This is a second major application that I decided to convert to C++ because that is the target language anyway.

    The purpose of this application is to come up with a very complex algorithm for creating realistic maps in a gaming environment. I am a "civfanatic" and a map "fetishist" (I have been obsessed with maps since I was bout 6-7 years old; I became the navigator for family trips are around 10.). I also was an Earth science teacher for 5 years. Here are the list of constraints I put on myself for this program after working with my first version in pure C#.

    1. This version of application must be able to deal with more than one type of tile because of the up and coming release of Civ 6 (as well as Civ 5). Currently both squares and hexes are supported.. eventually I will hopefully introduce an ISEA (Isocohedral Equal Area) grid on a true spherical globe (made of a combination of hexes and pentagrams. A soccer ball is an example of a kind of ISEA grid. The "Cell Format" is the interface that deals with this particular aspect of the program.

    2. "Modes" DO take care of the painting if that is the whole point of the "mode." Since much of what I am doing is based on intuition and trial and error in addition to research and my training, I need to be able to view a long list of points of data and be able to switch between them instantly; even superimpose them. (A few dozen already exist... think elevation, temperature, shore distance, precipitation, etc...). Superimposing the text itself is unlikely considering the time it would take... but I have already done it with color.

    I decided to consolidate code by using a "data" class which is what I am referring to by mode. The "coordinates" data mode is the mod that is chosen when I need to see the plot coordinates on the screen. Before, adding new drawing/data modes was an annoyingly lengthy process that involved changing several sections of code (switch case statements etc...) I have simplified the process tremendously. All data points now inherit a template class interface that handles the retrieval of data (using a typed delegate/callback... hence the need for a template) and "rendering" it on the map. The class is fairly simple and only involves creating a constructor, a render method (that was originally stuck in at least one case in switch case statements), and adding a single line for creating the single class instance. "Main mode' refers to the primary data that is being colored. The "data mode" refers to the data being printed (or illustrated in the case of wind/current direction). Eventually I will add at least one more "secondary" mode that will be color blended with the primary mode.

    1. Global Data is a class of static/global data that needs to be shared with all classes.

    Let me show you PART of an image exported by the previous version of the application to give you an idea of the nature of the program. Here is a strip from a world map image exported by the original program. The current "data mode' is global wind direction (with the size of the arrow based on relative wind speed). The current "main mode" (coloration) is plot type (blue = ocean, light blue = coast, green = plains, yellow = hills, orange = mountains). No mixing here so there is no secondary mode selected. Perhaps I should use another word besides "mode"... but hopefully you get the idea.

    Just in case you are curious about details regarding the project, there are several threads I started on Civfanatics that revolve around this engine. The main one is here.


  • Lifetime Qt Champion

    Interesting background ! That shed some lights for other suggestions. You should then maybe avoid doing it with widgets and go for the Graphics View framework. It looks like it should be more suited for your needs.

    On a side note, maybe the Marble project could be of interest.



  • @SGaist
    I remember you mentioning this or something similar when I was asking questions about combining views (i.e. "layers"; now that you have an understanding of the goal, I can be specific... I was talking about combining the primary/main view with the secondary and possibly more views).

    I will go ahead and give the framework a shot to see if text rendering is faster there.



  • @kshegunov and @euchkatzl
    Yeah you're right about the resizing... bad idea. I am surprised it doesn't do a recursive call then. I don't think it will change the non-recursive number of times the paint method is called for a zoom in/out event but it was simply a bad move.


  • Qt Champions 2017

    @primem0ver said:

    I am surprised it doesn't do a recursive call then.

    Qt tries to be, and in many cases succeeds in being, smart about events (input and window events especially) and their dispatching. The paint event is not issued immediately but is put in the event queue and is also sometimes subject to compression.



  • @primem0ver said:

    @SGaist
    I remember you mentioning this or something similar when I was asking questions about combining views (i.e. "layers"; now that you have an understanding of the goal, I can be specific... I was talking about combining the primary/main view with the secondary and possibly more views).

    I will go ahead and give the framework a shot to see if text rendering is faster there.

    Don't be too disappointed, though. Rendering of glyphs is a complex task, and neither Widgets nor GraphicsView makes use of the GPU or even multiple threads.

    However, GraphicsView makes it pretty easy to cache painted items: Unless you change the text or looks of an item, there's no need to re-render the glyphs every time.



  • @Asperamanca so basically you're saying it won't change the text rendering speed? That would be rather annoying after spending a day and a half of changing my code to work with the graphics view. (Close to being done)

    Another question that I have about the QGraphicsView. I need to change the way it detects zoom events. If the mouse is scrolled normally I want to scroll the picture down. If the shift is held down while the mouse is scrolled, THEN I want it to zoom.



  • @SGaist and everyone else interested...

    The QGraphicsView does speed up drawing text enough that the control is usable in that regard. However, there are three issues with it (in order of importance).

    1. Scrolling horizontally causes white lines to appear, Not sure why. They appear to be made of cached text from the "text" layer of objects that are being drawn on top of the picture. They clear when I scroll vertically but unless I do that it makes the view unusable.
    2. It takes a substantial amount of time to create the view.
    3. It takes a horrendous amount of time to destruct the view.

    I believe the time issue stems from the fact that there are so many objects to create and destroy (rather than simply drawing them manually).


Log in to reply