number of objects drawn to dc

Martin Landa wrote:

I need to draw to DC huge number (> 1e6) of objects

That's a LOT of objects -- can you really see them all at the same time?
You might want to do a bounding box check or something first. You might
also want to see if you can change what you draw depending on scale.

Also, is all this stuff changing? you could perhaps buffer some of it.

(GIS application,
tool for editing vector maps).

Cool -- I"d love to see what you come up with. Also, be sure to check
out FloatCanvas (wx.lib.floatcanvas, or newer version at:

http://morticia.cs.dal.ca/FloatCanvas/

It takes care of a lot of this for you. I'm also hoping to add more
GISey stuff with it -- I'd love help!

There is simplified OnPaint fn.

        dc = wx.BufferedPaintDC(self, self._Buffer)
        import time
        start = time.clock()
        nlines = 1000000
        for i in range(nlines):
            dc.DrawLine(0, 0, 100, 100)
        stop = time.clock()
        print >> sys.stderr, "time=%f" % (stop-start)

Maybe this is just a test, but the point of a bufferedDC is that you can
just blit the buffer in the OnPaint -- you only want to draw when
something changes (again, see FloatCanvas)

For example for 1e6 objects time needed for drawing is 3.4s on my
machine. But the applications is frozen for 10s and more.

The time for the Draw calls to return isn't necessarily how long it
really takes to draw. At least with GTK, X is asynchronous, so it's hard
to time the drawing.

Also, calling DrawLine a bunch of times is going to be slower than
DrawLines() or DrawLineList()

I hope some of this helps.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

Hi Chris,

thanks a lot for pointing me out to floatcanvas. I am quite newbie in
wxPython as you can see;-)

Martin Landa wrote:
> I need to draw to DC huge number (> 1e6) of objects

That's a LOT of objects -- can you really see them all at the same time?
You might want to do a bounding box check or something first. You might
also want to see if you can change what you draw depending on scale.

Also, is all this stuff changing? you could perhaps buffer some of it.

>(GIS application,
> tool for editing vector maps).

Cool -- I"d love to see what you come up with. Also, be sure to check
out FloatCanvas (wx.lib.floatcanvas, or newer version at:

http://morticia.cs.dal.ca/FloatCanvas/

It takes care of a lot of this for you. I'm also hoping to add more
GISey stuff with it -- I'd love help!

There is simplified OnPaint fn.
> dc = wx.BufferedPaintDC(self, self._Buffer)
> import time
> start = time.clock()
> nlines = 1000000
> for i in range(nlines):
> dc.DrawLine(0, 0, 100, 100)
> stop = time.clock()
> print >> sys.stderr, "time=%f" % (stop-start)

Maybe this is just a test, but the point of a bufferedDC is that you can
just blit the buffer in the OnPaint -- you only want to draw when
something changes (again, see FloatCanvas)

Well, this was not simplified OnPaint fn, anyway not good example...

Currently I am using one pseudoDC for drawing background map,
decorations, etc. The second pseudoDC for drawing vector map which is
edited. This pseudoDC object is input for C++ driver (using Swig
interface for Python). The C++ driver (library) is needed because
vector objects of vector map need to be read using C API of the given
GIS application.

So OnPaint():

       dc = wx.BufferedPaintDC(self, self._Buffer)

        # we need to clear the dc BEFORE calling PrepareDC
        bg = wx.Brush(self.GetBackgroundColour())
        dc.SetBackground(bg)
        dc.Clear()

        # use PrepareDC to set position correctly
        self.PrepareDC(dc)

        # create a clipping rect from our position and size
        # and update region
        rgn = self.GetUpdateRegion().GetBox()
        dc.SetClippingRect(rgn)
        # draw to the dc using the calculated clipping rect
        self.pdc.DrawToDCClipped(dc, rgn)

        # draw vector map layer
        if self.pdcVector:
            self.pdcVector.DrawToDCClipped(dc, rgn)

Vector objects are drawn to PseudoDC using C++ driver
...
self.driver.DrawMap(self.pdcVector)
...

There is DrawLine method, each rendered line need to have unique id.

void DrawLine(line) {
...
      dc->SetId(id++);
      dc->DrawLines(pointsScreen->GetCount(), points);
...
}

There something very strange at least for me;-) If I comment out
'dc->SetId(startId);', speed of rendering is quite OK.

