It’s been about two months since Windows 8 Consumer Preview is out and we, at Telerik, have been enjoying the new framework and all the features it offers. One of the aspects we've been exploring is the immediate mode rendering and what options the new runtime offers. XAML is in the core of WinRT (Windows Runtime) and it offers rich presentation capabilities but sometimes, especially on devices with limited hardware resources, we need to achieve even better performance to get up to the “fast and fluid” Metro applications' standard.
As I explained in this blog post, Direct2D is undoubtedly the best choice when it comes to presenting heavy visual scenes that are frequently updated. Another immediate-mode rendering option is the WriteableBitmap class where you can directly update pixels on the screen. One of the things that is surprisingly missing in WinRT is the ability to render a XAML UIElement on a WriteableBitmap instance. So, in case you need to use the WriteableBitmap class in your application, the only solution would be to have your own rendering engine that manipulates the pixels of the bitmap. Fortunately, the WriteableBitmapEx solution, by Rene Schulte, has already been ported to WinRT and it can serve your needs.
In this post I want to present yet another (a bit crazy) idea I have about outputting content within a WriteableBitmap instance. And that is – as the title states – integrating GDI+ within a C# Metro application and using it to present content.
I am a big fan of Windows Forms and GDI+ since the initial release of .NET framework. Its only disadvantage is probably the fact that it is a software rendering engine which does not exploit Display Adapter’s capabilities. Luckily Native API interoperability is enabled in .NET for Metro Applications and we can call unmanaged methods through the DllImport attribute. So, the idea is to create managed wrappers over some GDI+ methods and use them to draw content in an off-screen bitmap, whose pixels will then be passed to a WriteableBitmap instance.
If you are familiar with Windows Forms and GDI+ then the wrappers in the demo application will look pretty much familiar – Brush, Pen, Font, Graphics, etc. Here is an example of how the wrapper of a Brush-object looks like:
public
class
SolidBrush : NativeObject
{
private
Color color;
public
SolidBrush(Color color)
{
this
.color = color;
IntPtr handle;
int
result = SafeNativeMethods.GdiPlus.GdipCreateSolidFill(
this
.color.ToArgb(),
out
handle);
if
(result != 0)
{
throw
new
InvalidOperationException();
}
this
.NativePtr = handle;
}
public
Color Color
{
get
{
return
this
.color;
}
}
protected
override
void
DestroyNativePtr()
{
base
.DestroyNativePtr();
SafeNativeMethods.GdiPlus.IntGdipDeleteBrush(
this
.NativePtr);
}
}
Each GDI+ object is associated with a native pointer, which is unmanaged and we need to destroy it when the object is being disposed:
public
abstract
class
NativeObject : IDisposable
{
private
IntPtr nativePtr;
~NativeObject()
{
this
.Dispose(
false
);
}
protected
IntPtr NativePtr
{
get
{
return
this
.nativePtr;
}
set
{
this
.nativePtr = value;
}
}
public
void
Dispose()
{
this
.Dispose(
true
);
GC.SuppressFinalize(
this
);
}
protected
void
Dispose(
bool
disposing)
{
if
(
this
.NativePtr != IntPtr.Zero)
{
try
{
this
.DestroyNativePtr();
}
catch
{
throw
;
}
finally
{
this
.NativePtr = IntPtr.Zero;
}
}
}
protected
virtual
void
DestroyNativePtr()
{
}
}
We will initialize a Graphics object from a Bitmap and will draw a string on it. First, let’s create the needed GDI resources:
private
void
InitializeGdiResources()
{
this
.gdiBitmap =
new
Bitmap(PixelWidth, PixelHeight);
this
.gdiGraphics = Graphics.FromBitmap(
this
.gdiBitmap);
this
.gdiFont =
new
Font(
"Segoe UI"
, 32, GdiPlusToMetro.FontStyle.Bold | GdiPlusToMetro.FontStyle.Italic);
this
.gdiBrush =
new
SolidBrush(Colors.White);
this
.gdiStringFormat =
new
StringFormat(StringFormatFlags.NoClip);
this
.gdiPen =
new
Pen(Colors.White, 1);
this
.writeableBitmap =
new
WriteableBitmap(PixelWidth, PixelHeight);
this
.img.Source =
this
.writeableBitmap;
}
Time for some pixels to be output on the screen:
private
void
RenderText()
{
RectangleF rect =
new
RectangleF()
{
Width = PixelWidth,
Height = PixelHeight
};
this
.gdiGraphics.DrawString(DisplayText,
this
.gdiFont,
this
.gdiBrush, rect,
this
.gdiStringFormat);
}
Now that we have some content produced we need to update the pixels of our WriteableBitmap instance:
private
void
EndRender()
{
byte
[] pixels =
this
.gdiBitmap.GetPixels();
Stream stream =
this
.writeableBitmap.PixelBuffer.AsStream();
stream.Seek(0, SeekOrigin.Begin);
stream.Write(pixels, 0, pixels.Length);
this
.writeableBitmap.Invalidate();
}
I also performed the same test as in the Direct2D blog post – rendering a polyline, consisting of 5000 points, within a 1024*768 rectangle. The results are pretty impressive:
| FPS (frames-per-second) |
GDI+ (antialias off) | 27 |
GDI+ (antialias on) | 3-4 |
XAML Polyline | 3 |
A software-rendering engine, that GDI+ is, produces the same results (sometimes even better) than a hardware-accelerated counterpart, which the XAML Polyline is. I definitely think that MS should revisit the Polyline implementation – I really can’t understand why it is that slow.
I know that some (if not most) of you are wondering: “Why would I need GDI+ in my application when I have Direct2D”? Well, it is indeed up to you – this is just an experiment, which I (and hopefully you :)) find funny and interesting. With this experiment I learned that:
UPDATE:
GDI+ is not in the list of supported graphics APIs for developing Metro
Style applications, so this demo will fail the App Store verification
tool. So, if you want to have your application published on the store,
this approach is definitely not a solution :) Thanks to Pete_Brown and Morten Nielsen for pointing this out.
Well, that’s it, do not hesitate to grab the demo and give it a try. Please note that the set of wrapped GDI+ functionality is limited and its purpose is to fit the demo’s requirements. And have in mind that GDI+ will prevent you from publishing your app on the store.
Georgi worked at Progress Telerik to build a product that added the Progress value into the augmented and virtual reality development workflow.