Tuesday, January 27, 2009

Larrabee Update - More Ray Tracing

So Daniel Pohl has updated his ray-traced version of Quake Wars, with a link to an article here and also discussed here.And of course it has been slashdotted here.

It looks good, like all ray-tracing does. A couple things ray-tracing does real well, that polygon renderers can struggle with are:
1)shadows
2)reflections
3)water with refraction and reflection
4)glass with refraction and reflection

It is much easier to add these features to a ray-traced engine than a polygon engine. Reflections and refractions are just modifications to the path of a ray. Shadows are just another ray. Even collision detection, with some cleverness, can share work with the tracing for rendering.

Ray-tracing on LRB is going to be very interesting.

Monday, January 26, 2009

End of an Era Part II: Links and Speculation

So we got a 2nd press release, confirming Aces shutdown here, and the news was picked up the the Seattle Pi here confirming no more TrainSim.

I believe that is the only official confirmation of a particular product group being axed, at least so far. The fact there was so much activity about Aces and Flight Sim that a 2nd press release was required should strike someone at MS that the product was popular, but that would require critical thinking and I dont want to presume.

The rest is my informed speculation, so lets be clear that is all it is. Its pretty reasonably informed given I only left 4 months ago, but I did leave and wasn't there on Thursday so this is an amalgamation of what I knew and what I have heard from multiple sources.

Aces did not get shuttered because FSX sold poorly. To the contrary, FSX sold decently, as this post recounted. And this post places its sales as north of 1m units.

Aces had "internal issues" at the wrong time. They got caught in a game of musical chairs without a chair.

What do I mean by that?

Well, they had issues in trying to develop 3 releases simultaneously ( TS2, ESP2, FS11 ) with current resources and had a schedule change as a result. Note the slip in TS2 delivery discussed in this thread.

This was actually 2 slips, first to from Holiday 2008 to Spring 2009 and then to Holidays 2009 which is what the TSInsider site says now for the expected ship date.

Then ESP2 started consuming resources and the headcount ask went up.

The head count freeze that came down last August in MGS as part of the exec changes did not help from a project management angle, nixing adding more resources. The switch in MGS exec management after Don Matrick came in ( Shane Kim out, Phil Spencer in ) also didnt help from a relationship angle as Shane had been there for a while and had a relationship with Aces management. The exec switch in MGS was reported on gamedaily.

Net-net this meant MGS exec management lost confidence in Aces studio management because of the schedule changes and asking for more heads. Then the dictate to cut heads came down. So do you keep the team that missed their date and asked for more resources, or keep the teams that have been quietly executing? Thus Aces were easier to tag as "it"; eg the squeeky wheel got 'greased'.

Its really just a case of bad timing for the studio, given another year they would have been executing smoothly and had releases imminent. But the economy did not give them that time.

What does this mean for the future?

For the Flight franchise, P-12C ( Paul Lange, lead designer for FS 11 ) replied on this thread that the team asked if they could buy the IP and spin out, and was refused. And later in the thread he indicates the challenge this presents since it is unclear what the future is since the longer the code sits the less useful it is.

For the Trains franchise, Stone Lance ( Steve Heister, Core graphics programmer ) confirms TS2 is gone. And that was before the PI confirmation.

ESP is similarly gone.

We are all waiting to hear what this vague "future opportunity" is for both FS and ESP. And on that thin thread the future of the franchise depends. Until we hear what that amounts to, we are unable to gauge the damage. But it should be crystal clear this is not good for the franchise as we know it.

If I hear enough of what the "future opportunity" is to make a coherent post and present some more informed speculation, I will do so.

EDIT: ComputerWorld picked this up here, that is sorta mainstream isnt it?
EDIT2: FSInsider has been updated here, but TSInsider and ESP have not, yet. No new news.
EDIT3:Even more mainstream, this gets picked up by Atlantic Monthly, one of the editors is a fan.

Friday, January 23, 2009

End of an Era - Aces Studio and Flight Sim shuttered

Yesterday, amidst the stories of MS layoffs, Gamasutra broke the story that Aces, developers of Flight Sim, suffered heavy cuts.

Steve Lacey blogged about it here.

Now MS confirms the shutttering of the studio here.

I can confirm everyone except 6 people got RIFed. The 6 are to perform an orderly shutdown and service existing contracts ( likely ESP ). There were something like 100 people in the studio across FS,TS,ESP, and Core.

There was an all-hands meeting, then people got 1 of 2 emails from HR:
1) your last day is Jan 23 ( 24 hrs notice ) and your badge and email will stop working at 7:00 pm on the 23rd.
2) you have 60 days to find a job, your badge and email will work until Mar 23. If you find a job great, if not that is your last day.

There are various posts by current team members on various forums that confirm the RIF.

The outlook for Trains2 is very much in doubt.

There is some word that a much smaller group ( as low as 20 people ) may get started in the future to do something with the FS name, but we will have to see what that means as there are no details yet. Some small ray of hope, perhaps.

ESP may also continue outside of MGS, albeit at a much reduced scale. That also we will have to wait on details.

This is a sad event. My heart goes out to all affected.

EDIT: This thread on UkTrainSim.com has TS2 members confirming ( Stone Lance, Portergraphic ).
EDIT: This thread on Sim-Outhouse.com has FS11 members confirming ( Gibbage, P-12C ).
EDIT: Many Aces team members have updated their LinkedIn status. Check my out to find them.
EDIT2:This thead on Sim-Outhouse.com,page 8 halfway down, confirms by P-12C that MS will not sell the IP, and on page 9 that it is unclear what the future is since the longer the code sits the less useful it is. We'll just have to wait to see what the vague "future opportunity" amounts to.

Saturday, January 3, 2009

Maze Sample : Article 4 - D3D10Maze-Maze

Happy New Year!

I am just about finished with the D3D9 and D3D10 code, and that covers the first eight articles.

And I am working out how to deliver the Larrabee “Shared Surfaces” and Larrabee “Native” versions.

If you haven’t been keeping up “Shared Surfaces” is a Larrabee coding style where some code is moved over to Larrabee and some remains under D3D control, and the management of rendering buffers and Present remains with D3D. And “Native”, it should be obvious, is all Larrabee all the time. With all of those done, this series will be an excellent teaching tool in terms of compare and contrast.

Notes on the Article Series

Given some of the comments I am getting so far, I want to make clear the progression of the article series and the purpose of each sample on the way.

I have posted three articles so far:
· Intro - introduces article series and its purpose, and use of DXUT
· Base - provides empty shell DXUT app that works in both D3D9 and D3D10
· Skybox - adds simple skybox rendering

The purpose of next several articles is:
· Maze – generate the maze and render it
· Solve – provide simple solver and attach it to the self camera
· Camera – provide better camera support
· Char – provide animated character at solver location
· TextureLoad – embellishments (texture load, adverts, tessellation)

