Canvas incredibly slow. Why?

 

Hi, 

when I see samples of how people use the CCanvas class, Im always surprised. When I use the class, I have to work asynchronously all the time since it is simply way too slow and I don`t know why.

This little piece of code, which does not more than painting 3 times a text, needs between 0.3 and 1ms. The code is part of a class which uses CCanvas to display labels instead of label chart-objects. As its obvious in the code, no further functions or something which could waste time are called, its only the functions of CCanvas. The dimensions are also not big at all, its just 400x40 pixels, the fontsize is 8:
 
1. Erase
2. FontSet
3. Textout
4. TextOut
5. TextOut

And that takes up to a whole millisecond even without flashing the bitmap back to the chart? That bad performance makes it actually completely useless. The time needed should not be more than a few microseconds, not 1000 or even more. Any ideas welcome.

//+------------------------------------------------------------------+
//| Profiling                                                        |
//+------------------------------------------------------------------+
#ifdef PROFILER_ON
   #define PROFILE_TIME_START int _t_profile=(int)::GetMicrosecondCount();
   #define PROFILE_TIME_SHOW_EXPLICIT(title) { int t2=(int)::GetMicrosecondCount(); int tm=(t2-_t_profile)/1000; if (tm>0) Print(title,": ",tm," ms"); else Print(title,": ",t2-_t_profile," µs"); _t_profile=t2; }
   #define PROFILE_TIME_SHOW(title) { int t2=(int)::GetMicrosecondCount(); int tm=(t2-_t_profile)/1000; if (tm>0) Print(title,": ",tm," ms"); _t_profile=t2; }
#else
   #define PROFILE_TIME_START
   #define PROFILE_TIME_SHOW(title)
   #define PROFILE_TIME_SHOW_EXPLICIT(title)
#endif
 
//+------------------------------------------------------------------+
   //| Redraw                                                           |
   //+------------------------------------------------------------------+      
   protected: virtual bool OnRedraw()
      {
      PROFILE_TIME_START
      //--- Background
      COLOR_RGBA clr_bg=COLOR_TO_ARGB(__Design.Color_Bg_Graph);
      m_Canvas.Erase(clr_bg);
      int height=m_rect.Height();
      int width=m_rect.Width();   

      m_Canvas.FontSet(__Design.FontName, m_fontsize+m_fontsizerel,m_fontflags);
      int th=m_Canvas.TextHeight(null);
      int yoff=(height-th)/2;

      //--- Caption
      COLOR_RGBA clr = COLOR_TO_ARGB(__Design.Color_Caption);
      m_Canvas.TextOut(0,yoff,m_caption,clr);

      // Values (max two)     
      
      for (int i=0;i<m_cntvalues;i++)
         {
         if (m_colored)
            {
            if (m_value[i]<0)
               clr = COLOR_TO_ARGB(__Design.Color_Caption_Negative);
            else if (m_value[i]>0)
               clr = COLOR_TO_ARGB(__Design.Color_Caption_Positive);
            else 
               clr = COLOR_TO_ARGB(__Design.Color_Caption);
            }
         else 
            clr = COLOR_TO_ARGB(__Design.Color_Caption);
         m_Canvas.TextOut(m_x[i],yoff,m_text[i],clr);
         }
      PROFILE_TIME_SHOW_EXPLICIT("OnRedraw");   
      return true;
      }
 
have you tried to precalculate all needed colors ?
const COLOR_RGBA clr1 = COLOR_TO_ARGB(__Design.Color_Caption);

and you could also try to restructure your for loop to reduce conditional statements inside the loop.

good luck
 
Soewono Effendi #:
have you tried to precalculate all needed colors ?

and you could also try to restructure your for loop to reduce conditional statements inside the loop.

good luck

Thats bitwise stuff and should not have any recognizable effect. And there is only one condition which I could remove from the loop but for that the loop has to be doubled. As u see, I already set the string (m_text) upfront to avoid any kind of conversion and space allocation within the loop, coz that is something which takes processing time. The OnRedraw() occurs only then when something has changed. Preprocessing the colors would just take the same time at another place in the code. 

I suspect the antialiased font rendering at 192 DPI which is responsible here. I will try to do the rendering with Windows GDI instead, but not sure if that will result in any kind of improvement, since I assume, MQL does the same anyway internally. The whole concept with the array is actually just the same like double buffering with GDI.  

 
To get a litte feeling for the time-needed: The class takes the data from a string-array with 64 elements. I was concerned about the time to update that string array, since it does conversion from double, int and datetime into that string array and that that would be the problem. But, surprise, it takes between 0,02 and 0,05 msec to create, convert all data and add it to the array. I never assumed that the simple OnRedraw with almost no code would need 20-50 times longer. That makes no sense to me. 
 
Doerk Hilger #:
To get a litte feeling for the time-needed: The class takes the data from a string-array with 64 elements. I was concerned about the time to update that string array, since it does conversion from double, int and datetime into that string array and that that would be the problem. But, surprise, it takes between 0,02 and 0,05 msec to create, convert all data and add it to the array. I never assumed that the simple OnRedraw with almost no code would need 20-50 times longer. That makes no sense to me. 

You need measure the time for draw all elements in your canvas. If you are working with just few text it may not exceed 10-15 ms. If it takes more than that and your texts are static, maybe just changing the color (you can easily colorize pixels accordingly) you can have a class to store the pixels of each text. So when you need redraw the same text you just need copy the pixels to your canvas. Another option is have a class that store the pixels of each character (if you are working with multiple fonts or with different font styles may may need an instance of the same class for different fonts and/or styles) and when need draw a text you copy the pixels of each character acording to the text.

 
this code use static class, you might want give it a try.
Hopefully it is fast enough for your purpose.

 
Doerk Hilger:

Hi, 

when I see samples of how people use the CCanvas class, Im always surprised. When I use the class, I have to work asynchronously all the time since it is simply way too slow and I don`t know why.

