Category Archives: Graphics

DirectX Part 4: Loading FBX Models

“Hey Ben”, you say, “these articles on DirectX are great!” (I know.) “But when you talked about vertices in your previous post, you had us hard-code an array of vertex information. I’m working with snooty artists who want to to use a graphical user interface to place thousands of triangles. Can I import a mesh from Maya?”

Fiiiiiiiiiiiiiiiiiiine.

Some AAA shit right here

Some AAA shit right here

Chances are, your artists save out to .FBX files. FBX is a proprietary format, owned by Autodesk (the guys who make Maya and Max — the tools 90% of game artists use to generate content). If you don’t have any tools/skills to create FBXes, you can download a shitty FBX of a squashed bell that I made (see a screenshot of bell in Maya viewport on the right).

Anyhow, so Autodesk are assholes, but they aren’t stupid, and they know nobody will use their formats if their formats aren’t easy for people to do stuff with. So they created an SDK for loading FBX files and giving you all the sexy data however you want it. You can download the SDK here and you can read the documentation here. It will give you libraries to compile against (probably located in C:\Program Files\Autodesk\FBX\FBX SDK\2014.1\lib\vs2012\x64\release, with headers in C:\Program Files\Autodesk\FBX\FBX SDK\2014.1\include) that parse FBXes into data. It’s free and you can use it in commercial products.

To get the FBX SDK set up in Visual Studio:

  • Download and install the SDK
  • In Visual Studio, right-click on your project > Properties > Configuration Properties > VC++ Directories > Include Directories, and add C:\Program Files\Autodesk\FBX\FBX SDK\2014.1\include . Make sure you do it across debug+release configurations.
  • Right-click on your project > Properties > Configuration Properties > Linker > General > Additional Library Directories, and add C:\Program Files\Autodesk\FBX\FBX SDK\2014.1\lib\vs2012\[x86/x64]\debug for debug configuration, C:\Program Files\Autodesk\FBX\FBX SDK\2014.1\lib\vs2012\[x86/x64]\release for release configuration
  • Right-click on your project > Properties > Configuration Properties > Linker > Input, and add libfbxsdk.lib under “Additional Dependencies” (across debug+release!)
  • Copy C:\Program Files\Autodesk\FBX\FBX SDK\2014.1\lib\vs2012\[x86/x64]\[debug/release]\libfbxsdk.dll into your project’s output directory for the appropriate x86/x64 and debug/release build.
  • You’ll probably want to add a reference to the .pdb containing debug information for the FBX SDK, C:\Program Files\Autodesk\FBX\FBX SDK\2014.1\lib\vs2012\x64\debug\libfbxsdk.pdb, via Tools > Options > Debugging > Symbols

Now that you’ve got access to the FBX SDK, you’re gonna write some code that looks like this:


#include <fbxsdk.h>
#include <vector>

struct MyVertex
{
   float pos[3];
};

FbxManager* g_pFbxSdkManager = nullptr;

HRESULT LoadFBX(std::vector* pOutVertexVector)
{
   if(g_pFbxSdkManager == nullptr)
   {
      g_pFbxSdkManager = FbxManager::Create();

      FbxIOSettings* pIOsettings = FbxIOSettings::Create(g_pFbxSdkManager, IOSROOT );
      g_pFbxSdkManager->SetIOSettings(pIOsettings);
   }

   FbxImporter* pImporter = FbxImporter::Create(g_pFbxSdkManager,"");
   FbxScene* pFbxScene = FbxScene::Create(g_pFbxSdkManager,"");

   bool bSuccess = pImporter->Initialize("C:\\MyPath\\MyModel.fbx", -1, g_pFbxSdkManager->GetIOSettings() );
   if(!bSuccess) return E_FAIL;

   bSuccess = pImporter->Import(pFbxScene);
   if(!bSuccess) return E_FAIL;

   pImporter->Destroy();

   FbxNode* pFbxRootNode = pFbxScene->GetRootNode();

   if(pFbxRootNode)
   {
      for(int i = 0; i < pFbxRootNode->GetChildCount(); i++)
      {
         FbxNode* pFbxChildNode = pFbxRootNode->GetChild(i);

         if(pFbxChildNode->GetNodeAttribute() == NULL)
            continue;

         FbxNodeAttribute::EType AttributeType = pFbxChildNode->GetNodeAttribute()->GetAttributeType();

         if(AttributeType != FbxNodeAttribute::eMesh)
            continue;

         FbxMesh* pMesh = (FbxMesh*) pFbxChildNode->GetNodeAttribute();

         FbxVector4* pVertices = pMesh->GetControlPoints();

         for (int j = 0; j < pMesh->GetPolygonCount(); j++)
         {
            int iNumVertices = pMesh->GetPolygonSize(j);
            assert( iNumVertices == 3 );

            for (int k = 0; k < iNumVertices; k++)             {                int iControlPointIndex = pMesh->GetPolygonVertex(j, k);

               MyVertex vertex;
               vertex.pos[0] = (float)pVertices[iControlPointIndex].mData[0];
               vertex.pos[1] = (float)pVertices[iControlPointIndex].mData[1];
               vertex.pos[2] = (float)pVertices[iControlPointIndex].mData[2];
               pOutVertexVector->push_back( vertex );
            }
         }

      }

   }
   return S_OK;
}

