Drawing performance

Hi,

I'm working on data fields that draw a graph of historic values in the background.
It basically works by calling dc.drawLine for every pixel in the data field width and draw a
vertical line, representing the data value. Quite simple of course.

However, I wonder about performance. I measured the absolute time for the drawing function
with System.getTimer() from entry to exit.

On my Edge 820 it took 250ms for drawing 200 lines. Quite a lot for a routine that runs every second. Of course, from
inside the CIQ virtual machine, we can't know if other tasks are executed in that time, or if that
function really takes so long.

I tried to draw the 200 line graph in a BufferedBitmap and draw it to the data field DC in one call.
Suprisingly, this causes no speed up!

Can anybody explain the CIQ internals? Is there a faster method, expcept from drawing less lines ;)
Are there better profiling methods?
How do the other data fields with graphs draw their lines?

Thanks,
flo
  • Did you mean to say it draws a "Horizontal line, representing the data value"? I assume the draws a line for every pixel in the data field width are vertical lines. Regardless, 250ms (or 1/4 second) isn't unreasonable for a graphics intensive field. Partly depends on what else might be going on the screen. Is this just one field out of 10 on the screen, or is it taking up the entire screen.

    What other calculations are you doing? Are you only doing the drawing, or are you do some floating point operations to compute the line end points? Caching that could be more helpful. Also, are you setting colors or anything else for every line?

    Lastly, is battery being noticeably impacted?
  • Did you mean to say it draws a "Horizontal line, representing the data value"? I assume the draws a line for every pixel in the data field width are vertical lines.


    A vertical line for each pixel in the width of the data field. Such that the result is a graph with filled area and each pixel in the width represents one unit of time.

    Regardless, 250ms (or 1/4 second) isn't unreasonable for a graphics intensive field. Partly depends on what else might be going on the screen. Is this just one field out of 10 on the screen, or is it taking up the entire screen.
    What other calculations are you doing? Are you only doing the drawing, or are you do some floating point operations to compute the line end points? Caching that could be more helpful. Also, are you setting colors or anything else for every line?
    Lastly, is battery being noticeably impacted?


    The program I used for the benchmark tests does little more than plotting a constant value and drawing a text value.
    The timing values are only for the graph drawing function. Anyways, I found that
    But I conducted another experiment: I put five such fields on a screen. All are sharing one instance of the CIQ program, but the onLayout and onUpdate functions are called five times per second. Each field spends 100 to 200 ms in the drawGraph function (depending on the actual field width). The times sum up to significantly more than a second. So this proofs that the measured clock time contains time of other tasks, the execution is not continuous and task switching occurs (or we have a multicore CPU, hm..). Thus, we can't say how much time the drawing really takes.
    I can't estimate battery impact yet. But that's the main reason why I bother and try to save time and energy.
  • Could you post the code for just loop you're using to draw the lines?
  • Former Member
    Former Member over 6 years ago
    On Edges in particular, I do believe your execution time will have a noticeable impact on battery life.

    As you mentioned, it is impossible to know if the thread running the CIQ virtual machine has been swapped out during your render time, causing an error in your attempted measurement using System.getTimer(), but if you collect this timer over a large number of draws and consider the minimum value, it should at least be useful for comparing to alternate rendering methods.

    Drawing individual vertical lines is probably not exceptionally efficient especially since display memory is almost always stored in rows.

    Here are some things you might try to speed up the render of your graph:

    1. Collect your line lengths as an array of points, and use fillPolygon to render a chunk of the lines in one draw command. This will allow the graphics system to use some horizontal fills instead of vertical ones. This option probably gets a bit complicated because the maximum polygon points in CIQ is 64, so you would have to break your graph up into multiple polys.

    2. Render into a buffered bitmap as you collect this data every second. Draw only one line each cycle, and copy the bitmap into the main surface. When the width is filled copy the buffered bitmap to itself shifted over one pixel, and then draw the line for the new second.

    You may be able to reduce the number of points being rendered without significantly reducing the graph quality by using polygon fills instead of single lines also.
  • Could you post the code for just loop you're using to draw the lines?


    function drawGraph(dc, fgc) {
    var start = System.getTimer();
    var h = dc.getHeight();
    var w = dc.getWidth();
    var yg;
    var y;
    var x;
    var histoffs = _histlen - w ;

    var colors;
    if (fgc == Graphics.COLOR_WHITE) {
    colors = _colors_dark;
    } else {
    colors = _colors_bright;
    }
    var bgc = colors[0];
    // var bm = new Graphics.BufferedBitmap({:width => w, :height => h, :palette => colors});
    dc.setColor(bgc, bgc);
    dc.clear();
    var dcbm = dc; //bm.getDc();
    dcbm.setPenWidth(1);

    _lcol = bgc;
    var lcolnext;

    for (var xg = 0; xg < w; xg+= 1) {
    if (_scale_x) { // scale to fit field size
    y = interp(xg);
    } else if (histoffs + xg < 0 ) { // pad with empty space if history is too short
    y = 0;
    } else { // one pixel per history record
    y = _hist.at(histoffs + xg); // skip to history start
    }

    yg = scaley(y);
    if (y <= 0) { // below thresh
    continue;
    }
    if (y < _y_thresh) {
    lcolnext = colors[1];
    } else if (y < _y_max - _y_min) {
    lcolnext = colors[2];
    } else {
    lcolnext = colors[3];
    yg = h;
    }
    setColorCached(dcbm, lcolnext, bgc);
    dcbm.drawLine(xg, h, xg, h-yg);
    }
    var clock = System.getTimer() - start;
    // dc.drawBitmap(0,0, bm);
    // takes about 250ms on the Edge 820!
    // value = clock;
    }
    function setColorCached(dc, fc, bc) {
    if (fc == _lcol) {
    return;
    }
    dc.setColor(fc, bc);
    _lcol = fc;
    }
    }



    Commented out is the offscreen BufferedBitmap mode.
    Of course there's some scaling, but basically it is about drawing lines, near the end.
    I found that calling setColor also causes measureable exec. time, thus the color is memorized and only set if needed.
  • [..]

    Here are some things you might try to speed up the render of your graph:

    1. Collect your line lengths as an array of points, and use fillPolygon to render a chunk of the lines in one draw command. This will allow the graphics system to use some horizontal fills instead of vertical ones. This option probably gets a bit complicated because the maximum polygon points in CIQ is 64, so you would have to break your graph up into multiple polys.

    2. Render into a buffered bitmap as you collect this data every second. Draw only one line each cycle, and copy the bitmap into the main surface. When the width is filled copy the buffered bitmap to itself shifted over one pixel, and then draw the line for the new second.

    You may be able to reduce the number of points being rendered without significantly reducing the graph quality by using polygon fills instead of single lines also.


    I can try polygons, but I think, this will cause some complexity to break the graph up into equal-color objects. And I think point 2 will have much more impact.

    I was looking for some kind of 'blit' operation to shift the graph to the left on each redraw since the start of this, but couldn't find one. Your suggestion of drawing the bitmap to itself with a
    negative x coordinate is great. Thanks for that idea. This would save a lot of drawing the same lines. However it causes some practical challenges:

    - The datafield could be layouted and updated on differenent geometries at a time using the same instance. So the datafield object will have to keep one offscreen bitmap per geometry, or it will not work if more than one view of the datafield is configured.
    - onUpdate must catch up correctly if the field was invisible and was not drawn for a while.

    This shouldn't be that hard. I expect a huge execution time gain from drawing only what is missing.
  • Former Member
    Former Member over 6 years ago
    If you are rendering to a buffered bitmap, you should be able to render to this in your compute function, and avoid the "catching up" when you are off screen. If the render is fast enough this wouldn't really be necessary since it wouldn't impact power (render now vs later). However, this would actually take more power if it was off screen long enough to invalidate the entire buffer.

    Tracking multiple geometries is probably going to be tricky. It is probably impossible to tell the difference between the user changing the size of a configured field and switching to another page with a different configuration size. In the first case, you'd want to throw away the old buffer and in the second, track two separate ones. If you update the buffers only when visible, you could free them as soon as they don't have any relevant data anymore. This would allow your application to automatically purge buffers that may no longer be needed, and also provide no benefit if the layout is revisited. You will also need to keep at least a rough estimation of how much memory the buffers are using and purge them if they are going to cause your application to run out of memory, unless there is enough available for your use case that this is not a risk.
  • Thanks for your ideas!
    I chose to put the drawing into onUpdate only, because onCompute knows nothing about DC and geometry. The "catching up" is no problem.
    First running tests shows a runtime of 20ms on the Edge 820 instead of 250ms for a full redraw. Looks good!
  • Here we go! The implementation keeps one single background bitmap, bound to field height. The performance gain vanishes, if one has two views of the field with different geometry on one screen. But that doesn't make much sense anyway. I'm quite happy with that solution.

    https://apps.garmin.com/en-US/apps/27985ac2-1504-4259-b2de-cb89d299d68c ???????

    https://apps.garmin.com/en-US/apps/ee19c377-c1a8-4595-87f1-039c28b1ac00