Texture not showing, app error

Started by
7 comments, last by DesCr 19 years, 10 months ago
I''m completely lost. I''ve been staring at this code for 10+ hours and now I regret not going to sleep because I still haven''t figured it out. Suffice to say, I''m pretty pissed at people that write tutorials and don''t explain everything or act as if everybody''s game is going to be in 1 giant source file. Anyways, here''s the code. It compiles just fine but my texture isn''t showing up (splash.tga) and when I close the window I get an uninformative error. CTexture.cpp

//-------------------------------------------------------Chronicles of Lorium--

// CTexture.cpp

//		The CTexture class functions.

//-----------------------------------------------------------------------------


#include "CTexture.h"
#include "dx.h"

// Static member initialization

list <CTexture::LOADEDTEXTURE*> CTexture::loadedTextures;

// Load texture from file.

int CTexture::Init ( string sFilename ) {

	D3DSURFACE_DESC surfaceDesc;
	LOADEDTEXTURE* newTexture;
	list<LOADEDTEXTURE*>::iterator itTextures;

	// Make sure the texture is not already loaded.

	if(bLoaded)
		return FALSE;

	// Convert filename to lowercase letters.

	sFilename = strlwr( (char*)sFilename.c_str() );

	// Check if texture is on the loaded list.

	for( itTextures = loadedTextures.begin(); itTextures != loadedTextures.end(); itTextures++)
		if ( (*itTextures)->sFilename == sFilename) {

			// Get LOADEDTEXTURE object.

			texture = *itTextures;

			// Increment reference counter.

			(*itTextures)->referenceCount++;

			// Set loaded flag.

			bLoaded = true;

			// Successfully found texture

			return TRUE;
		}

		// Texture was not in the list, make a new texture.

		newTexture = new LOADEDTEXTURE;

		// Load texture from file.

		newTexture->texture = LoadTexture ( (char*)sFilename.c_str() );

		// Make sure texture was loaded

		if (!newTexture->texture)
			return FALSE;

		// Get texture dimensions

		newTexture->texture->GetLevelDesc( 0, &surfaceDesc);

		// Set new texture paramaters

		newTexture->referenceCount = 1;
		newTexture->sFilename = sFilename;
		newTexture->width = surfaceDesc.Width;
		newTexture->height = surfaceDesc.Height;

		// Push new texture onto list

		loadedTextures.push_back (newTexture);

		// Setup current texture instance

		texture = loadedTextures.back();

		// Successfully loaded texture

		return TRUE;
}

int CTexture::Close() {

	// Make sure texture is loaded.

	if(!bLoaded)
		return FALSE;

	// Decrement reference counter and nullify pointer.

	texture->referenceCount--;
	texture = NULL;

	// Clear loaded flag.

	bLoaded = FALSE;

	// Successfull unloaded texture

	return TRUE;
}

//Release all textures

int CTexture::CleanupTextures()
{
    list<LOADEDTEXTURE*>::iterator it;
 
     //Go through loaded texture list

    for (it = loadedTextures.begin(); it != loadedTextures.end (); it++)
    {
        //Release texture

        if ((*it)->texture)
            (*it)->texture->Release();
        (*it)->texture = NULL;
        
         //Delete LOADEDTEXTURE object

        delete (*it);
    }

    //Clear list

    loadedTextures.clear ();

    //Successfully released all textures

    return TRUE;
}

//Draw texture.

void CTexture::Blit (int X, int Y, D3DCOLOR vertexColor, float rotate)
{
    RECT rDest;

    //Setup destination rectangle

    rDest.left = X;
    rDest.right = X + texture->width;
    rDest.top = Y;
    rDest.bottom = Y + texture->height;

    //Draw texture

    BlitD3D(texture->texture, &rDest, vertexColor, rotate);
}
d3dinit.cpp

//-------------------------------------------------------Chronicles of Lorium--

// d3dinit.cpp

//		Initializes Direct3D, a rendering device, and a vertex buffer.

//-----------------------------------------------------------------------------


#include "dx.h"
#include "CTexture.h"

IDirect3D9*				g_pD3D; // Direct3D interface.

IDirect3DDevice9*		g_pd3dDevice; // Graphics adapter.

IDirect3DVertexBuffer9* g_pVB; // Buffer to hold vertices.


//-----------------------------------------------------------------------------

// Name: InitD3D()

// Desc: Initializes Direct3D

//-----------------------------------------------------------------------------

