|
|
Home » Developing U++ » U++ Developers corner » Optimizing DrawImage across platforms
Re: MILESTONE: gtk3 replaces gtk2 as default linux backend [message #53652 is a reply to message #53647] |
Sat, 18 April 2020 19:21 |
Tom1
Messages: 1277 Registered: March 2007
|
Senior Contributor |
|
|
mirek wrote on Sat, 18 April 2020 17:48As for windows numbers, looks like Image in Win32 was overengineered, one of optimizations was that the first time it is painted, it paints with SetSurface, only for subsequent paints actually move Image to system handles.
I have now exluded all those things whith #if 0 - looks like it improved at least current RectTracker performance quite nicely...
Mirek
void ImageSysData::Paint(SystemDraw& w, int x, int y, const Rect& src, Color c)
Is this the one (#if 0) you are referring to?
This caused a slowdown of 15-20 % for me. In this case I'm running on Core i7 with integrated Intel HD Graphics 4600 or something...
BR, Tom
|
|
|
Re: MILESTONE: gtk3 replaces gtk2 as default linux backend [message #53659 is a reply to message #53652] |
Sun, 19 April 2020 14:28 |
|
mirek
Messages: 14154 Registered: November 2005
|
Ultimate Member |
|
|
Tom1 wrote on Sat, 18 April 2020 19:21mirek wrote on Sat, 18 April 2020 17:48As for windows numbers, looks like Image in Win32 was overengineered, one of optimizations was that the first time it is painted, it paints with SetSurface, only for subsequent paints actually move Image to system handles.
I have now exluded all those things whith #if 0 - looks like it improved at least current RectTracker performance quite nicely...
Mirek
void ImageSysData::Paint(SystemDraw& w, int x, int y, const Rect& src, Color c)
Is this the one (#if 0) you are referring to?
This caused a slowdown of 15-20 % for me. In this case I'm running on Core i7 with integrated Intel HD Graphics 4600 or something...
BR, Tom
Feel free to experiment with it... And report results.
Mirek
|
|
|
Re: MILESTONE: gtk3 replaces gtk2 as default linux backend [message #53663 is a reply to message #53659] |
Sun, 19 April 2020 22:37 |
Tom1
Messages: 1277 Registered: March 2007
|
Senior Contributor |
|
|
Hi Mirek,
A 4K maximized window Paint from readily available Image/ImageBuffer Size(3840, 2035) comparison:
- Direct SetSurface: 5800 us
- Original DrawImage (1st>2nd>3rd...etc): 10900 us > 35000 us > 5100 us ...
- #if 0 variant (1st>2nd...etc): 18100 us > 6400 us ...
- Original without SetSurface optimization (1st>2nd...etc): 37000 us > 5000 us ...
This is still on Windows 10, Intel Core i7 with integrated Intel HD 4600.
So, SetSurface is always about 5800 us, which is a good all-around solution. #if 0 variant does not really help here. Original code with or without SetSurface optimization only starts to deliver after quite a few rounds when initialization penalty of 35-37 milliseconds is payed back with small gains like 700-800 us per round compared to direct SetSurface.
Maybe computers with better GPUs deliver better with these optimizations. (I don't know this as I do not have such hardware.) Anyway, with typical business setup this is not the case as high end GPUs only come with gaming rigs.
In the end I will probably stick with SetSurface for predictable performance.
On the Linux/GTK3 front the Ctrl::GetPrimaryScreenArea().GetSize(); (used for cache management) still eats half of the time. I think it should be cached in a per-monitor way.
Best regards,
Tom
|
|
|
Re: MILESTONE: gtk3 replaces gtk2 as default linux backend [message #53666 is a reply to message #53663] |
Mon, 20 April 2020 10:31 |
|
mirek
Messages: 14154 Registered: November 2005
|
Ultimate Member |
|
|
Worth testing:
#include <CtrlLib/CtrlLib.h>
using namespace Upp;
GUI_APP_MAIN
{
DDUMP(GetDeviceCaps(GetDC(NULL), SHADEBLENDCAPS) & SB_PIXEL_ALPHA);
}
This should detect whether AlphaBlend is HW accelerated on the platform.... It is 0 on my computer with RX580...
It would also be interesting to test how SW emulation fares, simply by changing
if(0 && fnAlphaBlend() && IsNull(c) && !ImageFallBack &&
In general, I think it might be a good idea to "reactivate" SetSurface, but maybe we can do that with some Image hinting system? I do not really like the idea of GetKind anymore, it is from old days when 1024x768 bitmap was considered huge...
gtk3: Have you tested:
static Size sz = Ctrl::GetPrimaryScreenArea().GetSize();
?
Mirek
[Updated on: Mon, 20 April 2020 11:24] Report message to a moderator
|
|
|
|
Re: MILESTONE: gtk3 replaces gtk2 as default linux backend [message #53671 is a reply to message #53666] |
Mon, 20 April 2020 12:57 |
Tom1
Messages: 1277 Registered: March 2007
|
Senior Contributor |
|
|
mirek wrote on Mon, 20 April 2020 11:31Worth testing:
#include <CtrlLib/CtrlLib.h>
using namespace Upp;
GUI_APP_MAIN
{
DDUMP(GetDeviceCaps(GetDC(NULL), SHADEBLENDCAPS) & SB_PIXEL_ALPHA);
}
This should detect whether AlphaBlend is HW accelerated on the platform.... It is 0 on my computer with RX580...
It would also be interesting to test how SW emulation fares, simply by changing
if(0 && fnAlphaBlend() && IsNull(c) && !ImageFallBack &&
In general, I think it might be a good idea to "reactivate" SetSurface, but maybe we can do that with some Image hinting system? I do not really like the idea of GetKind anymore, it is from old days when 1024x768 bitmap was considered huge...
gtk3: Have you tested:
static Size sz = Ctrl::GetPrimaryScreenArea().GetSize();
?
Mirek
Hi
1. DUMP gives me: GetDeviceCaps(GetDC(NULL), SHADEBLENDCAPS) & SB_PIXEL_ALPHA = 0
2. SW emulation (with "if(0 && ..." ) yields 28-31 milliseconds on a maximized 4k window.
3. On the Linux/GTK3 dept. "static Size sz = Ctrl::GetPrimaryScreenArea().GetSize();" does not work because first call yields zero size... The following calls return the monitor size. The initialization of "static Size sz" should be linked with a valid non-zero size returned.
Best regards,
Tom
|
|
|
|
Re: MILESTONE: gtk3 replaces gtk2 as default linux backend [message #53674 is a reply to message #53673] |
Mon, 20 April 2020 14:11 |
Tom1
Messages: 1277 Registered: March 2007
|
Senior Contributor |
|
|
Hi,
This works:
void SystemDraw::SysDrawImageOp(int x, int y, const Image& img, Color color)
{
...
static Size sz;
if(!sz.cx) sz = Ctrl::GetPrimaryScreenArea().GetSize();
cache.Shrink(4 * sz.cx * sz.cy, 1000); // Cache must be after Paint because of PaintOnly!
}
(Possibly not perfect on multi monitor environments if not started on largest monitor.)
Quote:What is the source of large opaque images in your case? Painter or ImageDraw or something else?
It's nearly always Painter -- or something similar (e.g. custom rendering) but the result is always in ImageBuffer.
Best regards,
Tom
|
|
|
|
|
Re: MILESTONE: gtk3 replaces gtk2 as default linux backend [message #53678 is a reply to message #53677] |
Mon, 20 April 2020 15:34 |
Tom1
Messages: 1277 Registered: March 2007
|
Senior Contributor |
|
|
How about putting it readily in constructor:
ImageBuffer(Size sz, int kind=IMAGE_UNKNOWN)
and default to something that always works, while not optimal for all uses.
And also in:
ImageBuffer::Create(Size sz, int kind=IMAGE_UNKNOWN)...
Mostly I know exactly what is going to be found in the buffer anyway...
Best regards,
Tom
|
|
|
|
Re: MILESTONE: gtk3 replaces gtk2 as default linux backend [message #53684 is a reply to message #53683] |
Mon, 20 April 2020 20:37 |
Tom1
Messages: 1277 Registered: March 2007
|
Senior Contributor |
|
|
Hi Mirek,
Win32: Current solution looks good! Probably as good as it can get with GDI.
Linux/GTK3: While SetSurface scores 12 ms times, DrawImage now yields about 5-6 ms results after initial run at 13-14 ms (due to caching).
(There is one RTIMESTOP("cairo_paint"); left in the code.)
I think this has now been taken pretty much as far as it goes with these backends, unless GTK3 still has some optimization available for opaque images.
Thanks and best regards,
Tom
|
|
|
Re: MILESTONE: gtk3 replaces gtk2 as default linux backend [message #53685 is a reply to message #53684] |
Mon, 20 April 2020 20:59 |
Tom1
Messages: 1277 Registered: March 2007
|
Senior Contributor |
|
|
Hi,
One Win32 question: Is it really necessary to cache the image if it is actually going to be drawn as a colored rect or via SetSurface?
Best regards,
Tom
[EDIT] Did some testing and this is faster than using SetSurface after caching:
void SystemDraw::SysDrawImageOp(int x, int y, const Image& img, const Rect& src, Color color)
{
GuiLock __;
if(img.GetLength() == 0)
return;
LLOG("SysDrawImageOp " << img.GetSerialId() << ' ' << img.GetSize());
// Insert this optimization here:
int kind = img.GetKindNoScan();
if(kind == IMAGE_OPAQUE && !IsNull(color)) {
Size sz=img.GetSize();
DrawRect(x, y, sz.cx, sz.cy, color);
return;
}
if(kind == IMAGE_OPAQUE && (GetDeviceCaps(GetHandle(), RASTERCAPS) & RC_DIBTODEV)) {
LTIMING("Image Opaque direct set");
Size sz=img.GetSize();
SetSurface(*this, x, y, sz.cx, sz.cy, ~img);
return;
}
// End of insertion
ImageSysDataMaker m;
...
Or is there some other reason to cache the image before display?
[Updated on: Mon, 20 April 2020 22:49] Report message to a moderator
|
|
|
Re: MILESTONE: gtk3 replaces gtk2 as default linux backend [message #53687 is a reply to message #53684] |
Tue, 21 April 2020 16:20 |
|
mirek
Messages: 14154 Registered: November 2005
|
Ultimate Member |
|
|
Tom1 wrote on Mon, 20 April 2020 20:37
I think this has now been taken pretty much as far as it goes with these backends, unless GTK3 still has some optimization available for opaque images.
Actually, it has cairo_surface_create_similar_image.
I have just finished experimenting with this feature. It is very confusing: It works really well, bringing DrawImage to microseconds time (probably using XRender path), however it has really nasty feature that it kills the drawing performance after DrawImage.
My guess is that it flips cairo into "GPU" mode and then when you want to draw e.g. rectangles, it starts copying memory between GPU and CPU for each element. Or something like that....
For future reference, before I revert them for now, here are changes I have tried:
struct ImageSysData {
Image img;
cairo_surface_t *surface = NULL;
void Init(const Image& m, cairo_surface_t *other);
~ImageSysData();
};
cairo_surface_t *CreateCairoSurface(const Image& img, cairo_surface_t *other)
{
Size isz = img.GetSize();
cairo_format_t fmt = CAIRO_FORMAT_ARGB32;
cairo_surface_t *surface = other && 0 ? cairo_surface_create_similar_image(other, fmt, isz.cx, isz.cy)
: cairo_image_surface_create(fmt, isz.cx, isz.cy);
cairo_surface_flush(surface);
byte *a = (byte *)cairo_image_surface_get_data(surface);
int stride = cairo_format_stride_for_width(fmt, isz.cx);
for(int yy = 0; yy < isz.cy; yy++) {
Copy((RGBA *)a, img[yy], isz.cx);
a += stride;
}
cairo_surface_mark_dirty(surface);
return surface;
}
cairo_surface_t *CreateCairoSurface(const Image& img)
{
return CreateCairoSurface(img, NULL);
}
void ImageSysData::Init(const Image& m, cairo_surface_t *other)
{
img = m;
surface = CreateCairoSurface(m, other);
SysImageRealized(img);
}
ImageSysData::~ImageSysData()
{
SysImageReleased(img);
cairo_surface_destroy(surface);
}
struct ImageSysDataMaker : LRUCache<ImageSysData, int64>::Maker {
Image img;
cairo_surface_t *other;
virtual int64 Key() const { return img.GetSerialId(); }
virtual int Make(ImageSysData& object) const { object.Init(img, other); return img.GetLength(); }
};
void SystemDraw::SysDrawImageOp(int x, int y, const Image& img, Color color)
{
GuiLock __;
FlushText();
if(img.GetLength() == 0)
return;
LLOG("SysDrawImageOp " << img.GetSerialId() << ' ' << x << ", " << y << ", "<< img.GetSize());
ImageSysDataMaker m;
static LRUCache<ImageSysData, int64> cache;
static int Rsz;
Rsz += img.GetLength();
if(Rsz > 200 * 200) { // we do not want to do this for each small image painted...
Rsz = 0;
cache.Remove([](const ImageSysData& object) {
return object.img.GetRefCount() == 1;
});
}
LLOG("SysImage cache pixels " << cache.GetSize() << ", count " << cache.GetCount());
m.img = img;
m.other = cairo_get_target(cr);
ImageSysData& sd = cache.Get(m);
if(!IsNull(color)) {
SetColor(color);
cairo_mask_surface(cr, sd.surface, x, y);
}
else {
RTIMESTOP("cairo_paint");
if(img.GetKindNoScan() == IMAGE_OPAQUE)
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface(cr, sd.surface, x, y);
cairo_paint(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
}
static Size ssz;
if(ssz.cx == 0)
ssz = Ctrl::GetVirtualScreenArea().GetSize();
cache.Shrink(4 * ssz.cx * ssz.cy, 1000); // Cache must be after Paint because of PaintOnly!
}
|
|
|
Re: MILESTONE: gtk3 replaces gtk2 as default linux backend [message #53688 is a reply to message #53687] |
Tue, 21 April 2020 18:03 |
Tom1
Messages: 1277 Registered: March 2007
|
Senior Contributor |
|
|
Hi,
Nice! I mean the latest GTK3 DrawImage optimization... Tried it and it feels very appealing and would perform great with these Painter generated scenes. I'm just wondering how well it would work with a typical GUI application workload, if the GUI was first rendered with Painter and then painted to window with this new efficient DrawImage solution. I also wonder if rectangles could possibly be drawn more efficiently using DrawImage with opaque color, since this uses an image mask and should therefore not need to copy it back to CPU accessible memory. I could not benchmark this properly here, but it might be something to look at.
Best regards,
Tom
|
|
|
|
|
|
Re: MILESTONE: gtk3 replaces gtk2 as default linux backend [message #53699 is a reply to message #53689] |
Wed, 22 April 2020 14:08 |
Tom1
Messages: 1277 Registered: March 2007
|
Senior Contributor |
|
|
Hi,
Here's a completing feature for RectTracker for tracking line drawing (TrackLine) and free-hand polyline (TrackRoute):
class LineTracker: public RectTracker{
public:
int mode;
Vector<Point> route;
Vector<Point> TrackLine(const Point& p){
mode=1;
route.Clear();
route.Add(p);
Track(Rect(p,p),0,0);
return clone(route);
}
Vector<Point> TrackRoute(const Point& p){
mode=2;
route.Clear();
route.Add(p);
Track(Rect(p,p),0,0);
return clone(route);
}
void MouseMove(Point p, dword){
if(mode==2 || route.GetCount()<2){
route.Add(p);
}
else route[1]=p;
Refresh();
sync(Rect(route[0],route[route.GetCount()-1]));
}
void Paint(Draw& w){
w.DrawImage(0, 0, master_image);
w.Clip(clip & GetMaster().GetSize());
int style=width;
if(pattern!=DRAWDRAGRECT_SOLID){
Color color2 = IsDark(color) ? White() : Black();
w.DrawPolyline(route,width,color2);
switch(pattern){
case DRAWDRAGRECT_NORMAL:
style=PEN_DOT;
break;
case DRAWDRAGRECT_DASHED:
style=PEN_DASHDOT;
break;
}
}
w.DrawPolyline(route,style,color);
w.End();
}
LineTracker(Ctrl &master): RectTracker(master){
MinSize(Size(-100000,-100000));
mode=1;
}
};
Feel free to include it in U++.
To test the functionality, you can try:
void LeftDown(Point p, dword flags){
LineTracker tracker(*this);
tracker.Solid();
tracker.Width(3);
tracker.SetColor(Blue());
Vector<Point> res;
if(flags&K_CTRL) res = tracker.TrackRoute(p);
else res = tracker.TrackLine(p);
// ...
}
It proved easy enough to inherit from RectTracker, so I made it a separate class after all.
Best regards,
Tom
[Updated on: Wed, 22 April 2020 21:11] Report message to a moderator
|
|
|
Goto Forum:
Current Time: Tue Dec 03 01:02:55 CET 2024
Total time taken to generate the page: 0.04935 seconds
|
|
|