Drawing a lot of SVG images is slow

88 views
Skip to first unread message

Quentin Cosendey

unread,
Apr 1, 2025, 1:36:36 PM4/1/25
to wx-u...@googlegroups.com
Hello,

I'm developing a game. I'm trying to use SVG images to draw the board
and all game elements (tokens, dice, etc.).
I wanted to use SVG because it allows the board to be seen all at once
and have its size adapted to the size of the window, i.e. fill the
window as much as possible / make the board as big as possible, without
pixelization effects. It also allows the user to customize the color of
some game elements (for example tokens) quite easily, as well as select
their own colors to make their own dark or high-contrast mode. All this
is good for visually impaired players.

However, displaying everything (~1000 elements) seems to be quite slow,
and it makes the interface lagging globally. It takes hundreds of
milliseconds to update the screen after having pressed a key and/or
moved the mouse.
Am I doing something wrong ? Do you have ideas on how I could improve
performances ?

Drawing code looks as follows, I have tried to simplify it a little:

void GameWindow::OnPaint (wxPaintEvent& e) {
  wxAutoBufferedPaintDC dc(this);
  std::unique_ptr<wxGraphicsContext> g(wxGraphicsContext::Create(dc));
    ...
  for (auto& square: squares) {
    wxRect pos = ... ; // boundaries of the square
    g->ResetClip();
    g->Clip(pos);
    for (std::string& image: square.getElements()) {
      auto bundle = app.getImage(image);
      wxSize defaultSize = bundle.GetDefaultSize();
      wxSize finalImageSize =
calcGreatestPossibleSize(wxSize(pos.width, pos.height), defaultSize);
      auto bitmap = bundle.GetBitmap(finalImageSize);
      if (bitmap.IsOk()) {
        finalImageSize = bitmap.GetSize();
        g->DrawBitmap(bitmap, pos.x, pos.y, finalImageSize.x,
finalImageSize.y);
      }
    }
    g->ResetClip();
  }
}


Loading code: wxImageBundle are stored in a
std::unordered_map<std::string, wxImageBundle> .
Using a simple array instead of the map and indexing images by index
instead of by name doesn't make a lot of difference, I have already tested.

wxBitmapBundle App::getImage (const std::string& name) {
  auto it = images.find(name);
  if (it!=images.end()) return it->second;
  wxString svgfn = "images/" + name + ".svg";
  if (!svgfn.size()) return wxBitmapBundle(); // not found, return
empty bundle
  wxSize imSize = readSVGImageSize(svgfn);
  auto bundle = wxBitmapBundle::FromSVGFile(svgfn, imSize);
  images[name] = bundle;
  return bundle;
}

The SVG images used are extremely simple for the moment, there are no
complex things: basic shapes, polygons, polylines, paths; plain fill and
stroke colors, no gradients or stuff like that.
Most squares have always the same size if the window isn't resized.

In case it can make a difference, GameWindows extends wxControl,
wxVarHVScrollHelper, wxAccessible

Where could the problem be ?

As far as I understand, the method wxImageBundle::GetBitmap basically
renders the SVG in a traditional in-memory bitmap. Is that really
necessary ? Isn't there a more direct way to draw SVG images ?
The documentation says that generated bitmaps are cached, so since the
size don't change unless the window is resized, it shouldn't be a
problem, right ?

Is SVG support in WX just not made for that kind of thing ?
In this case do you have an alternate SVG library integrable with WX to
advise ?

Thank you very much for your answers !

Igor Korot

unread,
Apr 1, 2025, 2:28:09 PM4/1/25
to wx-u...@googlegroups.com
Hi,
Sorry for top-posting, but…

What platform are you on?
What wx version?
Are you working with Debug build?
Choosing wxWidgets for the game is kind of weird - are you sure about that?

Thank you.

--
Please read https://www.wxwidgets.org/support/mlhowto.htm before posting.
---
You received this message because you are subscribed to the Google Groups "wx-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to wx-users+u...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/wx-users/6b90542c-47af-48da-9851-883ece612e48%40bluewin.ch.