E.g. for vector map with 1e4 lines/points (each line has two nodes
which are also drawn), it takes 1.2s for drawing and 0.6s for each
redrawing.

If set up id by 'dc->SetId(startId++);' drawing takes 6s and more!!!
The response is significantly worse. I am just wondering why is
SetId() so expensive.

Thanks again for any hits!

Martin

···

--
Martin Landa <landa.martin@gmail.com> * http://gama.fsv.cvut.cz/~landa *

Hi all,

trying to simplify my question..., 'stupid' example below

        import time
        start = time.clock()
        #nlines = self.__display.DrawMap(True) # force
        nlines = 1e4
        for line in range(nlines):
            self.mapWindow.pdcVector.SetId(line) # <--- HERE
            self.mapWindow.pdcVector.DrawLine(0,0,100,100)
        stop = time.clock()
        Debug.msg(3, "CDisplayDriver.DrawMap(): nlines=%d, sec=%f" % \
                      (nlines, stop-start))

On my machine it takes 3s (stop-start), if I comment out SetId() fn,
time is 0.04s. I am wondering why is pseudoDC.SetId() soooo
expensive...

Or I am just doing something wrong?

Thanks! Martin

···

2007/9/29, Martin Landa <landa.martin@gmail.com>:

Hi Chris,

thanks a lot for pointing me out to floatcanvas. I am quite newbie in
wxPython as you can see;-)

> Martin Landa wrote:
> > I need to draw to DC huge number (> 1e6) of objects
>
> That's a LOT of objects -- can you really see them all at the same time?
> You might want to do a bounding box check or something first. You might
> also want to see if you can change what you draw depending on scale.
>
> Also, is all this stuff changing? you could perhaps buffer some of it.
>
> >(GIS application,
> > tool for editing vector maps).
>
> Cool -- I"d love to see what you come up with. Also, be sure to check
> out FloatCanvas (wx.lib.floatcanvas, or newer version at:
>
> http://morticia.cs.dal.ca/FloatCanvas/
>
> It takes care of a lot of this for you. I'm also hoping to add more
> GISey stuff with it -- I'd love help!
>
> There is simplified OnPaint fn.
> > dc = wx.BufferedPaintDC(self, self._Buffer)
> > import time
> > start = time.clock()
> > nlines = 1000000
> > for i in range(nlines):
> > dc.DrawLine(0, 0, 100, 100)
> > stop = time.clock()
> > print >> sys.stderr, "time=%f" % (stop-start)
>
> Maybe this is just a test, but the point of a bufferedDC is that you can
> just blit the buffer in the OnPaint -- you only want to draw when
> something changes (again, see FloatCanvas)

Well, this was not simplified OnPaint fn, anyway not good example...

Currently I am using one pseudoDC for drawing background map,
decorations, etc. The second pseudoDC for drawing vector map which is
edited. This pseudoDC object is input for C++ driver (using Swig
interface for Python). The C++ driver (library) is needed because
vector objects of vector map need to be read using C API of the given
GIS application.

So OnPaint():

       dc = wx.BufferedPaintDC(self, self._Buffer)

        # we need to clear the dc BEFORE calling PrepareDC
        bg = wx.Brush(self.GetBackgroundColour())
        dc.SetBackground(bg)
        dc.Clear()

        # use PrepareDC to set position correctly
        self.PrepareDC(dc)

        # create a clipping rect from our position and size
        # and update region
        rgn = self.GetUpdateRegion().GetBox()
        dc.SetClippingRect(rgn)
        # draw to the dc using the calculated clipping rect
        self.pdc.DrawToDCClipped(dc, rgn)

        # draw vector map layer
        if self.pdcVector:
            self.pdcVector.DrawToDCClipped(dc, rgn)

Vector objects are drawn to PseudoDC using C++ driver
...
self.driver.DrawMap(self.pdcVector)
...

There is DrawLine method, each rendered line need to have unique id.

void DrawLine(line) {
...
            dc->SetId(id++);
            dc->DrawLines(pointsScreen->GetCount(), points);
...
}

There something very strange at least for me;-) If I comment out
'dc->SetId(startId);', speed of rendering is quite OK.

