Learning DirectX and C++: Step 2, Initialise DirectX

In order to make the most simple use of DirectX we must set up the required infrastructure. This is as follows:

  • A swap chain (at least two areas of memory, one for drawing into, one for displaying to the screen).
  • A RenderTargetView (a reference to the area of memory from the swap chain for drawing into).
  • A Depth/Stencil buffer, and a view onto it. (Used to help decided whether any given pixel should be drawn).
  • Bind the RT (RenderTarget) and Depth/Stencil views to an Output Merger stage.
  • Set the Viewport (a sub-rectangle of the BackBuffer that we will draw to).

Naturally after we have set up this infrastructure we have repeatedly draw some sort of scene; we will get to that after we have discussed the topics above.

The Swap Chain

The Swap Chain is an object which manages a number of frame buffers for us, typically two or more. One of the frame buffers (that is, an area of memory which can contain an image we generate) is for us to draw into, another is what is currently displayed on screen, and if needed, one can introduce more frame buffers, but we won’t look at that for now. We’re going to create a Swap Chain with two frame buffers in it, and it will allow us to “swap” them, so that after we have finished drawing, we can display what we have just drawn on the screen and begin drawing the next frame in the frame buffer that was just on screen.

What I have done, is added a couple of pointers, and a member function to the protected section of my Form class:

                // We do our directX initialisation in here
		virtual bool initialiseDirect3d();

                ID3D10Device	*device;
		IDXGISwapChain	*swapChain;

The definition of the initialiseDirect3d() method is as follows:

	DXGI_SWAP_CHAIN_DESC scDesc;
	scDesc.BufferDesc.Width = this->width;
	scDesc.BufferDesc.Height = this->height;
	scDesc.BufferDesc.RefreshRate.Numerator = 60;
	scDesc.BufferDesc.RefreshRate.Denominator = 1;
	scDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	scDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	scDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

	// Set it up so there is no multi-sampling on the back-buffer
	scDesc.SampleDesc.Count = 1;
	scDesc.SampleDesc.Quality = 0;

	scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	scDesc.BufferCount = 1;
	scDesc.OutputWindow = this->formWindowHandle;
	scDesc.Windowed = true;
	scDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	scDesc.Flags = 0;

	UINT createDeviceFlags = 0;
#if defined(DEBUG)  || defined(_DEBUG)
	createDeviceFlags |= D3D10_CREATE_DEVICE_DEBUG;
#endif
	HRESULT createSuccess = D3D10CreateDeviceAndSwapChain(0, D3D10_DRIVER_TYPE_HARDWARE, 0, createDeviceFlags, D3D10_SDK_VERSION, &scDesc, &(this->swapChain), &(this->device));

	if ( FAILED( createSuccess ) )
	{
		MessageBox(0, L"CreateDeviceAndSwapChain FAILED", 0, 0);
		return false;
	}

	return true;

Finally I have added a call to initialiseDirect3d from our Run member function, just after the calls to ShowWindow() and UpdateWindow().

The Render Target View

In DirectX, resources (such as the backbuffer) are bound to pipeline stages (such as the output merge stage), but not directly. First we must create a View of that resource, and that view will be bound to the pipeline stage. The backbuffer was created when we created the D3D10 Device and the Swap Chain, now we need to create a view of it that we can bind to pipeline stages.

In order to do this, we must get a pointer to the backbuffer as an ID3D10Texture2D instance, and then create an ID3D10RenderTargetView. We do not need to keep the ID3D10Texture2D pointer after we have used it, but we do need to keep our pointer to the ID3D10RenderTargetView, so we’ll make it a member of our class, in the protected section.

		ID3D10RenderTargetView	*renderTargetView;

To our initialiseDirect3d() member function we add a few lines of code to get the back buffer, create the render target view, and release the reference to the backbuffer afterwards:

	// Get a reference to the back buffer
        ID3D10Texture2D *backBuffer;
	this->swapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), reinterpret_cast(&backBuffer));

        // Create the RT View
	this->device->CreateRenderTargetView(backBuffer, 0, &(this->renderTargetView));

	// Release the reference to the backbuffer.
        if ( backBuffer )
	{
		backBuffer->Release();
	}
	backBuffer = NULL;

