New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GRAPHICS: Speed up and refactor SVG handling #4773
Conversation
02abb8f
to
d99a366
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
I am not sure other cases would be easy to find.
The policy has always been to use Surface::free after Surface::create and not to use Surface::free for other cases.
I am confused by this change. Yes, |
@sev- |
Well, I understand that, but shouldn't it be split into two commits, then? |
Now I am confused. :) So if I'm guessing right, |
On a second look... perhaps there is something to it. That Good catch! |
@sev- it seems there even was a memory leak, please take a look at the latest version whether the fix is good to go now. |
I would like to take time to look at it on a proper screen. |
In fact we are leaking memory and I don't see why we need _cache, _cachedX, _cachedY at all. |
@lephilousophe isn't possible that the intermediate surface has been used for implicit conversion from, say, 16bpp to 32bpp (which the nvg rasterizer expects) ? |
I thought that at first but both surfaces ( |
4abe3ec
to
d18ad30
Compare
Encouraged by @lephilousophe's analysis I have taken a further step, removed all the shenanigans with allocating and blitting, code is much faster and cleaner now. As a bonus, I have also reverted one commit which doesn't seem to be necessary, so we are caching bitmaps during initial startup again. All together this brought a speedup of 8 seconds on an intentionally weak hardware so I think it's worth applying. Please take a look whether I didn't miss something obvious. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, all in all, before this change we had a SVGBitmap
class which would render on any surface size with a cache for the last rendered resolution.
This implied that we keep a decoder around and a rasterized cache.
This code was used in 2 places: theme engine and grid widget where the SVGBitmap was rendered once and destroyed. A texture of the same pixel format was used to store the data.
With new code, the SVG is not a renderer anymore but directly a surface with a size set at creation time and a fixed pixel format.
This is OK for current uses but may reduce hypothetical future use cases.
The SVGSurface now consists only in a ManagedSurface so the memory footprint is reduced.
There are now less blitting too.
Except from my inline comments, code looks good to me and the features removal seem acceptable to me.
graphics/svg_surface.cpp
Outdated
#ifdef SCUMM_BIG_ENDIAN | ||
_pixelformat = new Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); | ||
const Graphics::PixelFormat PIXELFORMAT = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This requires a global constructor: this is not allowed as per our coding conventions.
Please move it back in the constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. I've changed it in a slightly different way, using a #define
as it serves basically the same purpose.
Otherwise I can inline the #ifdef
in the constructor but it wont look as good.
surf = nullptr; | ||
|
||
_bitmaps.erase(filename); | ||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am really not sure about this one: @sev- disabled cache because the it wasn't cleared when _scaleFactor changed.
I don't see anything new in code which would clear the cache in this case.
This must be added in setBaseResolution
.
If _scaleFactor
is different from s
: kill the cache like in refresh()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I have been worrying about the same thing. However I have also been unable to find a use case where this causes trouble (I was switching themes via the GUI, maybe there is some other way?). @sev- if you can remember a scenario which was broken without this change, that would help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should switch the scale factor, not the theme.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That, too - at least I couldn't find anything else than the large/medium/small scale options. And I don't see any difference in behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. I see the why of the commit.
It was done in April 2021.
At that time (before ad31dfc in August 2021), the _scaleFactor was depending on the height of the window in gui/gui-manager.cpp
.
A resize of the window implied a _scaleFactor
change which would have resulted in garbled images.
Changing the scale factor in GUI forces a ThemeEngine
destroy and recreate, that's why we can't replicate the issue.
I still think we should take the safe side and clean the cache in setBaseResolution
: changing the _scaleFactor
has to invalidate the cache even if today we don't see the bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I'm working on some backend code now I can't fully focus on the changes you've mentioned; would it be OK if I push the other two commits to master (which I need for my backend changes) and and let the third one present in this PR to keep the existing discussion visible?
d18ad30
to
6f87f19
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see no reasoning provided, nor what exactly does this speed up. Please, explain.
This reverts commit mikrosk@04f040a which claims to fix a theme reload bug but
I wasn't able to reproduce this behavior at all (with changed scale
and/or renderer ThemeEngine is reset and its _bitmap cache doesn't
contain any leftovers).
I do not understand why you express this discontent edging aggression in this commit log message. I read it as: "I was not able to reproduce it, the commit log is lying and the commit is useless. Reverting it"
graphics/module.mk
Outdated
@@ -48,7 +48,7 @@ MODULE_OBJS := \ | |||
scaler/normal.o \ | |||
sjis.o \ | |||
surface.o \ | |||
svg.o \ | |||
svg_surface.o \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason behind renaming this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to follow the convention, so SVGSurface is located in svg_surface.
graphics/svg_surface.cpp
Outdated
#define PIXELFORMAT Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24) | ||
#endif | ||
|
||
Graphics::SVGSurface::SVGSurface(Common::SeekableReadStream *in, int dw, int dh) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason behind this rename? We convert SVG into a bitmap. All of our graphics formats return Surface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it's inherited from ManagedSurface now, would you prefer to keep its old name despite this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I prefer to keep the old name for consistency.
Why is it now inherited from ManagedSurface and not a plain Surface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both ThemeEngine's _bitmaps[filename]
and grid's loadSurfaceFromFile
work with ManagedSurface so it was a nice optimization -- we can directly store/return new
ly created instances.
graphics/svg_surface.h
Outdated
* A derived graphics surface, which renders bitmap data from a SVG stream. | ||
*/ | ||
class SVGSurface : public ManagedSurface | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not conform to our code formatting guidelines
graphics/svg_surface.cpp
Outdated
in->read(data, size); | ||
data[size] = '\0'; | ||
|
||
NSVGimage *svg = nsvgParse(data, "px", 96); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does the cache go? Why? It is used extensively by our GUI code, and re-rendering it every time is not optimal, particularly in the icons list.
Rendering SVG is very expensive because it uses double-precision FPU arithmetics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. However from what I could see from the profiling, this bitmap is not reused. So while it looks good on the first sight, in reality this cache didn't provide any speedup, on the contrary.
As written in the other comment, I physically measured the speed this brought and it was enormous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess your discomfort with this change is related to the debate I had with @lephilousophe earlier -- whether it's acceptable to narrow SVGBitmap's features to its current usage (load svg file, render, copy and trash) or to keep it as generic as possible.
As the former approach brought significant speedup and simplified other parts of ScummVM code, too it looked like a good idea to go ahead.
That's definitely not the way I intended to be understood. It's only what I wrote there - this was fixing something but that something seems to be gone, bitmap loading is working (and cached) without this commit, period. As you can see, I tried to verify it with others to be sure that this claim is correct. No need to read between the lines. :) |
This cache buffer has no reason to be here and it is leaked when the class is destroyed.
6f87f19
to
40337da
Compare
After talking to @sev-:
|
Since the only use case for SVGBitmap is blitting it to a ManagedSurface, combine both into one to avoid unnecessary allocation and blitting.
This reverts commit 04f040a which forced a bitmap reloading to prevent reusing already up/downscaled images in case that _scaleFactor has changed. However after commit ad31dfc this no longer applies as changing the scale factor in GUI forces a ThemeEngine destroy and recreate. So _bitmaps[filename] is safe to keep its cached image which is reused e.g. during initial theme loading.
40337da
to
48f4f26
Compare
Thanks! |
Maybe you are aware of other places in code like this?
(I found this one the hard way)