With that in mind, please don’t expect a feature before its time.

I am striving to make the code be uniform across all articles but I am not being 100% strict about it, for instance now the sky camera hot key is “k” instead of “s” so that W-A-S-D camera controls work. So if small corrections are made over time, don’t get upset.

One other adjustment I am making is to only show the new code in the callbacks in the interest of making the articles a bit shorter. I’ll use ellipses (…) to indicate where code has been elided.

I have found that the complexity factor of the articles mean it is taking me a bit longer to work through these, for instance this article is on the order of 30 pages in Word. So expect an article every 2-4 weeks. If I get it done faster than that, great my batting average goes up. As work on the Larrabee titles heats up, my batting average may even go down so fair warning.

Now we need some way to represent the maze, then to generate it, then to generate geometry that represents the maze, and then textures and rendering code. On to the code.

Maze Internal Representation

We need to decide how the maze is defined and represented. First, each wall will be defined as a quad or two triangles as per Image 1 below:

The maze will be generated on a regular grid, with width==height, up to a maximum of 16x16, or 256 “cells”. Image 2 below shows a top view of the regular grid:



Initially, each cell will be generated with all four walls, as shown in Image 3:


And then we will “knock down” walls as appropriate to generate the connected maze. The maze floor and maze roof will be generated and rendered separately.

Now that we have that sorted, we will add a new folder to the project, Maze, that will contain our maze-specific code as follows:

In cmaze.h we will use bit flags to represent the presence of a wall, and the angles for turning, as follows:

const BYTE MAZE_WALL_NORTH = (1<<0);>
const BYTE MAZE_WALL_EAST = (1<<1);>
const BYTE MAZE_WALL_SOUTH = (1<<2);>
const BYTE MAZE_WALL_WEST = (1<<3);>
const BYTE MAZE_WALL_ALL = MAZE_WALL_NORTH MAZE_WALL_EAST MAZE_WALL_SOUTH MAZE_WALL_WEST;

#define NORTH_ANGLE 0x8000
#define EAST_ANGLE 0xc000
#define SOUTH_ANGLE 0x0000
#define WEST_ANGLE 0x4000

And the maze contents for each cell will be a wall-mask to contain the bit flags and a cell-id to identify which textures to use as follows:

struct MazeContents
{
BYTE wallMask[MAZE_MAX_WIDTH*MAZE_MAX_HEIGHT];
BYTE cellID[MAZE_MAX_WIDTH*MAZE_MAX_HEIGHT];
};

Finally we will define a class to wrap the maze representation, generation, and operations as follows:

class CMaze
{
public:
CMaze();
~CMaze();

HRESULT Init( DWORD width , DWORD height , DWORD Seed );
void Empty();

DWORD GetWidth() const { return m_dwWidth; };
DWORD GetHeight() const { return m_dwHeight; };
DWORD GetSize() const { return m_dwWidth*m_dwHeight; };
BYTE GetCellMaskXY( DWORD x , DWORD y ) const {
assert(m_pMaze->wallMask!=NULL);
return (m_pMaze)->wallMask[x+(y*m_dwHeight)];
};
BYTE GetCellMaskID( DWORD cellID ) const {
assert(m_pMaze->wallMask!=NULL);
return (m_pMaze)->wallMask[cellID];
};
BYTE GetCellID( DWORD x , DWORD y ) const {
assert(m_pMaze->cellID!=NULL);
return (m_pMaze)->cellID[x*m_dwWidth+y];
};
void SetCellID( DWORD x , DWORD y, BYTE id ) const {
assert(m_pMaze->cellID!=NULL);
(m_pMaze)->cellID[x*m_dwWidth+y] = id;
};
BOOL CanGoNorth( DWORD x , DWORD y ) const {
return !(GetCellMaskXY(x,y)&MAZE_WALL_NORTH);
};
BOOL CanGoEast( DWORD x , DWORD y ) const {
return !(GetCellMaskXY(x,y)&MAZE_WALL_EAST);
};
BOOL CanGoSouth( DWORD x , DWORD y ) const {
return !(GetCellMaskXY(x,y)&MAZE_WALL_SOUTH);
};
BOOL CanGoWest( DWORD x , DWORD y ) const {
return !(GetCellMaskXY(x,y)&MAZE_WALL_WEST);
};

DWORD WalkWalls( );

MazeContents* m_pMaze;
protected:
...
private:
CMaze( const CMaze& );
void operator=( const CMaze& );
};

Let’s now add methods to this class to generate the maze representation.

Maze Generator

Each class needs a constructor and a destructor, from cmaze.cpp:
CMaze::CMaze()
{
m_dwWidth = m_dwHeight = m_dwSize = 0;
m_dwSeed = 0;
m_pMaze = NULL;
m_dwMaxView = g_nMazeSize;
}

CMaze::~CMaze()
{
if( m_pMaze != NULL )
DXTRACE_ERR( TEXT("Warning: Destructing CMaze object without calling Empty()\n"),E_FAIL );

Empty();
}


These are both pretty straightforward. The main item to note is the expectation that cleanup is performed before the destructor is invoked.

Then we have a cleanup method called Empty() :

void CMaze::Empty()
{
if( m_pMaze != NULL )
SAFE_DELETE( m_pMaze );
m_dwWidth = m_dwHeight = m_dwSize = 0;
m_dwSeed = 0;
}

whose main responsibility is memory clean-up. The main method of interest, where the heavy lifting is done, is in Init():