Although a lot of code, it’s not that bad! Here’s what it does:

  • Initialize the FBX loader and tell it what types of data to load (this is the FbxIOSettings, and you can use it to specifically load only meshes, only meshes + materials, etc.)
  • Load the file at C:\MyPath\MyModel.fbx and grab its root node (the “handle” to the FBX contents)
  • Go through each item in the FBX, and ignore any items that aren’t meshes
  • Make sure that every polygon in the mesh is a triangle! This is important because DirectX only draws triangles. Tell your artists to triangulate their meshes (or write something that auto-triangulates meshes here).
  • Copy over the data for the 3 vertices that make up the triangle into your pOutVertexVector

And once it’s all done, you can call LoadFBX(...), pass in a pointer to an empty vector, and you’ll come out with a vector of vertex positions!

Now, I’m leaving it to you to hook everything up. No code samples. It’s your chance to fly solo. Just remember these things!

  • Since you stored your vertex data in a std::vector, everything is contiguous memory. If you copy in myVertexVector.size() * sizeof(MyVertex) bytes starting from &myVertexVector[0] to the GPU, you’ll have a perfect array of positions!
  • Right now, your array holds nothing but vertex positions. Modify your pVertexLayout to match, or add the COLOR and SOME_MORE_DATA attributes from the previous example into your MyVertex and give them reasonable values.
  • And in your Render() loop, make sure that m_pDeviceContext->Draw(...) is told the correct amount of vertices to draw.
  • Don’t forget to change your vertex shader if you make any changes to the vertex layout!

Finally, if you don’t use the shitty-bell FBX attached up at the top of this post, take note. In its default state, DirectX will only draw polygons with an [X,Y] from [-1,-1] to [1,1] and with a Z from 0 to 1. Look through the vertices in your FBX and make sure everything is in that range!

And by the end of it all, here’s what you’ll see:

Wow, this image of a janky purple bell was worth all the hours I have spent following your tutorials, Ben!

Wow, Ben, this image of a janky purple bell was worth all the hours I have spent following your tutorials!

DirectX Part 3: Vertices and Shaders

Let’s get some real 3D going.

PART ONE: DEFINING YOUR DATA AND FUNCTIONS

Everything in 3D is just a series of triangles, and triangles are just a series of vertices. Vertices must have 3-dimensional positions — it’s the only absolutely required information DirectX 11 needs — but they can have any number of additional traits. Normal vectors, colors (for vertex coloring), lighting information (per-vertex lighting), metadata, etc. So, before anything happens we have to tell DX11 what our vertex layout looks like — that is, what information defines a given vertex:


