I know that I have been saying that I will cover random terrain generation in my next post for several posts now, and if all goes well, that post will be published today or tomorrow. First, though, we will talk about Direct2D, and using it to draw a loading screen, which we will display while the terrain is being generated to give some indication of the progress of our terrain generation algorithm.
Direct2D is a new 2D rendering API from Microsoft built on top of DirectX 10 (or DirectX 11 in Windows 8). It provides functionality for rendering bitmaps, vector graphics and text in screen-space using hardware acceleration. It is positioned as a successor to the GDI rendering interface, and, so far as I can tell, the old DirectDraw API from earlier versions of DirectX. According to the documentation, it should be possible to use Direct2D to render 2D elements to a Direct3D 11 render target, although I have had some difficulties in actually getting that use-case to work. It does appear to work excellently for pure 2D rendering, say for menu or loading screens, with a simpler syntax than using Direct3D with an orthographic projection.
We will be adding Direct2D support to our base application class D3DApp, and use Direct2D to render a progress bar with some status text during our initialization stage, as we load and generate the Direct3D resources for our scene. Please note that the implementation presented here should only be used while there is no active Direct3D rendering occurring; due to my difficulties in getting Direct2D/Direct3D interoperation to work correctly, Direct2D will be using a different backbuffer than Direct3D, so interleaving Direct2D drawing with Direct3D rendering will result in some very noticeable flickering when the backbuffers switch.
The inspiration for this example comes from Chapter 5 of Carl Granberg’s Programming an RTS Game with Direct3D, where a similar Progress screen is implemented using DirectX9 and the fixed function pipeline. With the removal of the ID3DXFont interface from newer versions of DirectX, as well as the lack of the ability to clear just a portion of a DirectX11 DeviceContext’s render target, a straight conversion of that code would require some fairly heavy lifting to implement in Direct3D 11, and so we will be using Direct2D instead. The full code for this example can be found on my GitHub repository, at https://github.com/ericrrichards/dx11.git, and is implemented in the TerrainDemo and RandomTerrainDemo projects.
Adding Direct2D support to the D3DApp class
To use Direct2D, we need to create a Direct2D render target for our main application window. To create this resource, we will add a new function to our D3DApp.Init() function, InitDirect2D(), which we will execute after creating the main window in our InitMainWindow() function. It may be helpful to re-read my post on the D3DApp class, if any of this is unfamiliar.
private WindowRenderTarget _dxWRT;
internal WindowRenderTarget DxWrt {
get { return _dxWRT; }
}
private bool InitDirect2D() {
try {
var factory = new SlimDX.Direct2D.Factory(FactoryType.SingleThreaded);
_dxWRT = new WindowRenderTarget(factory, new WindowRenderTargetProperties() {
Handle = Window.Handle,
PixelSize = Window.ClientSize,
PresentOptions = PresentOptions.Immediately
});
Util.ReleaseCom(ref factory);
_progressUpdate = new ProgressUpdate(_dxWRT);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
return false;
}
return true;
}
Our first step is to create a Direct2D Factory interface. This Factory interface is similar to the Direct3D Factory that we used to create the DirectX11 SwapChain object; it provides a central point for initializing Direct2D resources. Because of the way that the SlimDX Direct2D wrapper is written, we only need this Factory to create the Direct2D render target; other Direct2D resources will be created using the render target, which maintains its own copy of the Factory, so we can dispose of the factory once we are done with it here.
The render target that we create will be bound to our applications main Window, which we specify using its Handle property. We also need to set the size of the Direct2D render target to match the client area of the application window. We expose the render target as an internal property of the D3DApp class, which will allow us to access the Direct2D render target from other classes in our Core engine library, using the D3DApp.GD3DApp static property.
After we create the Direct2D render target, we will use it to create an instance of our ProgressUpdate class, which we will cover next.
ProgressUpdate class
Our ProgressUpdate class will handle the creation and rendering of a simple progress bar, with some text indicating which stage of the current process we are executing. Our ProgressUpdate class will need a reference to the Direct2D render target, which we will pass into the constructor and store in the _rt member variable. We will need to create two Direct2D Brushes for our ProgressUpdate class: a solid black brush to render the outline of the progress bar with, and a solid green brush to use to fill the progress bar and render the status text. Using the Direct2D SolidColorBrush, we can consider a Brush to be simply a pixel color; Direct2D also provides the ability to use linear and radial gradient, as well as bitmap brushes, although we will not concern ourselves with these now. My suspicion is that a Direct2D brush is effectively a wrapper around a DirectX texture; this would jive with the need to recreate Brushes when the DirectX device is lost, and the options to change the wrap and texture transforms of the brush, which smacks loudly of the Direct3D texture address modes and texture coordinate transform matrices we have used earlier.
Besides the color brushes, we need to define the rectangular screen-space areas occupied by the progress bar, in the _borderBounds and _barBounds members. Our progress bar will be centered vertically, stretching across most of the screen. The border will be two pixels large on each side, so that it effectively outlines the green progress bar.
Lastly, to draw text, we need to create a DirectWrite TextFormat object, which we can think of as a font style. DirectWrite is a new text-rendering API, which like Direct2D, is meant to supplant GDI, with Unicode support and some other advanced features. The DirectWrite TextFormat class is quite similar to the older D3DX ID3DXFont interface that was used in Direct3D 9. To create a TextFormat object, we supply a DirectWrite Factory, a font face name, styles indicating the weight (boldness), style (italic), font spacing, pixel size of the letters and Unicode text locale of the desired font. We will also specify an additional property, TextAlignment.Center, which instructs the DirectWrite rendering engine to center the text drawn using this TextFormat within its containing rectangle. Finally, we define the rectangular area in which we will draw the status text for the progress bar, storing this rectangle in the _textRect member.
public class ProgressUpdate : DisposableClass {
private bool _disposed;
private SlimDX.Direct2D.Brush _brush;
private readonly WindowRenderTarget _rt;
private Rectangle _barBounds;
private SlimDX.Direct2D.Brush _clearColor;
private TextFormat _txtFormat;
private Rectangle _textRect;
private Factory _factory;
private Rectangle _borderBounds;
public ProgressUpdate(WindowRenderTarget rt1) {
_rt = rt1;
_brush = new SolidColorBrush(_rt, Color.Green);
_clearColor = new SolidColorBrush(_rt, Color.Black);
_borderBounds = new Rectangle(18, rt1.PixelSize.Height/2 - 2, rt1.PixelSize.Width - 36, 24);
_barBounds = new Rectangle(20, rt1.PixelSize.Height/2, rt1.PixelSize.Width-40, 20);
_factory = new Factory();
_txtFormat = new TextFormat(_factory, "Arial", FontWeight.Normal, FontStyle.Normal, FontStretch.Normal, 18, "en-us" ) {
TextAlignment = TextAlignment.Center
};
_textRect = new Rectangle(100, rt1.PixelSize.Height / 2-25, _rt.PixelSize.Width-200, 20);
}
}
Because the ProgressUpdate class needs to manage these DirectWrite and Direct2D resources, we will subclass our DisposableClass base class, and implement a Dispose method to clean up these references when the ProgressUpdate object is destroyed.
protected override void Dispose(bool disposing) {
if (!_disposed) {
if (disposing) {
Util.ReleaseCom(ref _brush);
Util.ReleaseCom(ref _clearColor);
Util.ReleaseCom(ref _txtFormat);
Util.ReleaseCom(ref _factory);
}
_disposed = true;
}
base.Dispose(disposing);
}
Drawing the ProgressUpdate
All Direct2D rendering calls need to occur between a RenderTarget.BeginDraw()/EndDraw() pair. We begin rendering our ProgressUpdate object by clearing the Direct2D render target to a background color (Color.Silver). Next, we draw the border of the progress bar by filling the _borderBounds rectangle with the _clearColor brush. Next, we create a rectangle to represent the progress represented by the prc parameter, by scaling the width of the _barBounds rectangle by the completion percentage value, and then filling this rectangle using the Direct2D RenderTarget.FillRectangle() method. Because this rectangle overlaps the border rectangle, we will be left with the black edge around the green progress bar.
Finally, we draw the text using the DrawText() method, which takes our message string, our TextFormat object, the text bounds rectangle, and our green Brush.
public void Draw(float prc, string msg) {
_rt.BeginDraw();
_rt.Transform = Matrix3x2.Identity;
_rt.Clear(Color.Silver);
_rt.FillRectangle(_clearColor, _borderBounds);
var r = new Rectangle(_barBounds.X, _barBounds.Y, (int) (_barBounds.Width*prc), _barBounds.Height);
_rt.FillRectangle(_brush, r);
_rt.DrawText(msg, _txtFormat, _textRect, _brush);
_rt.EndDraw();
}
Using the ProgressUpdate Class
To use the ProgressUpdate class, we will access the instance attached to our D3DApp class through the public property ProgressUpdate. We can either access this field directly, if we want to use the ProgressUpdate from our derived application class, or via the D3DApp.GD3DApp static instance, if we need the ProgressUpdate in another of our Core library classes. The screenshot above is the result of the following call to our ProgressUpdate.Draw function.
D3DApp.GD3DApp.ProgressUpdate.Draw(0.50f, "Smoothing terrain");
Next Time…
I swear, we will get to generating random terrains using Perlin Noise. We’ll also take a look at how we can add regular Windows Forms controls to our application window, which we will use to adjust the parameters used to create the random heightmap.
No comments :
Post a Comment