Fastest BitBlt in C#/.NET

A

_AV

I need to do ultra-fast bitblts in a C# program. Is it necessary to go
back to the older GDI DLL, or is are there fast calls within GDI+?

Basically what I'm doing is creating an array of 32-bit ints (pixels) in
memory, writiing individual pixels in the array, and then blitting it to
the screen. The in-mem image is relatively simple 2-D vector graphics,
so the Blit is the biggest bottleneck at present.
 
P

Patrik Löwendahl [C# MVP]

GDI+ is not known for it's speed *S*.

For fast graphics I would recomend using Direct X ( or assembler / c++) ;)
 
J

Justin Rogers

DrawImageUnscaled and DrawCachedBitmap are your fastest two managed
alternatives. DrawCachedBitmap approaches the same speed as BitBlt (not
quite, but almost) with the only problem being that it doesn't have a C#
equivalent and is part of the GDI+ flat API (consumed by C++).

Use GDI, you can get some pretty good frame-rates out of it. Take major
advantage of dirty rectangles so you only copy the area of the buffer that
changes and you should be fine.


--
Justin Rogers
DigiTec Web Consultants, LLC.
Blog: http://weblogs.asp.net/justin_rogers

Patrik Löwendahl said:
GDI+ is not known for it's speed *S*.

For fast graphics I would recomend using Direct X ( or assembler / c++) ;)

