DirectX 10/11 – Basic Shader Reflection – Automatic Input Layout Creation
This is gonna be a very brief post on a pretty simple but powerful tool for any hobby programmers tinkering with Direct3D. One of the most annoying things, at least for me, was always having to manually create input layouts for each different vertex shader I used in my hobby projects. I have now restarted work on my hobby game engine and have upgraded the renderer to DX11 and in doing so ripped out the DirectX effects framework. I’m actually planning on writing a post on moving from DX10 to DX11 but thats for another time. Right now lets get back to input layouts, if you can remember the input layout basically describes the memory layout of the input to the vertex shader program ( i.e. the input HLSL struct). In all my previous tutorials, we had to describe this input layout by hand and then create it using the compiled vertex shader for validation. We used to do this as follows:
const D3D10_INPUT_ELEMENT_DESC vertexInputLayout[] =
{
{ "ANCHOR", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
{ "DIMENSIONS", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D10_INPUT_PER_VERTEX_DATA, 0 },
{ "OPACITY", 0, DXGI_FORMAT_R32_FLOAT, 0, 16, D3D10_INPUT_PER_VERTEX_DATA, 0 }
};
const int vertexInputLayoutNumElements = sizeof(vertexInputLayout)/sizeof(vertexInputLayout[0]);
D3D10_PASS_DESC PassDesc;
pTechnique->GetPassByIndex( 0 )->GetDesc( &PassDesc );
if ( FAILED( pD3DDevice->CreateInputLayout( vertexInputLayout,
vertexInputLayoutNumElements,
PassDesc.pIAInputSignature,
PassDesc.IAInputSignatureSize,
&pVertexLayout ) ) )
{
return fatalError("Could not create Input Layout!");
}
The above sample is still using the effects framework but thats irrelevant. There is another problem with this approach with regards to flexibility, if you wish to swap shaders on the fly, they either all need to use the same vertex shader input layout or you need to somehow create all your input layouts in advance and then link them with the shaders you wish to load. Well, considering the fact that we use the compiled shader to actually validate the input layout then that means that the compiled shader has all the necessary information available for us to create the input layout. Basically we’re just going to reverse engineer the validation step to create an input layout based on the shader.
Now if we dont use the effects framework then we have to manually compile our shaders. I’m not going to go into much detail regarding this as the info is readily available in the SDK tutorials. Once you have compiled your shader you have the compiled shader blob which we can then use to create the final vertex shader program as shown below:
if( FAILED( D3DX11CompileFromFile( pFilename, NULL, NULL, pFunctionName, "vs_4_0", shaderFlags, NULL, NULL, &pCompiledShaderBlob, &pErrorBlob, NULL ) ) )
{
if ( pErrorBlob != NULL )
{
PRINT_ERROR_STRING( pErrorBlob->GetBufferPointer() );
pErrorBlob->Release();
}
return false;
}
HRESULT hr = pD3DDevice->CreateVertexShader( pCompiledShaderBlob->GetBufferPointer(), pCompiledShaderBlob->GetBufferSize(), NULL, &shader.pVertexShader);
We can very easily create our input layout using the following function:
HRESULT CreateInputLayoutDescFromVertexShaderSignature( ID3DBlob* pShaderBlob, ID3D11Device* pD3DDevice, ID3D11InputLayout** pInputLayout )
{
// Reflect shader info
ID3D11ShaderReflection* pVertexShaderReflection = NULL;
if ( FAILED( D3DReflect( pShaderBlob->GetBufferPointer(), pShaderBlob->GetBufferSize(), IID_ID3D11ShaderReflection, (void**) &pVertexShaderReflection ) ) )
{
return S_FALSE;
}
// Get shader info
D3D11_SHADER_DESC shaderDesc;
pVertexShaderReflection->GetDesc( &shaderDesc );
// Read input layout description from shader info
uint32 byteOffset = 0;
std::vector<D3D11_INPUT_ELEMENT_DESC> inputLayoutDesc;
for ( uint32 i=0; i< shaderDesc.InputParameters; i++ )
{
D3D11_SIGNATURE_PARAMETER_DESC paramDesc;
pVertexShaderReflection->GetInputParameterDesc(i, ¶mDesc );
// fill out input element desc
D3D11_INPUT_ELEMENT_DESC elementDesc;
elementDesc.SemanticName = paramDesc.SemanticName;
elementDesc.SemanticIndex = paramDesc.SemanticIndex;
elementDesc.InputSlot = 0;
elementDesc.AlignedByteOffset = byteOffset;
elementDesc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
elementDesc.InstanceDataStepRate = 0;
// determine DXGI format
if ( paramDesc.Mask == 1 )
{
if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_UINT32 ) elementDesc.Format = DXGI_FORMAT_R32_UINT;
else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_SINT32 ) elementDesc.Format = DXGI_FORMAT_R32_SINT;
else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32 ) elementDesc.Format = DXGI_FORMAT_R32_FLOAT;
byteOffset += 4;
}
else if ( paramDesc.Mask <= 3 )
{
if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_UINT32 ) elementDesc.Format = DXGI_FORMAT_R32G32_UINT;
else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_SINT32 ) elementDesc.Format = DXGI_FORMAT_R32G32_SINT;
else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32 ) elementDesc.Format = DXGI_FORMAT_R32G32_FLOAT;
byteOffset += 8;
}
else if ( paramDesc.Mask <= 7 )
{
if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_UINT32 ) elementDesc.Format = DXGI_FORMAT_R32G32B32_UINT;
else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_SINT32 ) elementDesc.Format = DXGI_FORMAT_R32G32B32_SINT;
else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32 ) elementDesc.Format = DXGI_FORMAT_R32G32B32_FLOAT;
byteOffset += 12;
}
else if ( paramDesc.Mask <= 15 )
{
if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_UINT32 ) elementDesc.Format = DXGI_FORMAT_R32G32B32A32_UINT;
else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_SINT32 ) elementDesc.Format = DXGI_FORMAT_R32G32B32A32_SINT;
else if ( paramDesc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32 ) elementDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
byteOffset += 16;
}
//save element desc
inputLayoutDesc.push_back(elementDesc);
}
// Try to create Input Layout
HRESULT hr = pD3DDevice->CreateInputLayout( &inputLayoutDesc[0], inputLayoutDesc.size(), pShaderBlob->GetBufferPointer(), pShaderBlob->GetBufferSize(), pInputLayout );
//Free allocation shader reflection memory
pVertexShaderReflection->Release();
return hr;
}
What this function does is peek inside the compiled shader using a shader reflection. A shader reflection is simply an interface for reading all the shader details at runtime. We first reflect the shader information using the D3DReflect function and then get the description of the shader using the refleciton interace. From this decsription we can see how many input elements the vertex shader takes and then for each one we can get it decsriptions. Using this data we can fill out our inputlayoutdesc structure. The above function is very very simple and is not robust to be able to handle any shader thrown at it. I quickly slapped it together just to get things up and running with the intention of extending it as needed in the future. I just figured its something, that at least to my knowledge, isnt readily known or mentioned and I figured just pointing it out would be useful for any hobbyist programmers :P
Oh btw, before I forget to actually use the shader reflection functions you need to include the D3DCompiler.h header and link against the D3Dcompiler.lib static library.
A Guide to Higher-Education for Aspiring Game Programmers
DISCLAIMER: I wrote this piece for a South African Game Development Magazine prior to leaving South Africa. It has been some time since I send the piece in for copy-editing and have heard nothing back regarding it so I’m simply going to post it here. The topic is a matter of some debate and the below article is simply my personal opinion! It was mentioned during the initial review by the game development magazine that certain sections of this article are rather inflammatory and we had agreed to remove them from the final piece. Seeing as this is my personal blog I figured I might as well post the entire un-edited version.
Once Again this is simply a personal opinion and should be taken as such!
Introduction
When asked to write an article for a local game dev magazine, I was initially apprehensive as writing is not exactly a strong point of mine. It was only once I realized that the article might actually benefit some prospective game programmers that it was well worth the effort. That being said trying to find a topic for the article proved quite challenging. My initial plan was to write a brief article describing the basics of vehicle steering and waypoint following. That idea got scrapped once I built a simple test bed and I realized that I don’t know nearly enough about physics of car motion to write an in-depth article on the topic.
While I was still deciding on a topic, huge discussions (read arguments) started popping up in various game dev communities I followed, all based around the same theme - education:
There seems to be quite a lot of debate on what prospective game programmers should study as well as what current college curricula should include. The most common questions still being asked by prospective game programmers are: “What do I need to study to become a game programmer?” and “What do I need to do to help me land a game industry job as a programmer?”. It is these questions that I wish to address in this article. Read the rest of this entry
A brief update…
I’ve been rather quiet the last 2 months. It’s taken a while to sort out a lot of things here and I’ve been quite busy with work but I will keep this blog going, I promise.
I finally managed to get a personal computer around a week ago and by PC I mean the most basic components needed just to get it up and running. Its gonna be a while before I complete the build and even longer still before I can watercool it like my last rig. As I mentioned I’ve been working at IO Interactive as a game programmer for the last two months. I’ve been working on Hitman: Absolution (the upcoming hitman game that blew everyone away at E3 – check out the trailer below).
I cant really talk about what I’ve been doing but what I can say is that the stuff I’ve been lucky enough to contribute to is amazing. From what I’ve seen Hitman:Absolution is gonna rock. I’ve ended up working with some really cool guys and I’ve learnt so much in the last two months. I’ve also re-learnt and remembered quite a ton of info that I had forgotten as well. I cant believe how rusty I’ve gotten over the course of completing my thesis. I really feel that I need to keep pushing myself to improve and learn. Speaking of my thesis, I’ve gotten back the initial feedback from my examiner and I have a few additions and corrections to make before I’ll be able to pass. I also have to finish writing the journal paper I started before I left South Africa. In addition to work, I’m gonna be quite busy the next few weeks so dont expect any major updates. After that I plan to just screw around with some hobby probjects and stupid ideas for a bit and hopefully, one of those I can turn into a new blog post.
Personal life wise, Denmark is pretty cool but at this point I’m rather homesick which I guess is only natural. Everything here is quite different and even though I’ve been here 2 months, it isnt even close to feeling like home yet. The internet is quite amazing here tho
Its been a huge culture shock for me as a lot of things that I took for granted arent available or popular locally. Things like skate brands, energy drinks or even silly things like white bread are quite hard to find. Though there is more to life than clothes and food, right?
I’ve also found myself riding a bicycle to work each day (around 6KM each day), combine that with Karate twice a week and I dont think I’ve ever had this much exercise in my life before. Damn it, I miss my car, hahaha! Anyways its midnight right now and I need to get to bed since I need to be up early tomorrow.
A wannabe game developer no more
So after all these years of hard work, I’ve finally managed to get into the game industry and into a well known AAA studio to boot. I’ve accepted an offer from IO Interactive (Hitman, Kane & Lynch) who are based in Copenhagen, Denmark. I will be moving to Denmark and starting at IOI in August!
This whole thing still feels a bit like a dream. I cant believe that I’ve finally managed to achieve what honestly felt like an impossible goal when I set it all those years ago. I guess it goes to show that with hard work anything is possible. As excited as I am, I cant help but feel a bit nervous. I’m a little fish getting thrown in the deep end. Then again, I dont think I’d want it any other way. I’ve been dying to improve and to learn and I’m finally going to be working with people that I can really learn a lot from and I honestly cant wait for that!!! To everyone that’s helped me along the way, you know who you are, thank you so much! I honestly dont think I’d have gotten this far without your encouragement and support.
On the downside, I dont know how active this blog will be once I start working but I will do my best to try and keep the content coming. Game dev blogs have been a huge help over the years and I would love to be able to pay the favor forward!
Pathfinding Thesis Complete
WOOT! My thesis, the bane of my existence for the last 2 years, is finally done! Its basically a review of the video game pathfinding field as well as presenting a novel grid map search technique: the spatial grid A*. The version linked below is the final draft that is being submitted to my faculty.