E.g. for vector map with 1e4 lines/points (each line has two nodes
which are also drawn), it takes 1.2s for drawing and 0.6s for each
redrawing.

If set up id by 'dc->SetId(startId++);' drawing takes 6s and more!!!
The response is significantly worse. I am just wondering why is
SetId() so expensive.

Thanks again for any hits!

Martin

--
Martin Landa <landa.martin@gmail.com> * http://gama.fsv.cvut.cz/~landa *

--
Martin Landa <landa.martin@gmail.com> * http://gama.fsv.cvut.cz/~landa *

I haven't profiled this, so I'm committing a cardinal sin by
speculating on performance-related results by just looking at the
source, but here's my guess.

wxPsuedoDC (wxPDC hereafter) has a list of PDC operations. It's in Z
order, and a PDC operation is a list of actual drawing operations +
some metadata.

When you call SetID(), that just sets the ID that the next drawing
operation will use.

When you do a drawing operation, it calls AddToList, which scans the
current list of operations for the current id, fails to find it, and
then creates a new one. There's an optimization in the search for
using the last ID, so repeated draws with the same ID are fast, but a
draw with a new id is slow - there's a linear search for each id, so
the time complexity for drawing n individual IDs is over O(n**2).

Adding an index for id->node lookups is probably the best solution.

···

On 10/1/07, Martin Landa <landa.martin@gmail.com> wrote:

Hi all,

trying to simplify my question..., 'stupid' example below

        import time
        start = time.clock()
        #nlines = self.__display.DrawMap(True) # force
        nlines = 1e4
        for line in range(nlines):
            self.mapWindow.pdcVector.SetId(line) # <--- HERE
            self.mapWindow.pdcVector.DrawLine(0,0,100,100)
        stop = time.clock()
        Debug.msg(3, "CDisplayDriver.DrawMap(): nlines=%d, sec=%f" % \
                      (nlines, stop-start))

On my machine it takes 3s (stop-start), if I comment out SetId() fn,
time is 0.04s. I am wondering why is pseudoDC.SetId() soooo
expensive...

Or I am just doing something wrong?

Thanks! Martin

2007/9/29, Martin Landa <landa.martin@gmail.com>:
> Hi Chris,
>
> thanks a lot for pointing me out to floatcanvas. I am quite newbie in
> wxPython as you can see;-)
>
> > Martin Landa wrote:
> > > I need to draw to DC huge number (> 1e6) of objects
> >
> > That's a LOT of objects -- can you really see them all at the same time?
> > You might want to do a bounding box check or something first. You might
> > also want to see if you can change what you draw depending on scale.
> >
> > Also, is all this stuff changing? you could perhaps buffer some of it.
> >
> > >(GIS application,
> > > tool for editing vector maps).
> >
> > Cool -- I"d love to see what you come up with. Also, be sure to check
> > out FloatCanvas (wx.lib.floatcanvas, or newer version at:
> >
> > http://morticia.cs.dal.ca/FloatCanvas/
> >
> > It takes care of a lot of this for you. I'm also hoping to add more
> > GISey stuff with it -- I'd love help!
> >
> > There is simplified OnPaint fn.
> > > dc = wx.BufferedPaintDC(self, self._Buffer)
> > > import time
> > > start = time.clock()
> > > nlines = 1000000
> > > for i in range(nlines):
> > > dc.DrawLine(0, 0, 100, 100)
> > > stop = time.clock()
> > > print >> sys.stderr, "time=%f" % (stop-start)
> >
> > Maybe this is just a test, but the point of a bufferedDC is that you can
> > just blit the buffer in the OnPaint -- you only want to draw when
> > something changes (again, see FloatCanvas)
>
> Well, this was not simplified OnPaint fn, anyway not good example...
>
> Currently I am using one pseudoDC for drawing background map,
> decorations, etc. The second pseudoDC for drawing vector map which is
> edited. This pseudoDC object is input for C++ driver (using Swig
> interface for Python). The C++ driver (library) is needed because
> vector objects of vector map need to be read using C API of the given
> GIS application.
>
> So OnPaint():
>
> dc = wx.BufferedPaintDC(self, self._Buffer)
>
> # we need to clear the dc BEFORE calling PrepareDC
> bg = wx.Brush(self.GetBackgroundColour())
> dc.SetBackground(bg)
> dc.Clear()
>
> # use PrepareDC to set position correctly
> self.PrepareDC(dc)
>
> # create a clipping rect from our position and size
> # and update region
> rgn = self.GetUpdateRegion().GetBox()
> dc.SetClippingRect(rgn)
> # draw to the dc using the calculated clipping rect
> self.pdc.DrawToDCClipped(dc, rgn)
>
> # draw vector map layer
> if self.pdcVector:
> self.pdcVector.DrawToDCClipped(dc, rgn)
>
> Vector objects are drawn to PseudoDC using C++ driver
> ...
> self.driver.DrawMap(self.pdcVector)
> ...
>
> There is DrawLine method, each rendered line need to have unique id.
>
> void DrawLine(line) {
> ...
> dc->SetId(id++);
> dc->DrawLines(pointsScreen->GetCount(), points);
> ...
> }
>
> There something very strange at least for me;-) If I comment out
> 'dc->SetId(startId);', speed of rendering is quite OK.
>
> E.g. for vector map with 1e4 lines/points (each line has two nodes
> which are also drawn), it takes 1.2s for drawing and 0.6s for each
> redrawing.
>
> If set up id by 'dc->SetId(startId++);' drawing takes 6s and more!!!
> The response is significantly worse. I am just wondering why is
> SetId() so expensive.
>
> Thanks again for any hits!
>
> Martin
>