--
Patrik Löwendahl [C# MVP]
www.cshrp.net - "Elegant code by witty programmers"
_AV said:
I need to do ultra-fast bitblts in a C# program. Is it necessary to go
back to the older GDI DLL, or is are there fast calls within GDI+?

Basically what I'm doing is creating an array of 32-bit ints (pixels) in
memory, writiing individual pixels in the array, and then blitting it to
the screen. The in-mem image is relatively simple 2-D vector graphics,
so the Blit is the biggest bottleneck at present.
 
J

Justin Rogers

He points out well in the FAQ how careful you have to be with unmanaged
code. Primarily, you have to be prepared to harden your methods. In 99.99%
of all cases the code in the tutorial will work, but there is an ever expanding
percentage that an exception will be thrown during various portions of the
method and help to create a nasty resource leak.

Make sure you either wrap your IntPtr's into a handle with GC protection if
you plan on not doing error handling, or place in the appropriate error handling
to make sure resources are properly cleaned.

To be purely hardened would take an immense amount of work. A basic hardening
can be performed by assuming that all work done in the finally block won't fail
(but
it still can fail, which creates a bad situation for everyone).
 
A

_AV

Make sure you either wrap your IntPtr's into a handle with GC protection if
you plan on not doing error handling,

Not sure I follow this, Justin. Is it possible to illustrate what you
mean re wrapping IntPtrs?
 
J

Justin Rogers

You need some intricate knowledge of how the run-time works. Take for instance
something like (pseudo)

IntPtr i1 = GetHdc();
DoSomethingWithDC(new HandleRef(null, i1), ...);
ReleaseDC(i1);

Okay, that code demonstrates the problem. DoSomethingWithDC can throw an
exception (OOM, SOE,
TAE, or any number of others)... If it does i1 is lost for good, and you can't
release it. Now, if you do this
with a Graphics object, the IntPtr is actually stored INTERNALLY first... The
Graphics object actually
has an internal member variable allowing it to wrap and then return the hDC...
If an exception occurs,
then at some point the GC will kick in and run the finalizer for the Graphics
and collect the resource by
calling ReleaseDC explicitly.

That is what I mean by wrapping the IntPtr in a managed object. For instance,
the CreateCompatibleDC
in the tutorial allocates an hDC, but doesn't wrap it in a managed object. If
the method fails later, there is
no storage of the IntPtr to be cleaned up, it just leaks. If you placed this in
say a new class called HandleDC
then a finalizer could enforce the release of the resource.

public class CompatibleDC {
private IntPtr dc = IntPtr.Zero;
~CompatibleDC() { Release(); }

private CompatibleDC() {}

public static CompatibleDC CreateCompatibleDC(IntPtr initialDC) {
CompatibleDC cDC = new CompatibleDC(); // allocate memory here, if OOM,
then no leakage.
cDC.dc = Win32.CreateCompatibleDC(initialDC); // Wrapped in an object
instance now.
return cDC; // Give this to the client
}

// Not thread-safe
public IntPtr DC { get { return this.dc; } }
public void Release() {
if ( dc != IntPtr.Zero ) { Win32.ReleaseDC(dc); dc = IntPtr.Zero; }
}
}
 
S

Sami Vaaraniemi

Instead of relying on garbage collection to (eventually) release the
resource, it would be better to make the wrapper class derive from
IDisposable and implement Dispose to release the resource. You would then
use the wrapper class like so:

using (CompatibleDC cdc = CompatibleDC.CreateCompatibleDC(initialDC))
{
// ...
}

Relying on garbage collection to release unmanaged resources is not a too
robust practice.

Regards,
Sami
 
J

Justin Rogers

That was an example. Unfortunately, in the realm of GDI, you'd wind up with 5 or
6
using statements per behavior, and it can get ugly very fast. If you were to
harden
the method, I certainly wouldn't go the path of the bloated using statement, and
would
probably rely on more appropriately specific try...finally blocks.

You won't find very many using blocks in Microsoft's own code. In fact, you
won't find
very many try...catch blocks around many protected resources. The idea being
that the
managed object eventually cleans up the resource if failure does occur. This is
extensively
done in the Windows Forms API. Still up in the air as to what a best practice
might be
when you want to leverage code brevity, performance, and robust/reliable
operation all
at the same time.
 
S

Sami Vaaraniemi

Justin Rogers said:
That was an example. Unfortunately, in the realm of GDI, you'd wind up with 5 or
6
using statements per behavior, and it can get ugly very fast. If you were to
harden
the method, I certainly wouldn't go the path of the bloated using statement, and
would
probably rely on more appropriately specific try...finally blocks.

Agreed, the code gets quickly ugly with nested using statements. I use
sometimes a custom resource manager object that can dispose other disposable
objects to replace many nested 'usings' with just one:

using (ResourceManager resMan = new ResourceManager())
{
CompatibleDC cdc1 =
(CompatibleDC)resMan.Add(CompatibleDC.CreateDC(initialDC));
CompatibleDC cdc2 =
(CompatibleDC)resMan.Add(CompatibleDC.CreateDC(initialDC));
// both cdc1 and cdc2 will be Disposed automatically
}
You won't find very many using blocks in Microsoft's own code. In fact, you
won't find
very many try...catch blocks around many protected resources. The idea being
that the
managed object eventually cleans up the resource if failure does occur. This is
extensively
done in the Windows Forms API. Still up in the air as to what a best practice
might be
when you want to leverage code brevity, performance, and robust/reliable
operation all
at the same time.

I wonder how well that approach works in practice. For example, if a game
leaves e.g., a device context or DirectX interfaces for the garbage
collector to release whenever there is an exception, then the application
could be fragile on a system with plenty of free memory because then the GC
feels no need to run often.

Regards,
Sami
 
F

Frank Hileman

In fact, there is a very clean way to avoid nesting using statements:

using (Pen p = new Pen())
using (Brush b = new Brush())
{
.....
}

So use them as much as possible! They are simply great.

Regards,
Frank Hileman

check out VG.net: www.vgdotnet.com
Animated vector graphics system
Integrated Visual Studio .NET graphics editor
 
J

Justin Rogers

I would say you've only beautified your code here. You haven't changed the
performance characteristics or the code bloat that results from a using
statement. You also assume that all resource allocations happen up-front,
which is definitely note true in good old GDI where you need to allocate an
object, use it for a few calls, allocate a second object, store so you can
restore
a third object, etc...

I wish using was the end all answer.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top