D3D11_INPUT_ELEMENT_DESC pVertexLayout[] =
{
   { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
   { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
   { "SOME_MORE_DATA", 0, DXGI_FORMAT_R32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
UINT uiNumElements = ARRAYSIZE( pVertexLayout );

Most of these toggles are meaningless to beginners. The two important ones are semantic ("POSITION" and "SOME_MORE_DATA"), which is the variable name you’ll call on in shaders, and format (DXGI_FORMAT_R32G32B32_FLOAT and DXGI_FORMAT_R32_FLOAT), which defines how much / what type of data is associated with the named variable.

You can name your vertex variables anything you want, but some names (such as "POSITION") are reserved and must have certain formats associated with them.

In our pVertexLayout, the format for "COLOR" is 3 RGB floats — easy. "POSITION" is also 3 RGB floats — they’re actually going to be used as XYZ, the RGB nomenclature means nothing. "SOME_MORE_DATA" is just one float for playing with.

Next, we’ll create the actual vertices to draw. It’s just going to look like raw numbers — only the pVertexLayout lets the GPU understand how to read the data.


FLOAT pVertexArray[] =
{
   0.0f, 0.5f, 0.5f,   1.0f, 0.0f, 0.0f,   0.2f,
   0.5f, -0.5f, 0.5f,   0.0f, 1.0f, 0.0f,   0.0f,
   -0.5f, -0.5f, 0.5f,   0.0f, 0.0f, 1.0f,   -0.2f
};

So, this defines three vertices:

  • a vertex located at (0.0, 0.5, 0.5) that’s colored red (1, 0, 0) and has a SOME_MORE_DATA of 0.2
  • a vertex located at (0.5, -0.5, 0.5) that’s colored green (0, 1, 0) and has a SOME_MORE_DATA of 0.0
  • a vertex located at (-0.5, -0.5, 0.5) that’s colored blue (0, 0, 1) and has a SOME_MORE_DATA of -0.2

Next, we’ll write the shader file itself! This should be exciting for you, because this is sorta the heart of rendering. Create a new file and call it “shaders.hlsl” or something similar. Just preserve the “.hlsl” format. HLSL is a common shader-authoring language, and you’re about to write a hello-world in it. Here it is:


struct VS_INPUT
{
   float4 vPosition : POSITION;
   float3 vColor : COLOR;
   float OffsetX : SOME_MORE_DATA;
};

struct VS_OUTPUT
{
   float4 vPosition : SV_POSITION;
   float3 vColor : COLOR;
};

VS_OUTPUT SimpleVertexShader( VS_INPUT Input )
{
   VS_OUTPUT Output;
   Output.vPosition.x = Input.vPosition.x + Input.OffsetX;
   Output.vPosition.y = Input.vPosition.y;
   Output.vPosition.z = Input.vPosition.z;
   Output.vPosition.w = Input.vPosition.w;
   Output.vColor = Input.vColor;
   return Output;
}

float4 SimplePixelShader( VS_OUTPUT Input ) : SV_Target
{
   return float4( Input.vColor.r, Input.vColor.g, Input.vColor.b, 1.0 );
}

This is fairly simple, largely because DirectX does a lot of magic in the background. We define a vertex shader that receives a pre-defined VS_INPUT struct and outputs a VS_OUTPUT struct. That float myVal : SOMETHING construct means that we want myVal to magically receive the value SOMETHING that we define in our pVertexLayout description.

SOME_MORE_DATA is going to be placed in OffsetX in our VS_INPUT, and POSITION and COLOR will also be there. We’ll create a VS_OUTPUT, copy over position and color, and add our OffsetX to the position’s x value. (By the way, fun fact — instead of saying vPosition.{x,y,z,w}, you can say vPosition.{r,g,b,a} or vPosition.{[0],[1],[2],[3]} — they all compile the same. Use whichever nomenclature makes sense!)

That SV_POSITION in VS_OUTPUT means that it’s a SYSTEM VALUE. System values are hardcoded variables that get special treatment, and the ultimate vertex position is one such special variable.

Then, SimplePixelShader will magically receive that information and return a color to draw to screen (by writing it in SV_Target — the special variable that stores a final color for this pixel).

So that’s everything you need — you’ve defined what your vertices will look like, you’ve made some vertices, and you’ve written a shader to handle them and draw the triangle they form to screen. Now, you need to hook it all up.

PART TWO: MAKING THE GPU AWARE OF YOUR DATA AND FUNCTIONS

First, write a function to handle shader compiling. Note that the shaders.hlsl file we just wrote contains multiple shaders — a vertex shader and a pixel shader — and we’ll have to compile each separately.


#include <C:\Program Files (x86)\Windows Kits\8.0\Include\um\d3dcompiler.h>

HRESULT CompileShaderFromFile(const WCHAR* pFileURI, const CHAR* pShaderName, const CHAR* pShaderModelName, ID3DBlob** ppOutBlob)
{
   DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
   dwShaderFlags |= D3DCOMPILE_DEBUG;

   ID3DBlob* pErrorBlob = nullptr;

   HRESULT hr = D3DCompileFromFile( pFileURI, nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, pShaderName, pShaderModelName, dwShaderFlags, 0, ppOutBlob, &pErrorBlob );

   if( FAILED(hr) ) return hr;

   if( pErrorBlob ) pErrorBlob->Release();

   return S_OK;
}

A lot of confusing toggles — par for the course. Pass in the path to shaders.hlsl in pFileURI, the name of the shader in pShaderName (i.e. "SimpleVertexShader"), and the name of the shader model to compile against in pShaderModelName (use "vs_5_0" for compiling vertex shaders, and "ps_5_0" for pixel shaders). The ppOutBlob returned is a handle to the compiled shader. Close your eyes to everything else.

Let’s use it to set up our vertex shader.


ID3DBlob* pVertexShaderBlob = nullptr;
ID3D11InputLayout* pVertexLayout = nullptr;

CompileShaderFromFile( L"SimpleShaders.hlsl", "SimpleVertexShader", "vs_5_0", &pVertexShaderBlob );
m_pd3dDevice->CreateVertexShader( pVertexShaderBlob->GetBufferPointer(), pVertexShaderBlob->GetBufferSize(), nullptr, &m_pVertexShader );
m_pDeviceContext->VSSetShader( m_pVertexShader, NULL, 0 );

HRESULT hr = m_pd3dDevice->CreateInputLayout( pVertexLayout, uiNumElements, pVertexShaderBlob->GetBufferPointer(), pVertexShaderBlob->GetBufferSize(), &pVertexLayout );

pVertexShaderBlob->Release();

m_pDeviceContext->IASetInputLayout( pVertexLayout );

So we use our new function to compile the SimpleVertexShader, we create a handle to the compiled code (m_pVertexShader) that recognizes it as a vertex shader, and then we tell our D3DDeviceContext to use it. Cool!

Next, we call m_pd3dDevice->CreateInputLayout, to make the GPU aware of our pVertexLayout that we defined all the way at the top, and set it as our official vertex layout. Note that CreateInputLayout requires the vertex shader in addition to the vertex input layout — this is because it cross-checks the two to make sure pVertexLayout contains all the information m_pVertexShader asks for.

Next, we set up our pixel shader, almost the same as we set our vertex shader…


ID3DBlob* pPixelShaderBlob = nullptr;
CompileShaderFromFile( L"SimpleShaders.hlsl", "SimplePixelShader", "ps_5_0", &pPixelShaderBlob );
m_pd3dDevice->CreatePixelShader( pPixelShaderBlob->GetBufferPointer(), pPixelShaderBlob->GetBufferSize(), nullptr, &m_pPixelShader );

pPixelShaderBlob->Release();

m_pDeviceContext->PSSetShader( m_pPixelShader, NULL, 0 );

…And then we set our vertices…


D3D11_BUFFER_DESC bd;
ZeroMemory( &bd, sizeof(bd) );
bd.ByteWidth = sizeof(pVertexArray)
bd.Usage = D3D11_USAGE_DEFAULT;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0; //no CPU access necessary

D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory( &InitData, sizeof(InitData) );
InitData.pSysMem = pVertexArray; //Memory in CPU to copy in to GPU

ID3D11Buffer* pVertexBuffer;
m_pd3dDevice->CreateBuffer( &bd, &InitData, &pVertexBuffer );

// Set vertex buffer
UINT offset = 0;
UINT stride = 7 * sizeof(float); //how much each vertex takes up in memory -- the size of 7 floats, one each for position XYZ, color RGB, and our SOME_MORE_DATA
m_pDeviceContext->IASetVertexBuffers( 0, 1, &pVertexBuffer , &stride , &offset );

m_pDeviceContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

Which, despite the line count, isn’t actually that scary! Our D3D11_BUFFER_DESC just says we want to allocate some memory on the GPU with size equal to the size of pVertexArray, to be used as a vertex buffer — it’s default behavior in every other way. Our D3D11_SUBRESOURCE_DATA tells the GPU where our vertex data lives on the CPU. We pass both structures in to m_pd3dDevice->CreateBuffer to copy that data to the GPU, then tell the GPU to use it as our VertexBuffer!

And now, finally, everything is set up. In your Render loop, call m_pDeviceContext->Draw( 3, 0 ); to draw 3 vertices. And will you look at that.

It took hours, but damn it, that is your triangle.

triangle

DirectX Part 2.5: The Rendering Pipeline

Okay, so, we’ve got all our DirectX stuff set up to start rendering pretty pictures.

So it’s important, at this time, to talk about the pipeline that does the rendering. Unfortunately, it’s a beast:

That's a ten-step pipeline, yes it is

Some of these can be simplified or ignored for now — but it’s important you understand this stuff. This is the core of rendering.

INPUT-ASSEMBLER

This stage is where we assemble (as in, gather together) our inputs (as in, our vertices and textures and stuff). The input-assembler stage knows what information needs to be associated with which vertices and shaders (does every vertex have an associated color, if it needs one? A UV position? Are we loading the textures each shader needs?). This stage makes sure to get that information from the CPU, and it passes that information in to the GPU for processing.

VERTEX SHADER

This stage does operations on vertices, and vertices alone. It receives one vertex with associated data, and outputs one vertex with associated data. It’s totally possible you don’t want to affect the vertex at all, in which case your vertex shader will just pass data through, untouched. One of the most common operations in the vertex shader is skinning, or moving vertices to follow a skeleton doing character animations.

HULL SHADER + TESSELLATOR + DOMAIN SHADER

These are new for DirectX 11, and a bit advanced, so I’m summarizing all these stages at once. The vertex shader only allows one output vertex per input vertex — you can’t end up with more vertices than you passed in. However, generating vertices on-the-fly has turned out to be very useful for algorithms like dynamic level of detail. So these pipeline stages were created. They allow you to generate new vertices to pass to further stages. The tessellation stages specifically are designed to create vertices that “smooth” the paths shaped by other vertices. For basic projects, it’s common to not use these stages at all.

GEOMETRY SHADER

Also fairly new, introduced in DirectX 10. This stage does operations on primitives — or triangles (also lines and points, but usually triangles). It takes as input all the vertices to build the triangle (and possibly additional vertices indicating the area around that triangle), and can output any number of vertices (including zero). In the sense that it operates on sets of vertices and outputs not-necessarily-the-same-amount of vertices, it’s similar to the hull shader / tessellator / domain shader. However, the geometry shader is different, because it can output less vertices than it received, and it allows you to create vertices anywhere, whereas tessellation can only create vertices along the path shaped by other vertices. Before DirectX 11, tessellation was done in the geometry shader, but because it was such a common use case, DX11 moved tessellation into its own special purpose (and significantly faster) pipeline stages. For basic projects, it’s common to not use this at all.

STREAM OUTPUT

After running the geometry shader, you have the full set of vertices you want to operate on. The stream-output stage allows you to redirect all the vertices back into the input-assembler stage for a second pass, or copy them out to CPU for further processing.  This stage is optional, and will not be used if you only need one pass to generate your vertices and don’t need the CPU to know what those vertices are (which, again, is probably the case for basic projects).

RASTERIZER

The GPU outputs a 1920×1080 (or whatever size) RGB image, but right now it only has a bunch of 3d vertex data. The rasterizer bridges that gap. It takes as input the entire set of triangle positions in the scene, information about where the camera is positioned and where it’s looking, and the size of the image to output. It then determines which triangles the camera would “see”, and which pixels they take up. This sounds easy, but is actually hard.

PIXEL SHADER

This stage works on each individual pixel of your image, and does things like texturing and lighting. Essentially, it’s where everything is made to look pretty. It receives input data about the vertices that compose the primitive that this pixel “sees”, interpolated to match the position of this pixel on the primitive itself. It then performs operations (such as “look up this corresponding pixel in a texture” or “light this pixel as though it were 38 degrees tilted and 3 meters away from an orange light”), and outputs per-pixel data — most notably the color of the pixel. Arguably, this is the most important stage of the entire DirectX pipeline, because this is where most an image’s prettiness comes from.

OUTPUT MERGER

Although it seems like you’re done at this point, you can’t just take the pixel shader output and arrange it all to make an image. It’s possible for the pixel shader to compute multiple output colors for a single pixel — for instance, if a pixel “sees” an opaque object and a semi-transparent object in front of it, or if the pixel shader was given a far-away object to compute colors for but it later turned out that pixel’s “sight” was blocked by a closer object. This is a problem, since our final rendered frame can only contain one color per pixel. So that’s where the output merger stage comes in. You tell it how to handle differences in depth and alpha values (as well as stencil values, which you can set for fancy rendering tricks) between two outputs of the same pixel. It follows those rules and creates the one final image to draw to screen.

And there you go! It’s a lot, but this is all the steps the GPU takes to go from raw data to an output image. There is no magic, just these steps.

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

DirectX Part 1: The “Initialize” Function

GUYS. I hate to break it to you, but normal programming on CPUs is for wimps. GPU programming is where you have to go to find fast cars / hot ladies. BUT THERE’S A PROBLEM: it’s hella hard to program for GPUs! Well. Until now, when I explain it all to you.

Since the dawn of computing, every line of code has run on your CPU, by default. Graphics cards only became a thing in the mid-80s, and even to this day, they aren’t really “standard” parts of a computer. What this means is that everything you want to run on a GPU has to be wrapped in APIs that very specifically say, “I want this to run on a GPU and it will be my fault if this computer has no GPU to run on”.

The two most common APIs to allow people to run code on graphics cards for the purpose of rendering pretty 3D scenes are DirectX and OpenGL. This is the first of many articles focusing on DirectX, although many of the concepts apply to OpenGL. Using the GPU to do non-rendering stuff, like cracking passwords, isn’t really DirectX’s strength and we aren’t gonna focus on it.

So, the entire scope of this article is the DirectX11 Initialize function. That’s a pretty small scope, but it’s a dense function, and it provides a great overview of what the API designers think you should care about re: your video card.

Anyhow, the Initialize function is called D3D11CreateDeviceAndSwapChain . More specifically, it’s:

HRESULT D3D11CreateDeviceAndSwapChain(
   _In_ IDXGIAdapter *pAdapter,
   _In_ D3D_DRIVER_TYPE DriverType,
   _In_ HMODULE Software,
   _In_ UINT Flags,
   _In_ const D3D_FEATURE_LEVEL *pFeatureLevels,
   _In_ UINT FeatureLevels,
   _In_ UINT SDKVersion,
   _In_ const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,
   _Out_ IDXGISwapChain **ppSwapChain,
   _Out_ ID3D11Device **ppDevice,
   _Out_ D3D_FEATURE_LEVEL *pFeatureLevel,
   _Out_ ID3D11DeviceContext **ppImmediateContext
);

( _In_ and _Out_ are compile-time hints indicating whether a function parameter is input or output. _In_ parameters can only be input (read from but not written to), and _Out_ parameters can only be output (written to but not read from) ).

ANYHOW WOW THAT’S A LOT OF PARAMETERS

The number of parameters is representative of DirectX’s overall API design — a design that assumes GPU programming is only for super-hardcore programmers. This is a self-fulfilling prophecy — the scariness of the DirectX API keeps everyone away who isn’t hardcore enough to handle reams of documentation — but it sucks, because GPUs are pretty mainstream now and many programmers could really add GPU skills to their arsenal. But that’s a topic for another time! Let’s cover these variables one-by-one.

_In_ IDXGIAdapter *pAdapter: “IDXGI” stands for “Interface to a DirectX Graphics Infrastructure”. Basically, a DirectX Graphics Infrastructure Adapter is anything that can handle displaying graphics. This includes video cards, integrated graphics chips on CPUs, or a CPU software renderer — if it can output images to screen, it’s a DXGI adapter. My dual-nVidia GTX560 machine has 3 adapters: one for each of my GTX560 cards, and one for the Microsoft Basic Render Driver that Microsoft falls back on if there are no video cards available. The value you pass in here will be the video card that DirectX gives you access to; pass in nullptr for the default video card (usually a pretty good guess).

_In_ D3D_DRIVER_TYPE DriverType: This is one of a pre-defined list of values, which lets you specify whether you want any commands passed through DirectX to go to the GPU hardware, or if you want to actually do a fake-out and emulate your commands in software. Chances are, if you’re using the graphics card, it’s because you want to make things go fast. So you want to use D3D_DRIVER_TYPE_HARDWARE. If you’re doing tricky enough stuff to warrant using another driver type, chances are, you’ll know about it.

_In_ HMODULE Software: This is only used if your D3D_DRIVER_TYPE above is “Software”, because this is a pointer to your software implementation of Direct3D that you want to use to debug stuff. Otherwise, it can be NULL. Again, if you’re doing tricky enough stuff that you need to pass a non-NULL value here, you’ll know about it.

_In_ UINT Flags: This is where you specify a bunch of low-level flags to modify behavior. If you’re only rendering from a single thread, or if you’re using the GPU for tasks that take lots of time to execute (i.e. cracking passwords instead of playing video games), or you want to debug stuff, there’s flags here that you may want to play with.

_In_ const D3D_FEATURE_LEVEL *pFeatureLevels: This is a pointer to the first element of an array of D3D_FEATURE_LEVEL items. “Feature Level” means “version of DirectX” — such as 9.1, 10.0, or 11.0 . In general, you want the highest feature level you can get, and newer video cards offer support for newer versions of DirectX. You’ll probably pass in an array like D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1 }; which means “Try for DX11.0 if possible, but if that fails, give me 10.1”. Alternatively, you can just pass in nullptr and it’ll set up whatever the highest DirectX feature level is that your adapter supports.

_In_ UINT FeatureLevels: The number of features in the above pFeatureLevels array. lol at the old-school C in this API, instead of just passing a vector or something you pass in a pointer-to-array-start and length-of-array. If you pass nullptr for pFeatureLevels, just set this to 0.

_In_ UINT SDKVersion: Just pass in D3D11_SDK_VERSION. That’s seriously what the documentation tells you to do. No choice, no explanation. Thanks, API designers.

_In_ const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc: Okay, so what is a swap chain? Well, it’s a collection of frame buffers (frame buffers being the 1920×1080 RGB image that gets presented to your 1920×1080 monitor — a buffer containing the entire frame to display on-screen). Anything you want displayed gets drawn to one of the frames in this collection, and then once you’re done drawing to that frame it gets displayed on-screen and you start drawing to the next frame buffer in the collection. This structure tells DirectX things like how many frame buffers to create, how big they should be, and how they’ll be used.

_Out_ IDXGISwapChain **ppSwapChain: This is a pointer to the output swap chain generated by DirectX. Every time you want to display your drawn image onto the monitor, you’ll have to call pSwapChain->Present(...). So hold on to this!

_Out_ ID3D11Device **ppDevice: This is a representation of DirectX running on your graphics card. Things like memory allocation and status checks relating to the whole GPU are done through the D3DDevice. Hold on to this, too!

_Out_ D3D_FEATURE_LEVEL *pFeatureLevel: This just confirms the version of DirectX that your graphics card is able to run. Quit out if it’s lower than you want it to be.

_Out_ ID3D11DeviceContext **ppImmediateContext: This is the thing you actually care about — the “context” to the D3DDevice (as in, this is how you use the DirectX wrapper that is now lying on top of your video card). The “Immediate” refers to the fact that any command you send through here gets executed on the graphics card immediately. You use the device context to set shaders and draw 3D geometry, which is pretty much the meat of rendering.

So you came out of this with your swap chain, your D3DDevice, and your D3DDeviceContext. Cool! Next time, we’ll look at how to start using these items to — god forbid — draw a purple box on screen.