3
\$\begingroup\$

I have done some tests of my code, which I got help to improve here before. The code simply takes a screenshot of a selected process window, encodes it to JPEG (if I want), saves it to a memorystream and returns it (necessary for sending it).

My test simply runs the code 1000 times, and posts the time it took in ms.

It's very simple to look at it this way. I think that 10 seconds is 0% delay, but I'm not sure. From the results, it should be that for some reason (it should be 60fps as my screen displays that, not 100).

So here are the results:

800x600 = 11160 ms
1152x864 = 20218 ms
1600x1200 = 36399 ms

The resolution isn't exactly correct as I am not counting the window borders, etc, so it is a bit larger. 800x600 is super fast, even with JPEG encoding, just a little slower than BMP.

But then, even the little jump to 1152x864 makes a huge performance hit, about twice as slow (with BMP, there was about 17k ms, so the encoding did quite the hit, but it's still very slow).

1600x1200 was more than 3x times slower. I didn't try BMP there, but I would guess it would land on 30k ms.

private static MemoryStream PrintWindow(IntPtr hwnd, EncoderParameters JpegParam)
{
    NativeMethods.Rect rc;
    NativeMethods.GetWindowRect(hwnd, out rc);
    using (Bitmap bmp = new Bitmap(rc.Width, rc.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
    {
        using (Graphics gfxBmp = Graphics.FromImage(bmp))
        {
            IntPtr hdcBitmap = gfxBmp.GetHdc();
            try
            {
                NativeMethods.PrintWindow(hwnd, hdcBitmap, 0);
            }
            finally
            {
                gfxBmp.ReleaseHdc(hdcBitmap);
            }
        }

        MemoryStream ms = new MemoryStream();
        bmp.Save(ms, GetEncoderInfo(ImageFormat.Jpeg), JpegParam);
        return ms;
    }
}

It basically hooks to a window and takes a screenshot of that window.

I didn't even think that capturing took make such a big performance hit, so I really need to solve this.

I am using User32.dll to capture. I do, however, want to try out CaptureBlt as I think it may be faster, as User32.dll forces the window that's being captured to refresh all the time, which causes some problems on some applications (flicker).

If you know any way to improve this (excluding the encoding part (JPEG)), let me know, as I must improve this.

Worth mentioning:

Graphic Card = 6970 2GB (High End)

CPU = 4ghz 4 core

After testing a bit more, I noticed that the thing that takes about 30-40% of the speed is actually the saving of the bitmap to a Memorystream.

MemoryStream ms = new MemoryStream();
bmp.Save(ms, GetEncoderInfo(ImageFormat.Jpeg), JpegParam);
return ms;

Even with .bmp it takes a huge performance hit. I don't really know what to improve there, as I must save it to a memorystream to use it.

UPDATE 1:

Performance increases by 5-10% if I reuse the same memorystream all the time. So I don't let the static function make a new one, and handle that outside.

UPDATE 2:

Setting a static capacity to Memorystream MAY give a performance increase, not really sure, if it give, it´s extremely little, here are the tests.

Static

Execute1 time = 11144 ms
Execute1 time = 11223 ms
Execute1 time = 11183 ms

Dynamic

Execute1 time = 11301 ms
Execute1 time = 11282 ms
Execute1 time = 11194 ms

As you can see, it is give and take, but it doesn't hurt performance it seems that´s for sure.

It seems setting capacity doesn't even work from the looks of it. I can set it to 2 bytes, and it will still work normally.

Update 3:

There must be something that can be done, maybe not to this code, but another way. As else, how can captured software be able to save high resolutions like 1080p at 60fps, only to be limited by CPU or GPU and not the actual algorithm?

Update 4:

I finally got GDI32 to work. It solves the flicker, however the performance seems to be pretty much the same, maybe a bit better.

IntPtr dc1;
IntPtr dc2;
NativeMethods.Rect rc;
NativeMethods.GetWindowRect(hwnd, out rc);

using (Bitmap bmp = new Bitmap(rc.Width, rc.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
    using (Graphics g = Graphics.FromImage(bmp))
    {
        dc1 = g.GetHdc();
        dc2 = NativeMethods.GetWindowDC(hwnd);
        try
        {
            NativeMethods.BitBlt(dc1, 0, 0, rc.Width, rc.Height, dc2, 0, 0, 13369376);
        }
        finally
        {
            g.ReleaseHdc(dc1);
        }
    }

    bmp.Save(ms, GetEncoderInfo(ImageFormat.Jpeg), JpegParam);
            return ms;

}

Update 5:

Another optimization is to remove everything that is disposable, and have it enabled all the time, until you are done.

Meaning, Bitmap and Graphics and Intptr to the window handle.

Also, calling GetWindowRect takes a lot of performance and is really only needed if the window size changed, so you can move that and call it before the loop. If you change the size, just rerun it.

All this improves quite a bit, and the final limitation is the capture Bitblt, and compressing (if not BMP).

\$\endgroup\$
  • \$\begingroup\$ What are you building? Why is it not enough for you for a screenshot to take on the order of tens of ms? Perhaps use a highly optimized system tool written in C that can grab the picture of a Window from a display buffer somewhere and dump it as bmp. I am sure there are plenty of fast tools to do this on Linux; there must be something for Windows as well. You could settle for dumping the screenshot in whatever format makes it quicker, and then try to catch up on processing in a separate thread. Again, it all depends on what you are trying to build. What is it? Who creates the requirements? \$\endgroup\$ – Leonid Aug 9 '13 at 17:18
  • \$\begingroup\$ I am building an application that records a window(preferably a buffer but don´t know how to do that), and then sends it and displays it (pretty much, Remote Desktop to simplify). I would gladly like to get a bmp from the display buffer if that´s faster. But i don´t know how to implement it. The requirements are made by myself. \$\endgroup\$ – Zerowalker Aug 9 '13 at 17:23
  • \$\begingroup\$ Looks like MSFT has created a library for creating a Remote Desktop application, and here is a simple example of how one would use it :) codeproject.com/Articles/43705/Remote-Desktop-using-C-NET \$\endgroup\$ – Leonid Aug 9 '13 at 17:39
  • \$\begingroup\$ Looked at that, but what i meant with Remote Desktop was just that an imaged at the client is displayed at the server, was wrong to say Remote Desktop really, sorry for that:) \$\endgroup\$ – Zerowalker Aug 9 '13 at 17:41
  • 1
    \$\begingroup\$ Hm ... if you log in programmatically and disable moue events, then what is the difference? I think the specialized library is able to do this fast because it was engineered with performance in mind. \$\endgroup\$ – Leonid Aug 10 '13 at 16:33
3
\$\begingroup\$

Well, first of all, what you call "the little jump" is not that little. You can easily devide 1152x864 by 800x600 for yourself and see, that it, surprisingly enough, equals 2. Which means, that your algorithm most likely has a complexity of O(n), which makes sense.

As for your code - you can find the common way to save screenshots in .Net on SO (just dont forget to clean up: dispose bitmap, etc. ). You dont need to use interop for such tasks. I doubt it can be optimized any further.

Edit: you should also set MemoryStream buffer size manually, to fit the size of resulting data. If you don't, it might cause MemoryStream to resize constantly and lead to performance loss.

\$\endgroup\$
  • \$\begingroup\$ Well true enough, but i need to be able to handle higher resolutions. Well i am using the "fastest" one i got knowledge about, which is User32 printwindow. Would gladly try something else. SourceCopy isn´t the way to go though. I actually remake the memorystream everytime, though i actually changed that just now to use the same memorystream over and over, and only dispose once everything is completed. This increase performance by about 5-10%. Will try setting the buffer to the ms to be as big as the possible image, to see if a static buffer will improve it even further. \$\endgroup\$ – Zerowalker Aug 9 '13 at 10:58
  • \$\begingroup\$ Good point about the "little bumb", I had the same thought. If you take (resolution.x * resolution.y) / execution time you get roughly 43, 49, 52. So your slowdown as size gets larger is linear with a small slope, but is certainly not exponential. \$\endgroup\$ – aglassman Aug 15 '13 at 15:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.