If you are an experienced DirectX programmer chances are you used the DirectDraw API for 2D drawing. If not, here’s a quick summary: it was used for quickly copying parts of one image to another using binary operations (“bit blitting”). This was a quick and good enough method for paletted or low BPP images, but if you wanted to do more advanced operations like rotation or alpha blending you had to implement your own software solution. With the advent of 3D graphics cards, hardware 2D support was phased out in favor of 3D acceleration, and this set the demise of DirectDraw.
Many years later, Direct2D was born to provide true 2D graphics to the DirectX API, instead of drawing 3D shapes using an orthographic projection. It features GPU accelerated raster and vector graphics support, and an immediate mode with a syntax similar to XNA’s SpriteBatch.
In this sample we will add Direct2D drawing to the basic Direct3D app created in the previous tutorial, so get the source code from the repository in case you don’t have it. First we must add the DeviceCreationFlags.BgraSupport parameter to the creation of our Direct3D11.Device, since it’s the color format that Direct2D works with. The next step is to create the default Direct2D device from the existing DXGI.Device2 we initialized previously and use it to generate a Direct2D1.DeviceContext instance:
- SharpDX.Direct2D1.Device d2dDevice = new SharpDX.Direct2D1.Device(dxgiDevice2);
- d2dContext = new SharpDX.Direct2D1.DeviceContext(d2dDevice, SharpDX.Direct2D1.DeviceContextOptions.None);
Next up we must create a Bitmap1 object that will hold a reference to the backbuffer so Direct2D has a surface to draw on; to do this we have to specify its properties using a BitmapProperties1 declaration:
- BitmapProperties1 properties = newBitmapProperties1(newPixelFormat(SharpDX.DXGI.Format.B8G8R8A8_UNorm, SharpDX.Direct2D1.AlphaMode.Premultiplied),
- DisplayProperties.LogicalDpi, DisplayProperties.LogicalDpi, BitmapOptions.Target | BitmapOptions.CannotDraw);
This way we are going to create a bitmap in 32-bit BGRA format (that’s why we previously changed the DeviceCreationFlags!) with premultiplied alpha; BitmapOptions.Target allows the bitmap to be used as a device context target and BitmapOptions.CannotDraw forgives from using it when creating a brush or other drawing resource.
Next up we get the surface of the swap chain that hold the backbuffer and create the Bitmap1 using it as its source:
- Surface backBuffer = swapChain.GetBackBuffer<Surface>(0);
- d2dTarget = newBitmap1(d2dContext, backBuffer, properties);
And it’s ready to use it as our drawing target. Simply assign it to the Target property of the Direct2D1.DeviceContext you created previously and start drawing whatever your want to it.
To draw anything, we have to specify a brush that holds the properties of the stroke used to fill the figure. In this tutorial we use the three basic brushes:
- SolidColorBrush: just a solid, plain color.
- LinearGradientBrush: a linear gradient that interpolates between the GradientStop specified in a GradientStopCollection. We use a 2-stop gradient.
- RadialGradientBrush: another gradient brush, but this time in a radial distribution. You can specify its RadiusX and RadiusY properties independently to get a circle or an ellipse.
Below is the code for drawing, where we set the drawing target and tell the context to start drawing. After clearing it with a well-known color, we fill different geometric shapes with the brushes previously created. EndDraw tells Direct2D to flush the buffer and raster as needed.
- d2dContext.Target = d2dTarget;
- d2dContext.FillRectangle(new Vector2(50, 50, 450, 150), solidBrush);
- d2dContext.FillRoundedRectangle(new RoundedRectangle()
- Rect = new Vector2(50, 250, 450, 150),
- RadiusX = 10,
- RadiusY = 10
- }, linearGradientBrush);
- d2dContext.FillEllipse(new Ellipse(new Vector2(250, 525), 100, 100), radialGradientBrush);
And this is the colorful screen we will get:
As always, get the source code from the GitHub repo.