Quentin Cosendey

unread,
Apr 1, 2025, 3:23:21 PM4/1/25
to wx-u...@googlegroups.com

Hello,


Igor> What platform are you on?


I'm on Windows 10 and 11.


Igor> What wx version?


I'm using  the version cloned directly from git. It isn't the very latest one, as I haven't updated it for a couple months, but I don't think it would change much.


Igor> Are you working with Debug build?


I mostly observe the same slugginess, whether I'm using the debug build or not.

I'm compiling with MinGW-W64 / GCC 13 in 64 bits.


Igor> Choosing wxWidgets for the game is kind of weird - are you sure about that?


Yes I'm completely sure. It isn't weird at all, because it allows quite easily to make the game accessible to screen readers and zoom software, among others. This is my special particularity. 

Most interactions are done via usual widgets (lists, buttons, grids, etc.), which makes it well suited to a GUI framework like WXWidgets. It's not at all a videogame in 3D.


Thank you.

Vadim Zeitlin

unread,
Apr 1, 2025, 3:50:07 PM4/1/25
to wx-u...@googlegroups.com
On Tue, 1 Apr 2025 19:36:31 +0200 Quentin Cosendey wrote:

QC> I'm developing a game. I'm trying to use SVG images to draw the board
QC> and all game elements (tokens, dice, etc.).

This will certainly be slow if you rasterize SVG every time, so you should
definitely cache the bitmaps generated for the given size. From the code
given, it's not clear whether or not this is the case.

QC> As far as I understand, the method wxImageBundle::GetBitmap basically
QC> renders the SVG in a traditional in-memory bitmap. Is that really
QC> necessary ? Isn't there a more direct way to draw SVG images ?

Not currently, no.

QC> The documentation says that generated bitmaps are cached, so since the
QC> size don't change unless the window is resized, it shouldn't be a
QC> problem, right ?

If you reuse the same wxBitmapBundle, this should be the case, but perhaps
something goes wrong somewhere...

QC> Is SVG support in WX just not made for that kind of thing ?

Performance definitely wasn't an important factor when choosing the SVG
rendering library for wx -- rather, the simplicity of integration it was.

QC> In this case do you have an alternate SVG library integrable with WX to
QC> advise ?

You could try using LunaSVG, but normally SVG rasterizing performance
shouldn't count anyhow, as the bitmaps should be cached. Drawing 1000
bitmaps is not going to be fast neither, unfortunately, so if you can find
a way to precombine them, consider doing this.

Also, under Windows, using D2D-based wxGraphicsContext could be much
faster than using the default GDI+-based one or GDI.

Regards,
VZ

--
TT-Solutions: wxWidgets consultancy and technical support
https://www.tt-solutions.com/

Eric Jensen

unread,
Apr 1, 2025, 5:16:40 PM4/1/25
to Quentin Cosendey
Hello Quentin,

Tuesday, April 1, 2025, 7:36:31 PM, you wrote:

QC> Hello,

QC> I'm developing a game. I'm trying to use SVG images to draw the board and all game elements (tokens, dice, etc.).
QC> I wanted to use SVG because it allows the board to be seen all at once and have its size adapted to the size of the window, i.e. fill the window as much as possible / make the board as big as possible, without pixelization effects. It also allows the user to customize the color of some game elements (for example tokens) quite easily, as well as select their own colors to make their own dark or high-contrast mode. All this is good for visually impaired players.

QC> However, displaying everything (~1000 elements) seems to be quite slow, and it makes the interface lagging globally. It takes hundreds of milliseconds to update the screen after having pressed a key and/or moved the mouse.
QC> Am I doing something wrong ? Do you have ideas on how I could improve performances ?

QC> Drawing code looks as follows, I have tried to simplify it a little:

QC> void GameWindow::OnPaint (wxPaintEvent& e) {
QC>   wxAutoBufferedPaintDC dc(this);
QC>   std::unique_ptr<wxGraphicsContext> g(wxGraphicsContext::Create(dc));
QC>     ...
QC>   for (auto& square: squares) {
QC>     wxRect pos = ... ; // boundaries of the square
QC>     g->ResetClip();
QC>     g->Clip(pos);
QC>     for (std::string& image: square.getElements()) {
QC>       auto bundle = app.getImage(image);
QC>       wxSize defaultSize = bundle.GetDefaultSize();
QC>       wxSize finalImageSize = calcGreatestPossibleSize(wxSize(pos.width, pos.height), defaultSize);
QC>       auto bitmap = bundle.GetBitmap(finalImageSize);
QC>       if (bitmap.IsOk()) {
QC>         finalImageSize = bitmap.GetSize();
QC>         g->DrawBitmap(bitmap, pos.x, pos.y, finalImageSize.x, finalImageSize.y);
QC>       }
QC>     }
QC>     g->ResetClip();
QC>   }
QC> }

In addition to what Vadim wrote:

At least when using GDI+, conversion from wxBitmap to wxGraphicsBitmap is slow and should be avoided at all costs. It's possible that this is not the case with the D2D renderer, but if in doubt, you should cache the bitmaps as wxGraphicsBitmap, not wxBitmap.

Also you should check if wxAutoBufferedPaintDC actually buffers, or not. If yes, explicitly using wxBufferedPaintDC with a static bitmap will save some time, too, as it avoids creating a new bitmap each time.

Eric



--

Quentin Cosendey

unread,
Apr 2, 2025, 12:36:32 AM4/2/25
to wx-u...@googlegroups.com
Hello,


VZ>  This will certainly be slow if you rasterize SVG every time, so you
should
definitely cache the bitmaps generated for the given size. From the code
given, it's not clear whether or not this is the case.

VZ>  If you reuse the same wxBitmapBundle, this should be the case, but
perhaps something goes wrong somewhere...


How can I check if I'm always returned the same cached bitmap or not ? /
if caching works as expected ?


VZ>  Also, under Windows, using D2D-based wxGraphicsContext could be
much faster than using the default GDI+-based one or GDI.


What should I do to use D2D instead of GDI ?

I already create a wxGraphicsContext from the DC, is that incorrect /
insufficient ? Is that in fact just a facade and GDI is still used anyway ?


EJ> At least when using GDI+, conversion from wxBitmap to
wxGraphicsBitmap is slow and should be avoided at all costs. It's
possible that this is not the case with the D2D renderer, but if in
doubt, you should cache the bitmaps as wxGraphicsBitmap, not wxBitmap.


I'll check that, but I don't think wxBitmapBundle allows that easily. If
it effectively doesn't, probably that I should try to find another way
to draw my SVG images.


VZ>  You could try using LunaSVG, but normally SVG rasterizing
performance shouldn't count anyhow, as the bitmaps should be cached.
Drawing 1000 bitmaps is not going to be fast neither, unfortunately, so
if you can find a way to precombine them, consider doing this.


I'll have a look at LunaSVG. Combining is maybe the key point, I'll also
try to find a way to improve that as well.


QC> Isn't there a more direct way to draw SVG images ?
VZ>  Not currently, no.

OK, sad! Is rasterization an obligation ? Or it's just that WXWidgets
currently don't give another way around it ?

Would it be technically possible and interesting to translate directly
from SVG commands to wxGraphicsContext or DC calls on the window,
without drawing first to an intermediate bitmap ? Sorry for the newbie
question.




Thank you very much.

Regards.

Eric Jensen

unread,
Apr 2, 2025, 2:00:35 AM4/2/25
to Quentin Cosendey
Hello Quentin,

Wednesday, April 2, 2025, 6:36:27 AM, you wrote:


VZ>>  Also, under Windows, using D2D-based wxGraphicsContext could be much faster than using the default GDI+-based one or GDI.
QC> What should I do to use D2D instead of GDI ?
wxGraphicsContext *gc = wxGraphicsRenderer::GetDirect2DRenderer()->CreateContext(...);