HRESULT InitD3D( HWND hWnd ) {
	
	// Create the Direct3D object which is needed to create the D3DDevice.

	if( FAILED( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION) ) ) {
		MessageBox( hWnd, "Could not create a Direct3D9 object.", "Direct3DCreate9", MB_OK);
		return E_FAIL;
	}

	// Set up the structure used to create the D3DDevice. Most parameters are

	// zeroed out. We set Windowed to TRUE, since we want to do D3D in a

	// window, and then set the SwapEffect to "discard", which is the most

	// efficient method of presenting the back buffer to the display.  And 

	// we request a back buffer format that matches the current desktop display 

	// format.

	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory( &d3dpp, sizeof(d3dpp) );
	d3dpp.Windowed = TRUE;
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;

	// Create the Direct3D device. Here we are using the default adapter (most

	// systems only have one, unless they have multiple graphics hardware cards

	// installed) and requesting the HAL (which is saying we want the hardware

	// device rather than a software one). Software vertex processing is 

	// specified since we know it will work on all cards. On cards that support 

	// hardware vertex processing, though, we would see a big performance gain 

	// by specifying hardware vertex processing.

	if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
									 D3DCREATE_SOFTWARE_VERTEXPROCESSING,
									 &d3dpp, &g_pd3dDevice ) ) ) {
		MessageBox( hWnd, "Could not create a Direct3D9 object.", "Direct3DCreate9", MB_OK);
		return E_FAIL;
	}

	return S_OK;
}

//-----------------------------------------------------------------------------

// Name: InitVB()

// Desc: Creates a vertex buffer and fills it with our vertices. The vertex

//       buffer is basically just a chuck of memory that holds vertices. After

//       creating it, we must Lock()/Unlock() it to fill it. For indices, D3D

//       also uses index buffers. The special thing about vertex and index

//       buffers is that they can be created in device memory, allowing some

//       cards to process them in hardware, resulting in a dramatic

//       performance gain.

//-----------------------------------------------------------------------------

HRESULT InitVB() {
	
	// Set render states

	if( FAILED( g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE) ) )						return E_FAIL;
	if( FAILED( g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE) ) )				return E_FAIL;
	if( FAILED( g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA) ) )			return E_FAIL;
	if( FAILED( g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA) ) )		return E_FAIL;
	if( FAILED( g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE) ) )	return E_FAIL;

	// Set the vertex shader.

	if( FAILED( g_pd3dDevice->SetVertexShader( NULL ) ) )		return E_FAIL;
	if( FAILED( g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ) ) )	return E_FAIL;

	// Create the vertex buffer.

	if( FAILED( g_pd3dDevice->CreateVertexBuffer( sizeof(SQUAREVERTEX) * 4, NULL, D3DFVF_CUSTOMVERTEX,
												  D3DPOOL_MANAGED, &g_pVB, NULL ) ) )
		return E_FAIL;
	if( FAILED( g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof(SQUAREVERTEX) ) ) )
		return E_FAIL;
	
	
	return S_OK;
}

//-----------------------------------------------------------------------------

// Name: Cleanup()

// Desc: Releases all previously initialized objects

//-----------------------------------------------------------------------------

VOID Cleanup()
{
	CTexture::CleanupTextures();

	g_pd3dDevice->SetStreamSource( 0, NULL, 0, 0 );

	if( g_pVB != NULL )        
		g_pVB->Release();

    if( g_pd3dDevice != NULL) 
        g_pd3dDevice->Release();

    if( g_pD3D != NULL)
        g_pD3D->Release();

}

//-----------------------------------------------------------------------------

// Name: Render()

// Desc: Draws the scene

//-----------------------------------------------------------------------------

VOID Render()
{
    if( NULL == g_pd3dDevice )
        return;

    // Clear the backbuffer to a blue color

    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );
    
    // Begin the scene

    if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
    {
		// Draw the triangles in the vertex buffer. This is broken into a few

		// steps. We are passing the vertices down a "stream", so first we need

		// to specify the source of that stream, which is our vertex buffer. Then

		// we need to let D3D know what vertex shader to use. Full, custom vertex

		// shaders are an advanced topic, but in most cases the vertex shader is

		// just the FVF, so that D3D knows what type of vertices we are dealing

		// with. Finally, we call DrawPrimitive() which does the actual rendering

		// of our geometry (in this case, just one triangle).

		g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof(SQUAREVERTEX) );
		g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
		g_pd3dDevice->DrawPrimitive( D3DPT_LINESTRIP, 0, 1 );

		// End the scene

        g_pd3dDevice->EndScene();
    }

    // Present the backbuffer contents to the display

    g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}

//-----------------------------------------------------------------------------

// Name: *LoadTexture()

// Desc: Loads a texture from a file using D3DX. Supported formats include

//		 BMP, PPM, DDS, JPG, PNG, TGA, DIB. Returns a pointer to a texture.