HRESULT CMaze::Init( DWORD dwWidth, DWORD dwHeight, DWORD dwSeed )
{
HRESULT hr;
CellNode* pCells = NULL;
CellNode* pCellNode = NULL;
DWORD dwNumWalls;
WallNode* pWalls = NULL;
WallNode* pWallNode = NULL;
WallNode tempWall;
DWORD dwIndex;
DWORD i;

m_Random.Reset( dwSeed );
// Empty out any old data
Empty();

// Store parameters and compute number of cells in the maze
m_dwWidth = dwWidth;
m_dwHeight = dwHeight;
m_dwSeed = dwSeed;
m_dwSize = m_dwWidth * m_dwHeight;

// Must be non-zero
if( m_dwSize == 0 )
{
hr = E_INVALIDARG;
DXTRACE_ERR( TEXT("Maze height and width must be greater than 0"), E_INVALIDARG );
goto LFail;
}

// Validate maze size
if( m_dwWidth>MAZE_MAX_WIDTH m_dwHeight>MAZE_MAX_HEIGHT )
{
hr = E_INVALIDARG;
DXTRACE_ERR( TEXT("Maze height and width must be less than 16"), E_INVALIDARG );
goto LFail;
}

if( (m_dwWidth % 2) != 0 (m_dwHeight % 2) != 0 )
{
hr = E_INVALIDARG;
DXTRACE_ERR( TEXT("Maze height and width must be divisable by 2"), E_INVALIDARG );
goto LFail;
}

// Allocate maze, and initially make all walls solid
m_pMaze = new MazeContents;
if( m_pMaze == NULL )
{
hr = E_OUTOFMEMORY;
DXTRACE_ERR( TEXT("new"), hr );
goto LFail;
}
memset( m_pMaze->wallMask, MAZE_WALL_ALL, m_dwSize );


So far this is all initialization and validation code. Notice we reset the random number generator and called Empty() to prepare to initialize.

// Okay, now we're going to generate the maze.
// We use Kruskal's algorithm
// Allocate and initialize temporary cell list

pCells = new CellNode[m_dwSize];
if( pCells == NULL )
{
hr = E_OUTOFMEMORY;
DXTRACE_ERR( TEXT("new"), hr );
goto LFail;
}

pCellNode = pCells;
for( i = 0; i <>pNext = NULL;
pCellNode->pPartition = pCellNode;
pCellNode++;
}

// Create list of walls
dwNumWalls = ((m_dwWidth-1)*m_dwHeight) +
((m_dwHeight-1)*m_dwWidth);
pWalls = new WallNode[dwNumWalls];
if( pWalls == NULL )
{
hr = E_OUTOFMEMORY;
DXTRACE_ERR( TEXT("new"), hr );
goto LFail;
}


At this point we have a cell list and wall list.
pWallNode = pWalls;
for( i = 1; i <>
{
for( DWORD j = 0; j <>
{
pWallNode->dwX = i;
pWallNode->dwY = j;
pWallNode->dwType = MAZE_WALL_WEST;
}
}

for( i = 0; i <>
{
for( DWORD j = 1; j <>
{
pWallNode->dwX = i;
pWallNode->dwY = j;
pWallNode->dwType = MAZE_WALL_NORTH;
}
}

// Randomly permute the wall list
for( i = dwNumWalls-1; i > 0; i-- )
{
dwIndex = m_Random.Get(i);
tempWall = pWalls[dwIndex];
pWalls[dwIndex] = pWalls[i];
pWalls[i] = tempWall;
}

Now we have a fully initialized and randomized wall list.

// Walk through all the walls
pWallNode = pWalls;
for( i = 0; i <>
{
// Determine the cells either side of the wall
DWORD dwCellA = pWallNode->dwX + (pWallNode->dwY * m_dwWidth);
DWORD dwCellB = dwCellA;
if( pWallNode->dwType == MAZE_WALL_NORTH )
dwCellB -= m_dwWidth;
else
dwCellB--;

// Are they already connected (partitions equal)?
CellNode* pCellA = pCells+dwCellA;
CellNode* pCellB = pCells+dwCellB;
if( pCellA->pPartition != pCellB->pPartition )
{
// Nope, so let's take out that wall.
// First, connect the partition lists
while ( pCellA->pNext )
pCellA = pCellA->pNext;
pCellB = pCellB->pPartition;
pCellA->pNext = pCellB;
while ( pCellB )
{
pCellB->pPartition = pCellA->pPartition;
pCellB = pCellB->pNext;
}

// Now remove the walls in our maze array
if( pWallNode->dwType == MAZE_WALL_NORTH )
{
m_pMaze->wallMask[dwCellA] &= ~MAZE_WALL_NORTH;
m_pMaze->wallMask[dwCellB] &= ~MAZE_WALL_SOUTH;
}
else
{
m_pMaze->wallMask[dwCellA] &= ~MAZE_WALL_WEST;
m_pMaze->wallMask[dwCellB] &= ~MAZE_WALL_EAST;
}
}
}

Here we walk the list of walls, determine the cells on either side, if the cells are not connected, then “knock out” the intervening wall and connect the two sets. We do this for all the walls, making a completely connected maze.
// Free temporary wall and cell lists
delete[] pWalls;
delete[] pCells;

return S_OK;

LFail:
SAFE_DELETE_ARRAY( pCells );
SAFE_DELETE_ARRAY( pWalls );
SAFE_DELETE_ARRAY( m_pMaze );
return hr;
}

And finally we clean up. Next WalkWalls:

DWORD CMaze::WalkWalls( )
{
DWORD nCount = 0;
for ( UINT x = 0; x <>
{
for ( UINT y = 0; y <>
{
BYTE cell = GetCellMaskXY(x,y);
SetCellID(x,y,(BYTE)(x*g_nMazeWidth+y));
if( (cell & MAZE_WALL_NORTH ) ) nCount++;
if( (cell & MAZE_WALL_SOUTH) ) nCount++;
if( (cell & MAZE_WALL_WEST) ) nCount++;
if( (cell & MAZE_WALL_EAST) ) nCount++; }
}
return nCount;
}

WalkWalls gets used to count the walls so we know how many to draw. There are a couple other utility methods defined in cmaze.h in the class definition, go read them if you are interested.

Now that we have a maze internal representation, we need to use it and generate geometry from it. And we will do that for both D3D9 and D3D10. We’ll create a class to wrap the internal representation and the rendering representation, and call it CMazeSim. It will consist of the lifetime methods (constructor, destructor), the internal instance variables, the operations or mini-API that clients use to manipulate the Maze, and generator and rendering methods as shown below.

class CMazeSim
{
public:
//lifetime
CMazeSim();
~CMazeSim();

//internals
CMaze m_Maze;
DWORD m_dwCellWallCount;

//operations
HRESULT Init10(DWORD dwRandomSeed,
ID3D10Device* pd3dDevice);
HRESULT Init9(DWORD dwRandomSeed,
IDirect3DDevice9* pd3dDevice);
HRESULT Reset10(DWORD dwRandomSeed,
ID3D10Device* pd3dDevice);
HRESULT Reset9(DWORD dwRandomSeed,
IDirect3DDevice9* pd3dDevice);
HRESULT Unset9();
HRESULT Update10(float fElapsedTime);
HRESULT Update9(float fElapsedTime);
HRESULT Stop10();
HRESULT Stop9();
//generation
void LoadNonIndexedQuad( MazeVertex* vertices,
const D3DXVECTOR3& vOrigin,
const D3DXVECTOR3& vBasis1,
const D3DXVECTOR3& vBasis2,
const D3DXVECTOR3& vNormal );
HRESULT LoadMazeFloor10(ID3D10Device* pd3dDevice);
HRESULT LoadMazeWalls10(ID3D10Device* pd3dDevice);
HRESULT LoadMazeRoof10(ID3D10Device* pd3dDevice);
HRESULT LoadMazeVertices10(ID3D10Device* pd3dDevice);
HRESULT LoadMaze10(ID3D10Device* pd3dDevice);

HRESULT LoadMazeFloor9(IDirect3DDevice9* pd3dDevice);
HRESULT LoadMazeWalls9(IDirect3DDevice9* pd3dDevice);
HRESULT LoadMazeRoof9(IDirect3DDevice9* pd3dDevice);
HRESULT LoadMazeVertices9(IDirect3DDevice9* pd3dDevice);
HRESULT LoadMaze9(IDirect3DDevice9* pd3dDevice);

//rendering
DWORD m_dwNumCullableThings;
void DrawMazeVisNoneRoof10(ID3D10Device* pd3dDevice,
double fTime, float fElapsedTime);
void DrawMazeVisNoneWalls10(ID3D10Device* pd3dDevice,
double fTime, float fElapsedTime);
void DrawMazeVisNoneFloor10(ID3D10Device* pd3dDevice,
double fTime, float fElapsedTime);
void DrawMaze10(ID3D10Device* pd3dDevice,
D3DXMATRIX mCurrentView,
D3DXMATRIX mWorldViewProj,
double fTime, float fElapsedTime);

void DrawMazeVisNoneRoof9(IDirect3DDevice9* pd3dDevice,
double fTime, float fElapsedTime);
void DrawMazeVisNoneWalls9(IDirect3DDevice9* pd3dDevice,
double fTime, float fElapsedTime);
void DrawMazeVisNoneFloor9(IDirect3DDevice9* pd3dDevice,
double fTime, float fElapsedTime);
void DrawMaze9(IDirect3DDevice9* pd3dDevice,
D3DXMATRIX mCurrentView,
D3DXMATRIX mWorldViewProj,
double fTime, float fElapsedTime);

};

Note the instance variable m_Maze, of type CMaze. That is our internal representation.

Maze Sim-Operations

The constructor and destructor are empty for CMazeSim. Init10 and Init9 are more interesting, below:

HRESULT CMazeSim::Init10(DWORD dwRandomSeed,
ID3D10Device* pd3dDevice)
{
HRESULT hr = S_OK;

//gen the maze
hr = Reset10(dwRandomSeed,pd3dDevice);

//load the maze resources
LoadMaze10(pd3dDevice);

return hr;
}
HRESULT CMazeSim::Init9(DWORD dwRandomSeed,
IDirect3DDevice9* pd3dDevice)
{
HRESULT hr = S_OK;

//gen the maze
hr = Reset9(dwRandomSeed,pd3dDevice);

//reload the maze resources
LoadMaze9(pd3dDevice);

return hr;
}

Here we see the calls to Reset and then LoadMaze, in both D3D10 and D3D9 variants. LoadMaze handles loading the long-lived resources like the effects and textures and only needs to be invoked once.

Reset handles manipulating the CMaze ivar to regenerate the maze by invoking Empty and then Init, and then generating the maze geometry using LoadMazeVertices, in both D3D10 and D3D9 variants. Because the maze can change when it is regenerated, LoadMazeVertices needs to be called every time Reset is called. We also count how many walls are in the maze, so we can render the right amount later. And in the D3D9 variant we have to reset the effect file, see below:

HRESULT CMazeSim::Reset10(DWORD dwRandomSeed,
ID3D10Device* pd3dDevice)
{
HRESULT hr = S_OK;

//clear maze
m_Maze.Empty();

//reinit new maze
hr = m_Maze.Init( g_nMazeWidth,
g_nMazeHeight,
dwRandomSeed);

//count the walls
m_dwCellWallCount = m_Maze.WalkWalls();

//reload the maze geometry
LoadMazeVertices10(pd3dDevice);

return S_OK;
}
HRESULT CMazeSim::Reset9(DWORD dwRandomSeed,
IDirect3DDevice9* pd3dDevice)
{
HRESULT hr = S_OK;

//clear maze
m_Maze.Empty();

//reinit new maze
hr = m_Maze.Init( g_nMazeWidth,
g_nMazeHeight,
dwRandomSeed);

//count the walls
m_dwCellWallCount = m_Maze.WalkWalls();

if (g_pEffectMaze9)
V_RETURN(g_pEffectMaze9->OnResetDevice() );

//reload the maze geometry
LoadMazeVertices9(pd3dDevice);

return S_OK;
}

In both D3D10 and D3D9 the Stop method releases resources, as shown below:

HRESULT CMazeSim::Stop10()
{
//d3d maze
SAFE_RELEASE(g_pMazeInputLayout);
if( g_pMazeFloor10 ) g_pMazeFloor10->Release();
if( g_pMazeWalls10 ) g_pMazeWalls10->Release();
if( g_pMazeRoof10 ) g_pMazeRoof10->Release();

if( g_pTextureFloorRV ) g_pTextureFloorRV->Release();
if( g_pTextureWallsRV ) g_pTextureWallsRV->Release();
if( g_pTextureRoofRV ) g_pTextureRoofRV->Release();

SAFE_RELEASE( g_pEffectMaze10 );

// Destroy the maze
m_Maze.Empty();

return S_OK;
}
HRESULT CMazeSim::Stop9()
{
SAFE_RELEASE( g_pMazeInputDecl );
SAFE_RELEASE( g_pMazeFloor9 );
SAFE_RELEASE( g_pMazeWalls9 );
SAFE_RELEASE( g_pMazeRoof9 );

SAFE_RELEASE( g_pTextureFloor );
SAFE_RELEASE( g_pTextureWall );
SAFE_RELEASE( g_pTextureRoof );

if( g_pEffectMaze9 ) g_pEffectMaze9->OnLostDevice();
SAFE_RELEASE( g_pEffectMaze9 );

// Destroy the maze
m_Maze.Empty();

return S_OK;
}

A difference between D3D10 and D3D9 is D3D9 requires additional resource handling during a lost device as shown in Unset9:

HRESULT CMazeSim::Unset9()
{
SAFE_RELEASE( g_pMazeFloor9 );
SAFE_RELEASE( g_pMazeWalls9 );
SAFE_RELEASE( g_pMazeRoof9 );

if( g_pEffectMaze9 ) g_pEffectMaze9->OnLostDevice();

return S_OK;
}

Maze Sim Geometry and Resources – D3D10

Now we need to show how we generate the D3D geometry from the maze internal representation. The “high-level” method is LoadMazeVertices10:

HRESULT CMazeSim::LoadMazeVertices10(ID3D10Device* pd3dDevice)
{
//floor
LoadMazeFloor10(pd3dDevice);
//walls
LoadMazeWalls10(pd3dDevice);
//roof
LoadMazeRoof10(pd3dDevice);

return S_OK;
}

It calls LoadMazeFloor10, LoadMazeWalls10, and LoadMazeRoof10:

HRESULT CMazeSim::LoadMazeFloor10(ID3D10Device* pd3dDevice)
{
HRESULT hr;
D3D10_BUFFER_DESC bd;
MazeVertex vertices[g_nMazeWidth*g_nMazeHeight*2*3];

DWORD nw = 0;
//populate buffer
for( DWORD i = 0; i < j =" 0;" usage =" D3D10_USAGE_DEFAULT;" bytewidth =" sizeof(" bindflags =" D3D10_BIND_VERTEX_BUFFER;" cpuaccessflags =" 0;" miscflags =" 0;" psysmem =" vertices;" hr =" pd3dDevice-">CreateBuffer( &bd,
&InitData,
&g_pMazeFloor10 );
if( FAILED( hr ) )
return hr;
else
return S_OK;
}
HRESULT CMazeSim::LoadMazeWalls10(ID3D10Device* pd3dDevice)
{
HRESULT hr;
D3D10_BUFFER_DESC bd;
MazeVertex vertices[g_nMazeWidth*g_nMazeHeight*4*2*3];
DWORD nw = 0;

//full height
for( DWORD i = 0; i < j =" 0;" cell =" m_Maze.GetCellMaskXY(i,j);" m_dwcellwallcount =" nw;" usage =" D3D10_USAGE_DEFAULT;" usage =" D3D10_USAGE_DEFAULT;" bytewidth =" sizeof(" bindflags =" D3D10_BIND_VERTEX_BUFFER;" cpuaccessflags =" 0;" miscflags =" 0;" psysmem =" vertices;" hr =" pd3dDevice-">CreateBuffer( &bd,
&InitData,
&g_pMazeWalls10 );
if( FAILED( hr ) )
return hr;
else
return S_OK;

}
HRESULT CMazeSim::LoadMazeRoof10(ID3D10Device* pd3dDevice)
{
HRESULT hr;
D3D10_BUFFER_DESC bd;
MazeVertex vertices[g_nMazeWidth*g_nMazeHeight *2*3];

DWORD nw = 0;
//populate buffer
for( DWORD i = 0; i < j =" 0;" usage =" D3D10_USAGE_DEFAULT;" usage =" D3D10_USAGE_DEFAULT;" bytewidth =" sizeof(" bindflags =" D3D10_BIND_VERTEX_BUFFER;" cpuaccessflags =" 0;" miscflags =" 0;" psysmem =" vertices;" hr =" pd3dDevice-">CreateBuffer( &bd,
&InitData,
&g_pMazeRoof10 );
if( FAILED( hr ) )
return hr;
else
return S_OK;
}

In all three of these methods, we loop across the maze extent and use LoadNonIndexedQuad to populate an array of MazeVertices. The vectors passed in to LoadNonIndexedQuad depend on whether it’s a floor, wall, or roof quad we are generating. Then we set up a D3D10 buffer descriptor and pass it to pd3dDevice->CreateBuffer to generate our vertex buffer.

The magic of LoadNonIndexedQuad is to loop in the width and height directions based on the amount of tessellation and generate vertices and texture coordinates. It uses the first vector as an origin, and the next two vectors as basis vectors to control the iteration:

void CMazeSim::LoadNonIndexedQuad( MazeVertex* vertices,
const D3DXVECTOR3& vOrigin,
const D3DXVECTOR3& vBasis1,
const D3DXVECTOR3& vBasis2,
const D3DXVECTOR3& vNormal )
{
// Compute edge steps
float fStep = 1.0f / g_dwTesselation;
D3DXVECTOR3 vStep1 = vBasis1 * fStep;
D3DXVECTOR3 vStep2 = vBasis2 * fStep;

// Fill out grid of vertices
D3DXVECTOR3 rowstart = vOrigin;
float u = 1;
for( DWORD i = 0;i < pos =" rowstart;" v =" 0;" j =" 0;j">pos = pos;
vertices->tex.x = u;
vertices->tex.y = v;
vertices++;
//t1v2
vertices->pos = pos+vStep2;
vertices->tex.x = u;
vertices->tex.y = v+fStep;
vertices++;
//t1v3
vertices->pos = pos+vStep1;
vertices->tex.x = u-fStep;
vertices->tex.y = v;
vertices++;
//t2v1
vertices->pos = pos+vStep2;
vertices->tex.x = u;
vertices->tex.y = v+fStep;
vertices++;
//t2v2
vertices->pos = pos+vStep2+vStep1;
vertices->tex.x = u-fStep;
vertices->tex.y = v+fStep;
vertices++;
//t2v3
vertices->pos = pos+vStep1;
vertices->tex.x = u-fStep;
vertices->tex.y = v;
vertices++;
}
}
}

Look back and note the second basis vector for the floor is up, for the roof down. And that we call LoadNonIndexedQuad four times in LoadMazeWalls10, once for each north, south, east, and west wall that could be in a cell.

Now we need to load the resources. LoadMaze10 is the method that performs this work:

HRESULT CMazeSim::LoadMaze10(ID3D10Device* pd3dDevice)
{
HRESULT hr;
D3D10_PASS_DESC PassDesc;
ID3D10EffectPass* pPass;
ID3D10Blob* l_p_Errors = NULL;
LPVOID l_pError = NULL;

// prepare for effect files
WCHAR str[MAX_PATH];
DWORD dwShaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;
#if defined( DEBUG ) defined( _DEBUG )
// Set the D3D10_SHADER_DEBUG flag to embed debug information
dwShaderFlags = D3D10_SHADER_DEBUG;
#endif

// Read the maze D3DX effect file
V_RETURN( DXUTFindDXSDKMediaFileCch( str, MAX_PATH,
L"effects\\maze-singletexture10.fx" ) );
V_RETURN( D3DX10CreateEffectFromFile( str,
NULL, NULL, "fx_4_0",
dwShaderFlags, 0, pd3dDevice, NULL,NULL, &g_pEffectMaze10, &l_p_Errors, NULL ));
if( l_p_Errors )
{
l_pError = l_p_Errors->GetBufferPointer();
// then cast to a char* to see it in the locals window
}

//technique
g_pRenderMazeSolid10= g_pEffectMaze10->GetTechniqueByName(
"RenderMazeSolid" );
g_pRenderMazeAlpha10= g_pEffectMaze10->GetTechniqueByName(
"RenderMazeAlpha" );

// Obtain the parameter handles
g_pmMazeWorldViewProj10 = g_pEffectMaze10->GetVariableByName(
"g_mWorldViewProj" )->AsMatrix();
g_pmMazeWorldView10 = g_pEffectMaze10->GetVariableByName(
"g_mWorldView" )->AsMatrix();
g_pDiffuseTex10 = g_pEffectMaze10->GetVariableByName(
"g_txDiffuse" )->AsShaderResource();

//create the layout
pPass = g_pRenderMazeSolid10->GetPassByIndex( 0 );
pPass->GetDesc( &PassDesc );
V( pd3dDevice->CreateInputLayout( MazeVertexLayout, sizeof(MazeVertexLayout)/sizeof(D3D10_INPUT_ELEMENT_DESC),
PassDesc.pIAInputSignature,
PassDesc.IAInputSignatureSize,
&g_pMazeInputLayout ) );
//load the floor texture
hr = D3DX10CreateShaderResourceViewFromFile( pd3dDevice,
L"media\\panel-light-stripe.bmp", NULL, NULL, &g_pTextureFloorRV, NULL );
if( FAILED( hr ) )
return hr;
//load the wall texture
hr = D3DX10CreateShaderResourceViewFromFile( pd3dDevice,
L"media\\panel-light-rad.bmp", NULL, NULL, &g_pTextureWallsRV, NULL );
if( FAILED( hr ) )
return hr;
//load the roof texture
hr = D3DX10CreateShaderResourceViewFromFile( pd3dDevice,
L"media\\panel-light-plain.bmp", NULL, NULL, &g_pTextureRoofRV, NULL );
if( FAILED( hr ) )
return hr;

return S_OK;
}

Here we load the effect file, get the techniques and variables from it, use the description to generate the vertex layout, and then load the floor, wall, and roof texture.

Maze Sim Geometry and Resources – D3D9

The D3D9 code path has a similar master loader for vertices, LoadMazeVertices9:

HRESULT CMazeSim::LoadMazeVertices9(IDirect3DDevice9* pd3dDevice)
{
//floor
LoadMazeFloor9(pd3dDevice);
//walls
LoadMazeWalls9(pd3dDevice);
//roof
LoadMazeRoof9(pd3dDevice);

return S_OK;
}

It calls LoadMazeFloor9, LoadMazeWalls9, and LoadMazeRoof9:

HRESULT CMazeSim::LoadMazeFloor9(IDirect3DDevice9* pd3dDevice)
{
HRESULT hr;
MazeVertex* vertices;

if ( g_pMazeFloor9) SAFE_RELEASE(g_pMazeFloor9);

if( FAILED( pd3dDevice->CreateVertexBuffer( ((g_nMazeWidth * g_nMazeHeight) *2*3) *sizeof(MazeVertex),
D3DUSAGE_DYNAMIC D3DUSAGE_WRITEONLY,
FVF_MAZETEXTURED,
D3DPOOL_DEFAULT,
&g_pMazeFloor9, NULL ) ) )
return E_FAIL;

//lock to load
g_pMazeFloor9->Lock( 0,//offset
0,//entire vb
(VOID**)&vertices,
D3DLOCK_DISCARD );

DWORD nw = 0;
//populate buffer
for( DWORD i = 0; i < j =" 0;">Unlock();

return S_OK;
}
HRESULT CMazeSim::LoadMazeWalls9(IDirect3DDevice9* pd3dDevice)
{
HRESULT hr;
MazeVertex* vertices;
DWORD nw = 0;

if ( g_pMazeWalls9) SAFE_RELEASE(g_pMazeWalls9);

if( FAILED( pd3dDevice->CreateVertexBuffer( ((g_nMazeWidth * g_nMazeHeight)* 4*2*3)*sizeof(MazeVertex),
D3DUSAGE_DYNAMIC D3DUSAGE_WRITEONLY,
FVF_MAZETEXTURED,
D3DPOOL_DEFAULT,
&g_pMazeWalls9, NULL ) ) )
return E_FAIL;

//lock to load
g_pMazeWalls9->Lock( 0,//offset
0,//entire vb
(VOID**)&vertices,
D3DLOCK_DISCARD );
//load full height
for( DWORD i = 0; i < j =" 0;" cell =" m_Maze.GetCellMaskXY(i,j);">Unlock();
m_dwCellWallCount = nw;

//done
return S_OK;
}
HRESULT CMazeSim::LoadMazeRoof9(IDirect3DDevice9* pd3dDevice)
{
HRESULT hr;
MazeVertex* vertices;

if ( g_pMazeRoof9) SAFE_RELEASE(g_pMazeRoof9);
//create roof vb
if( FAILED( pd3dDevice->CreateVertexBuffer( ((g_nMazeWidth* g_nMazeHeight) *2*3)*sizeof(MazeVertex),
D3DUSAGE_DYNAMIC D3DUSAGE_WRITEONLY,
FVF_MAZETEXTURED,
D3DPOOL_DEFAULT,
&g_pMazeRoof9, NULL ) ) )
return E_FAIL;

//lock to load
g_pMazeRoof9->Lock( 0,//offset
0,//entire vb
(VOID**)&vertices,
D3DLOCK_DISCARD );
DWORD nw = 0;

//populate buffer
for( DWORD i = 0; i < j =" 0;">Unlock();

return S_OK;
}

Which are very similar to their D3D10 variants except for the required vertex buffer handling changes. It is a bit more convenient to fill a memory structure and pass it to D3D10 to create a populated vertex buffer instead of the D3D9 way of creating an empty buffer, locking it, populating it, and unlocking it.

Now we need to load the resources with LoadMaze9 is to load the effect file and textures:

HRESULT CMazeSim::LoadMaze9(IDirect3DDevice9* pd3dDevice)
{

HRESULT hr;
LPVOID l_pError = NULL;
LPD3DXBUFFER p_Errors;

// prepare for effect files
WCHAR str[MAX_PATH];
DWORD dwShaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;
#if defined( DEBUG ) defined( _DEBUG )
// Set the D3D10_SHADER_DEBUG flag to embed debug information
dwShaderFlags = D3D10_SHADER_DEBUG;
#endif

// Read the maze D3DX effect file
V_RETURN( DXUTFindDXSDKMediaFileCch( str, MAX_PATH,
L"effects\\maze-singletexture9.fx" ) );

D3DXCreateEffectFromFile( pd3dDevice, str,
NULL, NULL, dwShaderFlags,
NULL, &g_pEffectMaze9, &p_Errors );
if( p_Errors )
{
l_pError = p_Errors->GetBufferPointer();
// then cast to a char* to see it in the locals window
p_Errors->Release();
}

//technique
g_pRenderMazeSolid9 = g_pEffectMaze9->GetTechniqueByName( "RenderMazeSolid" );
g_pRenderMazeAlpha9 = g_pEffectMaze9->GetTechniqueByName( "RenderMazeAlpha" );

// Obtain the parameter handles
g_hmMazeWorldViewProjHandle9 = g_pEffectMaze9->GetParameterByName( NULL, "g_mWorldViewProj" );
g_pDiffuseTex9 = g_pEffectMaze9->GetParameterByName( NULL, "g_txDiffuse" );

//create the layout
pd3dDevice->CreateVertexDeclaration( decl_TextureModDiffuse_Vert,
&g_pMazeInputDecl );

//load the floor texture
V_RETURN( DXUTFindDXSDKMediaFileCch( str, MAX_PATH,
L"media\\panel-light-stripe.bmp" ) );

V_RETURN( D3DXCreateTextureFromFileEx( pd3dDevice, str,
D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT,
0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED,
D3DX_DEFAULT, D3DX_DEFAULT, 0,
NULL, NULL, &g_pTextureFloor ) );

//load the wall texture
V_RETURN( DXUTFindDXSDKMediaFileCch( str, MAX_PATH,
L"media\\panel-light-rad.bmp" ) );

V_RETURN( D3DXCreateTextureFromFileEx( pd3dDevice, str,
D3DX_DEFAULT, D3DX_DEFAULT,D3DX_DEFAULT,
0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED,
D3DX_DEFAULT, D3DX_DEFAULT, 0,
NULL, NULL, &g_pTextureWall ) );
//load the roof texture
V_RETURN( DXUTFindDXSDKMediaFileCch( str, MAX_PATH,
L"media\\panel-light-plain.bmp" ) );

V_RETURN( D3DXCreateTextureFromFileEx( pd3dDevice, str,
D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT,
0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED,
D3DX_DEFAULT, D3DX_DEFAULT, 0,
NULL, NULL, &g_pTextureRoof ) );

return S_OK;
}

Maze Sim Rendering-D3D10

Let’s get ready to render! Here we have a “high-level” method,DrawMaze10:

void CMazeSim::DrawMaze10(ID3D10Device* pd3dDevice,
D3DXMATRIX mView,
D3DXMATRIX mWorldViewProj,
double fTime, float fElapsedTime)
{
// Render the maze
g_pmMazeWorldViewProj10->SetMatrix( ( float* )&mWorldViewProj );
// Set primitive topology
pd3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
pd3dDevice->IASetInputLayout(g_pMazeInputLayout);

//opaque pass
g_pRenderMazeSolid10->GetPassByIndex(0)->Apply(0);
//walls
DrawMazeVisNoneWalls10(pd3dDevice, fTime, fElapsedTime);
//floor
DrawMazeVisNoneFloor10(pd3dDevice, fTime, fElapsedTime);

//make roof transparent
if ( !g_bSkyCam )
{
g_pRenderMazeAlpha10->GetPassByIndex(0)->Apply(0);
//roof
DrawMazeVisNoneRoof10(pd3dDevice, fTime, fElapsedTime);
}
}

that wraps rendering the floor, walls, and roof:

void CMazeSim::DrawMazeVisNoneFloor10(ID3D10Device* pd3dDevice,
double fTime, float fElapsedTime)
{
// Set vertex buffer
UINT stride = sizeof( MazeVertex );
UINT offset = 0;
pd3dDevice->IASetVertexBuffers(0,1, &g_pMazeFloor10, &stride, &offset );

//texture
g_pDiffuseTex10->SetResource( g_pTextureFloorRV );
pd3dDevice->PSSetShaderResources(0,1,&g_pTextureFloorRV);
pd3dDevice->Draw( g_nMazeWidth*g_nMazeHeight*2*3, 0);
}
void CMazeSim::DrawMazeVisNoneWalls10(ID3D10Device* pd3dDevice,
double fTime, float fElapsedTime)
{
// Set vertex buffer
UINT stride = sizeof( MazeVertex );
UINT offset = 0;
pd3dDevice->IASetVertexBuffers(0,1, &g_pMazeWalls10, &stride, &offset );

//texture
g_pDiffuseTex10->SetResource( g_pTextureWallsRV );
pd3dDevice->PSSetShaderResources(0,1,&g_pTextureWallsRV);
pd3dDevice->Draw( m_dwCellWallCount*3*2, 0);
}
void CMazeSim::DrawMazeVisNoneRoof10(ID3D10Device* pd3dDevice,
double fTime, float fElapsedTime)
{
// Set vertex buffer
UINT stride = sizeof( MazeVertex );
UINT offset = 0;
pd3dDevice->IASetVertexBuffers( 0, 1, &g_pMazeRoof10, &stride, &offset );

//texture
g_pDiffuseTex10->SetResource( g_pTextureRoofRV );
pd3dDevice->PSSetShaderResources(0,1,&g_pTextureRoofRV);
pd3dDevice->Draw( g_nMazeWidth*g_nMazeHeight*2*3, 0);
}

That is it for the D3D10 rendering, very straightforward. Read the effect file to see the very simple shader and stateblocks used.

Maze Sim Rendering-D3D9

The D3D9 code path also has a high-level method, DrawMaze9:

void CMazeSim::DrawMaze9(IDirect3DDevice9* pd3dDevice,
D3DXMATRIX mView,
D3DXMATRIX mWorldViewProj,
double fTime, float fElapsedTime)
{
HRESULT hr;
UINT cPass;

// Render the maze
pd3dDevice->SetVertexDeclaration( g_pMazeInputDecl );

//opaque pass
g_pEffectMaze9->SetTechnique( g_pRenderMazeSolid9 );

//floor
V( g_pEffectMaze9->SetTexture( g_pDiffuseTex9, g_pTextureFloor ) );
V( g_pEffectMaze9->SetMatrix( g_hmMazeWorldViewProjHandle9,
&mWorldViewProj ) );
V( g_pEffectMaze9->Begin( &cPass, 0 ) );
V( g_pEffectMaze9->BeginPass( 0 ) );
DrawMazeVisNoneFloor9(pd3dDevice, fTime, fElapsedTime);
V( g_pEffectMaze9->EndPass() );
V( g_pEffectMaze9->End() );

//walls
g_pEffectMaze9->SetTechnique( g_pRenderMazeSolid9 );
V( g_pEffectMaze9->SetTexture( g_pDiffuseTex9,
g_pTextureWall ) );
V( g_pEffectMaze9->SetMatrix( g_hmMazeWorldViewProjHandle9,
&mWorldViewProj ) );
V( g_pEffectMaze9->Begin( &cPass, 0 ) );
V( g_pEffectMaze9->BeginPass( 0 ) );
DrawMazeVisNoneWalls9(pd3dDevice, fTime, fElapsedTime);
V( g_pEffectMaze9->EndPass() );
V( g_pEffectMaze9->End() );

//make roof transparent if "in the maze" else dont render
if ( !g_bSkyCam )
{
pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE);
pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);
pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );

V( g_pEffectMaze9->SetTexture( g_pDiffuseTex9,
g_pTextureRoof ) );
V( g_pEffectMaze9->SetMatrix( g_hmMazeWorldViewProjHandle9,
&mWorldViewProj ) );
V( g_pEffectMaze9->Begin( &cPass, 0 ) );
V( g_pEffectMaze9->BeginPass( 0 ) );
DrawMazeVisNoneRoof9(pd3dDevice, fTime, fElapsedTime);
V( g_pEffectMaze9->EndPass() );
V( g_pEffectMaze9->End() );

pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );
}
}

You can see the D3D9 version is a little bit longer, with more state setting via the API where in D3D10 the state is contained in the effect file.

Now we have drawing the floor, walls, and roof:

void CMazeSim::DrawMazeVisNoneFloor9(IDirect3DDevice9* pd3dDevice,
double fTime, float fElapsedTime)
{
HRESULT hr;

V( pd3dDevice->SetStreamSource( 0, g_pMazeFloor9, 0,
sizeof( MazeVertex ) ) );
pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0,
g_nMazeWidth*g_nMazeHeight*2);
}
void CMazeSim::DrawMazeVisNoneWalls9(IDirect3DDevice9* pd3dDevice,
double fTime, float fElapsedTime)
{
HRESULT hr;

V( pd3dDevice->SetStreamSource( 0, g_pMazeWalls9, 0,
sizeof( MazeVertex ) ) );
pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0,
m_dwCellWallCount*2);

}
void CMazeSim::DrawMazeVisNoneRoof9(IDirect3DDevice9* pd3dDevice,
double fTime, float fElapsedTime)
{
HRESULT hr;

V( pd3dDevice->SetStreamSource( 0, g_pMazeRoof9, 0,
sizeof( MazeVertex ) ) );
pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0,
g_nMazeWidth*g_nMazeHeight*2);
}

Read the effect file to see the very simple shader and stateblocks used.

Callbacks and Maze Rendering-D3D10

We are in the final stretch, with the callbacks for D3D10 and D3D9 left.

BOOL g_bRunSim = true;
CMazeSim g_MazeSim;
DWORD g_nMazeSize, g_nMazeWidth, g_nMazeHeight, g_nMazeCells;
DWORD g_dwRandomSeed;
DWORD g_dwTesselation = 1;

In the D3D10 code path, we have some global state and an instance of the CMazeSim class shown above.

HRESULT CALLBACK OnD3D10CreateDevice( ID3D10Device* pd3dDevice, const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc,
void* pUserContext )
{

//init internal maze representation
g_nMazeSize =16;
g_nMazeWidth =16;
g_nMazeHeight =16;
g_dwRandomSeed =314159;
hr = g_MazeSim.Init10(g_dwRandomSeed,pd3dDevice);

return S_OK;
}

Above the Init10 method on CMazeSim is called from the OnD3D10CreateDevice callback.

For rendering, the CMazeSim class completely encapsulates this, as follows:

void CALLBACK OnD3D10FrameRender( ID3D10Device* pd3dDevice,
double fTime, float fElapsedTime,
void* pUserContext )
{

//scale and pos the maze
D3DXMatrixIdentity(&mWorldViewProj);
D3DXMATRIX mWorldMaze, mWorldRot,mWorldScale;
D3DXMatrixTranslation( &mWorldMaze, 0.0f, 0.1f, 0.0f );
D3DXMatrixMultiply( &mWorldViewProj, &mView, &mProj );
D3DXMatrixMultiply( &mWorldViewProj, &mWorldMaze,
&mWorldViewProj );


// Render the maze
g_MazeSim.DrawMaze10(pd3dDevice, mView, mWorldViewProj,
fTime, fElapsedTime);

}

For resource releasing, again CMazeSim and method Stop10 encapsulates the work:

void CALLBACK OnD3D10DestroyDevice( void* pUserContext )
{

//destroy the internal maze
g_MazeSim.Stop10();
}

Callbacks and Maze Rendering-D3D9

We have some extern’ed global state and an instance of the CMazeSim class.

extern CMazeSim g_MazeSim;
extern DWORD g_nMazeSize,g_nMazeWidth,g_nMazeHeight,g_nMazeCells;
extern DWORD g_dwRandomSeed;

And the Init9 method on CMazeSim is called from the OnD3D9CreateDevice callback.

HRESULT CALLBACK OnD3D9CreateDevice( IDirect3DDevice9* pd3dDevice,
const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
void* pUserContext )
{

//init internal maze representation
g_nMazeSize =16;
g_nMazeWidth =16;
g_nMazeHeight =16;
g_dwRandomSeed =314159;
hr = g_MazeSim.Init9(g_dwRandomSeed,pd3dDevice);

return S_OK;
}

Now we have a slight difference between the D3D10 and the D3D9 implementation, in that for D3D9 we have OnD3D9ResetDevice handling to call CMazeSim::Reset9 as follows:

HRESULT CALLBACK OnD3D9ResetDevice( IDirect3DDevice9* pd3dDevice,
const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
void* pUserContext )
{

//maze
hr = g_MazeSim.Reset9(g_dwRandomSeed,pd3dDevice);

return S_OK;
}

for resources in the default video memory pool.

Rendering is very similar, invoking the D3D9 version of DrawMaze:

void CALLBACK OnD3D9FrameRender( IDirect3DDevice9* pd3dDevice,
double fTime, float fElapsedTime,
void* pUserContext )
{ …

//scale and pos the maze
D3DXMatrixIdentity(&mWorldViewProj);
D3DXMATRIX mWorldMaze, mWorldRot,mWorldScale;
D3DXMatrixTranslation( &mWorldMaze, 0.0f, 0.1f, 0.0f );
D3DXMatrixMultiply( &mWorldViewProj, &mView, &mProj );
D3DXMatrixMultiply( &mWorldViewProj, &mWorldMaze,
&mWorldViewProj );

// Render the maze
g_MazeSim.DrawMaze9(pd3dDevice, mView, mWorldViewProj,
fTime, fElapsedTime);


}

Now we need to clean up, which again shows a slight difference from the D3D10 code to clean up default pool resources in OnD3D9LostDevice to call CMazeSim::Unset9:

void CALLBACK OnD3D9LostDevice( void* pUserContext )
{

//maze
g_MazeSim.Unset9();

}

And then we have similar code for OnD3D9DestroyDevice to call CMazeSim::Stop9:

void CALLBACK OnD3D9DestroyDevice( void* pUserContext )
{

//destroy the internal maze
g_MazeSim.Stop9();

}

Conclusion

With that we now have a basic maze renderer, as shown in the two screen captures below:

D3D10


D3D9

Here is a link to the code project. And here is a
link to a doc with properly formattted code blocks.

Although we still lack a solver, better camera controls, animated characters, and more interesting rendering. That is to come in the future articles.

Look for the solver article around the end of January.