Just a few more steps to go…

The Depth/Stencil Buffer

The Depth/Stencil buffer is another area of memory, which is used to inform the Depth and Stencil tests respectively. These tests help the GPU know whether or not to overwrite a given pixel in the back buffer. We’ll probably talk about them in more detail later. We’ll need to add two more pointers as members to our Form class: one for the actual memory buffer (we’ll add it as a ID3D10Texture2D), and one for the view onto that buffer (we’ll add it as an ID3D10DepthStencilView).

		ID3D10Texture2D			*depthStencilBuffer;
		ID3D10DepthStencilView	*depthStencilView;

Next we add some code to our initialiseDirect3d member function to create the buffer and the view.
First we describe the buffer:

	D3D10_TEXTURE2D_DESC depthStencilDesc;
	depthStencilDesc.Width = this->width;
	depthStencilDesc.Height = this->height;
	depthStencilDesc.MipLevels = 1;
	depthStencilDesc.ArraySize = 1;
	depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthStencilDesc.SampleDesc.Count = 1;
	depthStencilDesc.SampleDesc.Quality = 0;
	depthStencilDesc.Usage = D3D10_USAGE_DEFAULT;
	depthStencilDesc.BindFlags = D3D10_BIND_DEPTH_STENCIL;
	depthStencilDesc.CPUAccessFlags = 0;
	depthStencilDesc.MiscFlags = 0;

Note that the SampleDesc field values must be the same as for the render target.

Next we create the buffer and view:

	createSuccess = this->device->CreateTexture2D(&depthStencilDesc, 0, &(this->depthStencilBuffer));
	if ( FAILED( createSuccess ) )
	{
		MessageBox(0, L"CreateTexture2D FAILED", 0, 0);
		return false;
	}

	createSuccess = this->device->CreateDepthStencilView(this->depthStencilBuffer, 0, &(this->depthStencilView));
	if ( FAILED( createSuccess ) )
	{
		MessageBox(0, L"CreateDepthStencilView FAILED", 0, 0);
		return false;
	}

Finally, we need to bind both the RT view and the Depth/Stencil view to the Output Merger pipeline stage. Effectively this tells the GPU which render target, and which depth/stencil buffer to use for now.

	this->device->OMSetRenderTargets(1, &(this->renderTargetView), &(this->depthStencilView));

The Viewport

The last bit of infrastructure we must set up is the Viewport. This is effectively a window within our back buffer, to which drawing is constrained. We’ll use the whole backbuffer for now. But you could use a smaller viewport for effects like picture-in-picture, etc. Simply, we describe the viewport, and then set it; we do not need to keep it around or anything, as it is really part of the state of the GPU.

	D3D10_VIEWPORT viewPort;
	viewPort.TopLeftX = 0;
	viewPort.TopLeftY = 0;
	viewPort.Width = this->width;
	viewPort.Height = this->height;
	viewPort.MinDepth = 0.0f;
	viewPort.MaxDepth = 1.0f;

	this->device->RSSetViewports(1, &vp);

Let’s do something with it all…

Right, now that we’ve done all this hard work, let’s do something with it… how about painting the window blue you ask? Great, that’s just what I was thinking. We’ll introduce a new member function to our class called Draw, and we’ll call it from the message handler, in the part of the if statement that we have set aside for consuming the time that we don’t spend handing messages.

All we do is clear the render target with a specific colour, clear the depth and stencil buffers, and then tell the swap chain to Present, which makes it swap the back buffer with the one currently displayed on screen.

In the protected section:

		virtual void draw();

And the method definition:

void Form::draw()
{
	float color[4] = { 0.0f, 0.125f, 0.6f, 1.0f };
	this->device->ClearRenderTargetView(this->renderTargetView, color );

	this->device->ClearDepthStencilView( this->depthStencilView, D3D10_CLEAR_DEPTH|D3D10_CLEAR_STENCIL, 1.0f, 0 );

	this->swapChain->Present(0, 0);
}

And that’s really all there is to it. Once again, I’ve uploaded the code, as a zip file this time. Also, don’t forget to link against d3d10.lib and d3dx10.lib.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.