//-----------------------------------------------------------------------------


IDirect3DTexture9 *LoadTexture( char *fileName ) {
	IDirect3DTexture9 *d3dTexture;
	D3DXIMAGE_INFO SrcInfo; // Optional


	// Use a magenta colorkey.

	D3DCOLOR colorkey = 0xFFFF00FF;

	// Load image from file.

	if ( FAILED( D3DXCreateTextureFromFileEx( g_pd3dDevice, fileName, 0, 0, 1, 0,
				 D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_FILTER_NONE, D3DX_DEFAULT,
				 colorkey, &SrcInfo, NULL, &d3dTexture ) ) )
			return NULL;

	return d3dTexture;
}

//-----------------------------------------------------------------------------

// Name: BlitExD3D

// Desc: Draws a textured quad on the back buffer.

//-----------------------------------------------------------------------------

HRESULT BlitD3D( IDirect3DTexture9 *texture, RECT *rDest, D3DCOLOR vertexColor, float rotate ) {
	SQUAREVERTEX* vertices;

	// Lock the vertex buffer

	g_pVB->Lock( 0, 0, (void**)&vertices, NULL);

	// Setup vertices

	// A -0.5f modifier is applied to vertex coordinates to match texture

	// and screen coords. Some drivers may compensate for this

	// automatically, but on others texture alignment errors are introduced

	// More information on this can be found in the Direct3D 9 documentation

	vertices[0].color = vertexColor;
	vertices[0].x = (float) rDest->left - 0.5f;
	vertices[0].y = (float) rDest->top - 0.5f;
	vertices[0].z = 0.0f;
	vertices[0].rhw = 1.0f;
	vertices[0].u = 0.0f;
	vertices[0].v = 0.0f;

	vertices[1].color = vertexColor;
	vertices[1].x = (float) rDest->right - 0.5f;
	vertices[1].y = (float) rDest->top - 0.5f;
	vertices[1].z = 0.0f;
	vertices[1].rhw = 1.0f;
	vertices[1].u = 1.0f;
	vertices[1].v = 0.0f;


	vertices[2].color = vertexColor;
	vertices[2].x = (float) rDest->right - 0.5f;
	vertices[2].y = (float) rDest->bottom - 0.5f;
	vertices[2].z = 0.0f;
	vertices[2].rhw = 1.0f;
	vertices[2].u = 1.0f;
	vertices[2].v = 1.0f;


	vertices[3].color = vertexColor;
	vertices[3].x = (float) rDest->left - 0.5f;
	vertices[3].y = (float) rDest->bottom - 0.5f;
	vertices[3].z = 0.0f;
	vertices[3].rhw = 1.0f;
	vertices[3].u = 0.0f;
	vertices[3].v = 1.0f;

	// Handle rotation.

	if (rotate != 0) {
		RECT rOrigin;
		float centerX, centerY;
		int index = 0;

		// Find center of destination rectangle.

		centerX = (float)(rDest->left + rDest->right) / 2;
		centerY = (float)(rDest->top + rDest->bottom) / 2;

		// Translate destination rect to be centered on the origin.

		rOrigin.top = rDest->top - (int)(centerY);
		rOrigin.bottom = rDest->bottom - (int)(centerY);
		rOrigin.left = rDest->left - (int)(centerX);
		rOrigin.right = rDest->right - (int)(centerX);

		// Rotate vertices about the origin.

		vertices[index].x = rOrigin.left * cosf(rotate) - rOrigin.top * sinf(rotate);
		vertices[index].y = rOrigin.left * cosf(rotate) - rOrigin.top * sinf(rotate);

		vertices[index + 1].x = rOrigin.right * cosf(rotate) - rOrigin.top * sinf(rotate);
		vertices[index + 1].y = rOrigin.right * cosf(rotate) - rOrigin.top * sinf(rotate);

		vertices[index + 2].x = rOrigin.right * cosf(rotate) - rOrigin.bottom * sinf(rotate);
		vertices[index + 2].y = rOrigin.right * cosf(rotate) - rOrigin.bottom * sinf(rotate);

		vertices[index + 3].x = rOrigin.left * cosf(rotate) - rOrigin.bottom * sinf(rotate);
		vertices[index + 3].y = rOrigin.left * cosf(rotate) - rOrigin.bottom * sinf(rotate);

		// Translate vertices to proper position

		vertices[index].x += centerX;
		vertices[index].y += centerY;

		vertices[index + 1].x += centerX;
		vertices[index + 1].y += centerY;

		vertices[index + 2].x += centerX;
		vertices[index + 2].y += centerY;

		vertices[index + 3].x += centerX;
		vertices[index + 3].y += centerY;
	}

		// Unlock the vertex buffer

		if( FAILED( g_pVB->Unlock() ) )
			return E_FAIL;

		// Set texture

		g_pd3dDevice->SetTexture( 0, texture );

		// Draw image

		g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2);

		return S_OK;
}
lsplash.cpp