VZ>>  You could try using LunaSVG, but normally SVG rasterizing performance shouldn't count anyhow, as the bitmaps should be cached. Drawing 1000 bitmaps is not going to be fast neither, unfortunately, so if you can find a way to precombine them, consider doing this.
QC> I'll have a look at LunaSVG. Combining is maybe the key point, I'll also try to find a way to improve that as well.
Rendering the SVG each time will always be significantly slower than drawing a pre-rendered bitmap.

Eric



--

Leo28C

unread,
Apr 2, 2025, 2:12:00 AM4/2/25
to wx-u...@googlegroups.com
>Isn't there a more direct way to draw SVG images ?

According to Brave Search, yes: https://github.com/micahpearlman/MonkVG

I think that one sounds like what you need. It doesn't mention wx compatibility, but since it runs on top of OpenGL, I imagine it can be made compatible without much tinkering.

Quentin Cosendey

unread,
Apr 2, 2025, 12:05:57 PM4/2/25
to wx-u...@googlegroups.com
Hello,


QC> What should I do to use D2D instead of GDI ?
EJ> wxGraphicsContext *gc =
wxGraphicsRenderer::GetDirect2DRenderer()->CreateContext(...);


wxGraphicsRenderer::GetDirect2DRenderer() doesn't exists for me.


GameWindow.cpp: In member function 'void
GameWindow::OnPaint(wxPaintEvent&)':
GameWindow.cpp:943:21: error: 'GetDirect2DRenderer' is not a member of
'wxGraphicsRenderer'
  943 | wxGraphicsRenderer::GetDirect2DRenderer() ->CreateContext(dc)


Do I need to do something special to have it ?


QC>Isn't there a more direct way to draw SVG images ?
Leo> According to Brave Search, yes:
Leo> https://github.com/micahpearlman/MonkVG
Leo> I think that one sounds like what you need. It doesn't mention wx
compatibility, but since it runs on top of OpenGL, I imagine it can be
made compatible without much tinkering.


I don't use OpenGL at all, so I don't know if it's doable with
WXWidgets, but I'll have a look at that library, at least maybe to get
ideas. Thank you.


Thank you very much.

Best regards.

Igor Korot

unread,
Apr 2, 2025, 12:21:32 PM4/2/25
to wx-u...@googlegroups.com
Hi,


On Wed, Apr 2, 2025, 11:05 AM Quentin Cosendey <quentin....@bluewin.ch> wrote:
Hello,


QC> What should I do to use D2D instead of GDI ?
EJ> wxGraphicsContext *gc =
wxGraphicsRenderer::GetDirect2DRenderer()->CreateContext(...);


wxGraphicsRenderer::GetDirect2DRenderer() doesn't exists for me.


GameWindow.cpp: In member function 'void
GameWindow::OnPaint(wxPaintEvent&)':
GameWindow.cpp:943:21: error: 'GetDirect2DRenderer' is not a member of
'wxGraphicsRenderer'
   943 | wxGraphicsRenderer::GetDirect2DRenderer() ->CreateContext(dc)


Do I need to do something special to have it ?

Did you include necessary header?



QC>Isn't there a more direct way to draw SVG images ?
Leo> According to Brave Search, yes:
Leo> https://github.com/micahpearlman/MonkVG
Leo> I think that one sounds like what you need. It doesn't mention wx
compatibility, but since it runs on top of OpenGL, I imagine it can be
made compatible without much tinkering.


I don't use OpenGL at all, so I don't know if it's doable with
WXWidgets, but I'll have a look at that library, at least maybe to get
ideas. Thank you.

Look at all OpenGL samples in wx.
Build and run them.

Hopefully you will get smth useful...

Thank you.



Thank you very much.

Best regards.



