DirectX Part 2: Displaying, Like, Anything

So you just read the previous tutorial, you initialized Direct3D on your video card, and now you have pointers to these three items — a Swap Chain, a D3D Device, and a D3D Device Context. Well, in this tutorial, we’re going to display a color on screen! And it’s going to require using all three items.

Yes, that’s a lot of work for drawing a color to the screen — but it’s the groundwork for vertices and shaders and all that sexy stuff in the future. So let’s review.

D3D Device: This is an interface to the physical resources on your video card (i.e. GPU memory and processors). You’ll only have this one D3D Device to work with. Use it to allocate memory for textures, shaders, and the like.

D3D Device Context: This is an interface to the commands you want to give to the video card. As an analogy, the D3D Device is the company that builds the guitar, and the D3D Device Context is the musician that uses the guitar to play songs. You’ll only have this one context to work with (although pros use multiple contexts, known as “deferred” contexts). Things like passing in triangles, textures, and shader commands to generate a pretty picture are done through the D3D Device Context.

Swap Chain: This is an interface to the image you display on the monitor. So, it’ll contain a 1920×1080 texture that you draw on, to display on your 1920×1080 monitor. It’s called a swap chain because you’re actually drawing into 1 of 2 1920×1080 textures (you draw on one while the other is being displayed by the monitor), and then you swap those images when you’re done drawing. Since the swap chain directly controls what image is on the monitor, any time you want to see anything, you’ll use it.

Anyhow, let’s see how to draw a color to screen!

First, there’s some setup code you need to run once, after you initialize Direct3D:


ID3D11Texture2D* pBuffer = NULL;
m_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&pBuffer );

m_pd3dDevice->CreateRenderTargetView( pBuffer, NULL, &m_pRenderTargetView );
pBackBuffer->Release();

m_pDeviceContext->OMSetRenderTargets( 1, &m_pRenderTargetView, NULL );

Again, let’s take it step by step.


ID3D11Texture2D* pBuffer = NULL;
m_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&pBuffer );

This is simple enough: you’re making pBuffer point to the buffer (texture) at index 0 of the swap chain. You can’t grab the texture that’s currently displayed to screen, so this is the only texture you have to deal with.

That __uuidof( ID3D11Texture2D ) bit looks confusing, but it’s a fairly common setup here, so try to get comfortable with it! In order to future-proof the D3D APIs, rather than have GetBuffer(...) return a pointer to an ID3D11Texture2D (which will become obsolete come D3D 12), GetBuffer() writes out an empty void* pointer — but it guarantees that you can cast that pointer to whatever type you give in argument 2.


m_pd3dDevice->CreateRenderTargetView( pBuffer, NULL, &m_pRenderTargetView );
pBackBuffer->Release();

This code makes our D3D Device aware of the texture in our swap chain (which in turn will become the texture displayed in our monitor). Now, pBuffer and m_pRenderTargetView are two different interfaces to the same memory. They both modify the exact same image (which will eventually go to screen), but they expose different ways to modify it. Like an X-ray versus an infrared image of a human body — both offer different information about the same subject.

Calling Release() says that we don’t need to look at our frame texture in any of the methods exposed by pBackBuffer anymore. Our frame texture still exists in memory, but we can only view it through m_pRenderTargetView now. It’s a very good idea to Release() any handles you don’t need anymore.


m_pDeviceContext->OMSetRenderTargets( 1, &m_pRenderTargetView, NULL );

This says “Hey GPU! Render to this buffer”. Because we’re clever, we just made sure the buffer we’re rendering to is the one that gets displayed to screen — but it’s still not on screen yet! WE ARE SO CLOSE.

Fun fact: The “OM” stands for “Output Merger”, because it takes all the information you give the video card (which shaders/vertices/etc to use) and merges them all to create an output image.

AND NOW WE GET TO ACTUALLY DRAW. TO THE SCREEN.
In your update loop, or some loop that gets called every time you want to draw a new frame, include this code:


float ClearColor[4] = {
(float)rand() / RAND_MAX, //red
(float)rand() / RAND_MAX, //green
(float)rand() / RAND_MAX, //blue
1.0f //alpha
};
m_pDeviceContext->ClearRenderTargetView( m_pRenderTargetView, ClearColor );
m_pSwapChain->Present( 0, 0 );

Hey, simple!

Your ClearColor is a standard RGBA color — 0 is min, 1 is max. Now that we set our DeviceContext to write to the SwapChain back buffer, that clear command on m_pRenderTargetView clears our backbuffer to ClearColor, and we just tell SwapChain to present it.

THAT WAS EASY

GRAPHICS CARDS ARE SO FRIENDLY

Leave a Reply

Your email address will not be published.