This little piece of code, which does not more than painting 3 times a text, needs between 0.3 and 1ms. The code is part of a class which uses CCanvas to display labels instead of label chart-objects. As its obvious in the code, no further functions or something which could waste time are called, its only the functions of CCanvas. The dimensions are also not big at all, its just 400x40 pixels, the fontsize is 8:
 
1. Erase
2. FontSet
3. Textout
4. TextOut
5. TextOut

And that takes up to a whole millisecond even without flashing the bitmap back to the chart? That bad performance makes it actually completely useless. The time needed should not be more than a few microseconds, not 1000 or even more. Any ideas welcome.

you can also use the TextOut command which draws straight into a resource 

TextOut - Object Functions - MQL5 Reference - Reference on algorithmic/automated trading language for MetaTrader 5

Documentation on MQL5: Object Functions / TextOut
Documentation on MQL5: Object Functions / TextOut
  • www.mql5.com
The function displays a text in a custom array (buffer) and returns the result of that operation. The array is designed to create the graphical...
 
Samuel Manoel De Souza #:

You need measure the time for draw all elements in your canvas. If you are working with just few text it may not exceed 10-15 ms. If it takes more than that and your texts are static, maybe just changing the color (you can easily colorize pixels accordingly) you can have a class to store the pixels of each text. So when you need redraw the same text you just need copy the pixels to your canvas. Another option is have a class that store the pixels of each character (if you are working with multiple fonts or with different font styles may may need an instance of the same class for different fonts and/or styles) and when need draw a text you copy the pixels of each character acording to the text.

Thx. No, the text is not static, it changes with every tick and the OnRedraw is restricted to update not more than once a second. The colors changes accordingly to the value frequently. Storing the pixels is no option. This single object is one of a collection of <>50 objects - in every chart. Extreme scenarios show up to 100 charts, 50 is almost average. Any kind of too much buffering would result in a huge allocation of memory. And, as you can imagine, this is not the only case where I am using graphical stuff. Furthermore the charts are dynamic in their scale, which affects the font. 

I understand your suggestion and appreciate it, but the workload for this one single panel would be way too extreme. The easier way out is doing all the CCanvas stuff in C# instead and set any output to a different thread and send back the bitmap-buffers via a pipe, since it seems to be really the case that fonts take that long. I should not block the main thread with just that. 

I hoped I overlooked something, but probably not :/ 

 
Lorentzos Roussos #:

you can also use the TextOut command which draws straight into a resource 

TextOut - Object Functions - MQL5 Reference - Reference on algorithmic/automated trading language for MetaTrader 5

That is exactly what CCanvas::TextOut does. 

 
Doerk Hilger:

Hi, 

when I see samples of how people use the CCanvas class, Im always surprised. When I use the class, I have to work asynchronously all the time since it is simply way too slow and I don`t know why.

This little piece of code, which does not more than painting 3 times a text, needs between 0.3 and 1ms. The code is part of a class which uses CCanvas to display labels instead of label chart-objects. As its obvious in the code, no further functions or something which could waste time are called, its only the functions of CCanvas. The dimensions are also not big at all, its just 400x40 pixels, the fontsize is 8:
 
1. Erase
2. FontSet
3. Textout
4. TextOut
5. TextOut

And that takes up to a whole millisecond even without flashing the bitmap back to the chart? That bad performance makes it actually completely useless. The time needed should not be more than a few microseconds, not 1000 or even more. Any ideas welcome.

I made a check on my own code, it matches more or less your results. The 'culprit' is TextOut. Though I have 16 calls in my test.

Though, I don't see why your think " That bad performance makes it actually completely useless ", it's still perfectly usable for a GUI.
 
Doerk Hilger:

Hi, 

when I see samples of how people use the CCanvas class, Im always surprised. When I use the class, I have to work asynchronously all the time since it is simply way too slow and I don`t know why.

This little piece of code, which does not more than painting 3 times a text, needs between 0.3 and 1ms. The code is part of a class which uses CCanvas to display labels instead of label chart-objects. As its obvious in the code, no further functions or something which could waste time are called, its only the functions of CCanvas. The dimensions are also not big at all, its just 400x40 pixels, the fontsize is 8:
 
1. Erase
2. FontSet
3. Textout
4. TextOut
5. TextOut

And that takes up to a whole millisecond even without flashing the bitmap back to the chart? That bad performance makes it actually completely useless. The time needed should not be more than a few microseconds, not 1000 or even more. Any ideas welcome.


I think there is a different reason. - Why do you need to recreate the bitmap so often??