//-------------------------------------------------------Chronicles of Lorium--

// lsplash.cpp

//		Displays the lorium engine splash screen

//-----------------------------------------------------------------------------


#include <windows.h>
#include "dx.h"
#include "lorium.h"
#include "CTexture.h"

HRESULT LoriumSplash () {
	if( NULL == g_pd3dDevice )
		return E_FAIL;

	CTexture txSplash;
	txSplash.Init("splash.tga");

	txSplash.Blit(512, 512, 0xffffffff, 0);
	

	// Clear the backbuffer to a black color

    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );

	// Begin the scene.

    if( SUCCEEDED( g_pd3dDevice->BeginScene() ) ) {
		
		// Set the stream source.

		g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof(SQUAREVERTEX) );

		// Let D3D know what vertex shader to use

		g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );

		// Render geometry.

		g_pd3dDevice->DrawPrimitive( D3DPT_LINESTRIP, 0, 1 );

		// End the scene

        g_pd3dDevice->EndScene();

		// Present the backbuffer contents to the display

		g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
	}

	return S_OK;
}
winmain.cpp

//-------------------------------------------------------Chronicles of Lorium--

// winmain.cpp

//		The WinMain function (entry point) and the

//		Windows message handler.

//-----------------------------------------------------------------------------


#include <windows.h>
#include "lorium.h"
#include "dx.h"
#include "CTexture.h"
#define WIN32_LEAN_AND_MEAN

HRESULT LoriumSplash ();


//-----------------------------------------------------------------------------

// Name: MsgProc()

// Desc: The window''s message handler

//-----------------------------------------------------------------------------

LRESULT CALLBACK MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) {
	switch( msg )
	{
		case WM_DESTROY:
			Cleanup();
			PostQuitMessage( 0 );
			return 0;

		case WM_PAINT:
			LoriumSplash();
			ValidateRect( hWnd, NULL );
			return 0;
	}

	return DefWindowProc( hWnd, msg, wParam, lParam );
}

//-----------------------------------------------------------------------------

// Name: WinMain()

// Desc: The application''s entry point

//-----------------------------------------------------------------------------

INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT ) {

	// Register the window class.

	WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
							GetModuleHandle(NULL), NULL, NULL, NULL, NULL, 
							"Lorium Engine", NULL };
	RegisterClassEx( &wc );

	// Create the Window

	HWND hWnd = CreateWindow( "Lorium Engine", "Lorium Engine ver 0.1",
							  WS_OVERLAPPEDWINDOW, 100, 100, 800, 600,
							  GetDesktopWindow(), NULL, wc.hInstance, NULL );

	// Initialize Direct3D.

	if( ( SUCCEEDED( InitD3D (hWnd) ) ) && ( SUCCEEDED( InitVB() ) ) ) {

		// Show the window.

		ShowWindow( hWnd, SW_SHOWDEFAULT );
		UpdateWindow( hWnd );

		// Enter the message loop.

		MSG msg;
		while(TRUE) {

			// Handle messages.

			if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) {
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			
			// GameMain will go here

			// LoriumSplash will take a texture.

			LoriumSplash();
		}
	}

	UnregisterClass( "Lorium Engine", wc.hInstance );
	return 0;
}
CTexture.h

//-------------------------------------------------------Chronicles of Lorium--

// CTexture.h

//		The CTexture class Prototype.

//-----------------------------------------------------------------------------


#pragma once

#include <windows.h>
#include <assert.h>
#include <string>
#include <sstream>
#include <list>
#include <d3d9.h>
#include <d3dx9.h>
using namespace std;

class CTexture
{
public:

    //Set default member values

    CTexture()
    {
        bLoaded = FALSE;
        texture = NULL;
    }

    //Load texture from file

    int CTexture::Init (string sFilename);

    //Unload a texture

    int CTexture::Close();

    //Draw texture with limited colour modulation

    void CTexture::Blit (int X, int Y, D3DCOLOR vertexColour = 0xFFFFFFFF, float rotate = 0);

    //Draw texture with full colour modulation

    void CTexture::BlitEx (int X, int Y, D3DCOLOR* vertexColours, float rotate);

    //Release all unreferenced textures

    static int GarbageCollect();

    //Release all unreferenced textures

    static int CleanupTextures();

private:

    //Loaded texture struct

    struct LOADEDTEXTURE
    {
        int referenceCount;             //Amount of CTexture instances containing this texture

        IDirect3DTexture9* texture;     //The texture

        string sFilename;               //The filename of the texture

        int width;                      //Width of the texture

        int height;                     //Height of the texture

    };

    //Linked list of all loaded textures

    static list <LOADEDTEXTURE*> loadedTextures;

    BOOL bLoaded;           //Texture loaded flag

    LOADEDTEXTURE* texture; //The texture




};
dx.h

//-------------------------------------------------------Chronicles of Lorium--

// dx.h

//		Contains globals and function prototypes for DirectX.

//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------

// Dependencies.

//-----------------------------------------------------------------------------

#include <d3d9.h>
#include <d3dx9.h>

extern IDirect3D9*				g_pD3D; // Direct3D interface.

extern IDirect3DDevice9*		g_pd3dDevice; // Graphics adapter.

extern IDirect3DVertexBuffer9* g_pVB; // Buffer to hold vertices.


//-----------------------------------------------------------------------------

// Function prototypes.

//-----------------------------------------------------------------------------

HRESULT InitD3D( HWND hWnd );
HRESULT InitVB();
IDirect3DTexture9 *LoadTexture( char *fileName );
HRESULT BlitD3D( IDirect3DTexture9 *texture, RECT *rDest, D3DCOLOR vertexColor, float rotate );

//-----------------------------------------------------------------------------

// Structures.

//-----------------------------------------------------------------------------

struct SQUAREVERTEX {
    float x, y, z, rhw; // The transformed position for the vertex.

    D3DCOLOR color;     // The vertex color (32 bit AARRGGBB).

	float u, v;			// The texture coordinates of the vertex.

};

// Our custom FVF, which describes our custom vertex structure

const DWORD D3DFVF_CUSTOMVERTEX = D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1;
lorium.h

//-------------------------------------------------------Chronicles of Lorium--

// lorium.h

//		Contains globals and function prototypes for the game.

//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------

// Function prototypes.

//-----------------------------------------------------------------------------

void Cleanup();
void Render();
Advertisement
Anybody? I''m losing way too much sleep over this!
The texture loader may expect a full path to the texture.

Niko Suni

If you set up the debug runtime it may give you more useful feedback. Also, link with the debug version of d3dx.

Stay Casual,

Ken
Drunken Hyena
Stay Casual,KenDrunken Hyena
quote:Original post by DrunkenHyena
If you set up the debug runtime it may give you more useful feedback. Also, link with the debug version of d3dx.

Stay Casual,

Ken
Drunken Hyena


Well I link d3d9.lib and d3dx9.lib and include the headers. Is there something else I''m supposed to do to get debug versions?

Also, I''m open for suggestions if anyone knows a good tutorial on 2D in Direct3D9. I read the one here at GameDev, which has led to the above problem. I don''t think he tested his code.
First, why are you rendering line strips? Second, your UV coords are in the wrong order if you want to draw a triangle strip. It should be (0,0) (1,0) (0,1) (1,1). If you''re trying to draw a triangle fan, disregard that second comment.

GDNet+. It's only $5 a month. You know you want it.

That was a change someone told me to make. It''s back to trianglefan now, but still having the same problems.
I feel so dumb. Creating the texture and blitting it within the Render() function did the trick. I''m still getting the shutdown error, and I believe I''m freeing all my pointers...
quote:Original post by DesCr
Well I link d3d9.lib and d3dx9.lib and include the headers. Is there something else I''m supposed to do to get debug versions?


To get the debug version of D3DX, link with d3dx9d.lib instead of d3dx9.lib.

To enable the debug version of D3D itself ("the debug runtimes"), go to the DirectX applet in the Control Panel and select "Use Debug Version of Direct3D" under the Direct3D tab.
Additionally you can control the amount of debug information you get using the "Debug Output Level" slider and the "Maximum Validation" option.

You can find more information about the debug runtimes in the FAQ for this forum and on DrunkenHyena''s web site (click his signature).


Once you''re using the debug D3D runtimes and debug D3DX, they will provide more informative error descriptions and warnings for anything you may be doing wrong. These messages will be sent to the Output window in MSVC when you debug the program (press F5 in MSVC with the default key layout). Alternatively you can capture them with dbmon.exe in the DirectX SDK or with DebugView on the sysinternals.com site.




Simon O''Connor
Game Programmer &
Microsoft DirectX MVP

Simon O'Connor | Technical Director (Newcastle) Lockwood Publishing | LinkedIn | Personal site

This topic is closed to new replies.

Advertisement