Le 02.04.2025 à 08:00, 'Eric Jensen' via wx-users a écrit :
> Hello Quentin,
>
> Wednesday, April 2, 2025, 6:36:27 AM, you wrote:
>
>
> VZ>>  Also, under Windows, using D2D-based wxGraphicsContext could be much faster than using the default GDI+-based one or GDI.
> QC> What should I do to use D2D instead of GDI ?
> wxGraphicsContext *gc = wxGraphicsRenderer::GetDirect2DRenderer()->CreateContext(...);
>
> VZ>>  You could try using LunaSVG, but normally SVG rasterizing performance shouldn't count anyhow, as the bitmaps should be cached. Drawing 1000 bitmaps is not going to be fast neither, unfortunately, so if you can find a way to precombine them, consider doing this.
> QC> I'll have a look at LunaSVG. Combining is maybe the key point, I'll also try to find a way to improve that as well.
> Rendering the SVG each time will always be significantly slower than drawing a pre-rendered bitmap.
>
> Eric
>
>
>

--
Please read https://www.wxwidgets.org/support/mlhowto.htm before posting.
---
You received this message because you are subscribed to the Google Groups "wx-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to wx-users+u...@googlegroups.com.

Vadim Zeitlin

unread,
Apr 2, 2025, 12:24:47 PM4/2/25
to wx-u...@googlegroups.com
On Wed, 2 Apr 2025 06:36:27 +0200 Quentin Cosendey wrote:

QC> How can I check if I'm always returned the same cached bitmap or not ? /
QC> if caching works as expected ?

There is no simple way to do it, unfortunately. I'd probably just use the
debugger to check that the bitmaps are not re-rendered, but it might be a
better idea to add some logging to this code.

QC> I'll check that, but I don't think wxBitmapBundle allows that easily. If
QC> it effectively doesn't, probably that I should try to find another way
QC> to draw my SVG images.

You can do caching outside of wxBitmapBundle too.

QC> QC> Isn't there a more direct way to draw SVG images ?
QC> VZ>  Not currently, no.
QC>
QC> OK, sad! Is rasterization an obligation ?

Yes, of course, you need to turn vector graphics into pixels.

QC> Would it be technically possible and interesting to translate directly
QC> from SVG commands to wxGraphicsContext or DC calls on the window,
QC> without drawing first to an intermediate bitmap ? Sorry for the newbie
QC> question.

It would, but it still would be much slower than just rasterizing to a
bitmap once and then simply blitting it to the screen, so it's not really
interesting.

Vadim Zeitlin

unread,
Apr 2, 2025, 12:25:46 PM4/2/25
to wx-u...@googlegroups.com
On Wed, 2 Apr 2025 18:05:51 +0200 Quentin Cosendey wrote:

QC> wxGraphicsRenderer::GetDirect2DRenderer() doesn't exists for me.

This means your library was built with wxUSE_GRAPHICS_DIRECT2D==0. You
need to set it to 1 in your setup.h and rebuild.

Quentin Cosendey

unread,
Apr 6, 2025, 10:41:28 AM4/6/25
to wx-u...@googlegroups.com
Hello,


I have tried LunaSVG and that library works well... but it doesn't solve
the slugginess problem.


I finally have found the cause, and it has nothing to do with the SVG
library in use, nor the resterization process. Caching works well also
with wxBitmapBundle.


First on the OnPaint method, I added:

auto updateRegion = GetUpdateRegion();

gc->Clip(updateRegion);


So to make the system only actually only paint the zone of the screen it
really needs to paint.


My second, and much bigger error, was to systematically call Refresh()
on every key down and everytime the mouse is moved over a different item
(to highlight the item in focus). I wasn't aware that it caused the
whole control to be repainted, and **that** was the primary cause of the
slugginess, while it was only necessary to repaint the previously and
the newly highlighted items.

Calling RefreshRect, or any method indirectly calling RefreshRect like
RefreshRow, RefreshColumn, RefeshRowColumn or RefreshRowsColumns instead
of just Refresh makes the UI working as smooth as it can.

It seems that even if there are 1000 calls to
wxGraphicsContext::DrawBitmap, the system is smart enough to ignore them
completely if it's outside the update region.


Thank you very much for your help.
Reply all
Reply to author
Forward
0 new messages