Martin Landa wrote:

Currently I am using one pseudoDC for drawing background map,
decorations, etc.

I started on FloatCanvas long before pseudoDC existed, so I know nothing about it. I really should check it out though.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

Attached is a patch to implement this. Some timing information below
(wx29() is with my patch, wx28() is without):

C:\>python -m timeit -s "import crash;dc = crash.wx29()" "crash.dotest(dc,1000)"

100 loops, best of 3: 7.91 msec per loop

C:\>python -m timeit -s "import crash;dc = crash.wx28()" "crash.dotest(dc,1000)"

100 loops, best of 3: 13.5 msec per loop

C:\>python -m timeit -s "import crash;dc = crash.wx29()" "crash.dotest(dc,10000)
"
10 loops, best of 3: 71.8 msec per loop

C:\>python -m timeit -s "import crash;dc = crash.wx28()" "crash.dotest(dc,10000)
"
10 loops, best of 3: 580 msec per loop

Note the linear speed of the 29 version, vs the quadratic speed of the
28 version. The patch does some internals refactoring - FindObjNode is
renamed to FindObject and returns the actual pdcObject instead of a
wxList::Node. This s

pdc.patch (7.9 KB)

···

On 10/1/07, Chris Mellon <arkanes@gmail.com> wrote:

I haven't profiled this, so I'm committing a cardinal sin by
speculating on performance-related results by just looking at the
source, but here's my guess.

wxPsuedoDC (wxPDC hereafter) has a list of PDC operations. It's in Z
order, and a PDC operation is a list of actual drawing operations +
some metadata.

When you call SetID(), that just sets the ID that the next drawing
operation will use.

When you do a drawing operation, it calls AddToList, which scans the
current list of operations for the current id, fails to find it, and
then creates a new one. There's an optimization in the search for
using the last ID, so repeated draws with the same ID are fast, but a
draw with a new id is slow - there's a linear search for each id, so
the time complexity for drawing n individual IDs is over O(n**2).

Adding an index for id->node lookups is probably the best solution.

Chris Mellon wrote:

···

On 10/1/07, Chris Mellon <arkanes@gmail.com> wrote:

I haven't profiled this, so I'm committing a cardinal sin by
speculating on performance-related results by just looking at the
source, but here's my guess.

wxPsuedoDC (wxPDC hereafter) has a list of PDC operations. It's in Z
order, and a PDC operation is a list of actual drawing operations +
some metadata.

When you call SetID(), that just sets the ID that the next drawing
operation will use.

When you do a drawing operation, it calls AddToList, which scans the
current list of operations for the current id, fails to find it, and
then creates a new one. There's an optimization in the search for
using the last ID, so repeated draws with the same ID are fast, but a
draw with a new id is slow - there's a linear search for each id, so
the time complexity for drawing n individual IDs is over O(n**2).

Adding an index for id->node lookups is probably the best solution.

Attached is a patch to implement this.

Thanks!

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!