This is a very long, very long, very long tutorial about how to get started with D3D in C++.
//////////////////////////////////////////
// //
// Direct3D basics //
// //
// You found this at bobobobo's weblog, //
// https://bobobobo.wordpress.com //
// //
// Creation date: July 1/09 (HAPPY CANADA DAY! ) //
// Last modified: July 3/09 //
// //
//////////////////////////////////////////
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <d3d9.h> // core direct3d
#include <d3dx9.h> // aux libs
#include <dxerr9.h> // detailed error messages
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib") // aux libs
#ifdef _DEBUG
#pragma comment(lib,"d3dx9d.lib")
#else
#pragma comment(lib,"d3dx9.lib")
#endif
#pragma comment(lib, "dxerr9.lib")
// Macros.
#define SAFE_RELEASE(ptr) if(ptr) { ptr->Release(); ptr = NULL; }
#define CAST_AS_DWORD(x) *((DWORD*)&x)
#define PI 3.14159
struct Globals
{
// Wrapping the Win32-related variables up
// together in their own struct so they don't
// get in the way of the Direct3D ones
struct _Win
{
HINSTANCE hInstance; // window app instance
HWND hwnd; // handle for the window
HWND hConsole ; // handle for the console window
int width, height; // the desired width and
// height of the CLIENT AREA
// (DRAWABLE REGION in Window)
} win ;
#pragma region DIRECT3D9 stuff
////////////////////////////
// Declare The IDirect3D9 INTERFACE!!
// IDirect3D9 interface
IDirect3D9 * d3d ; // represents the BEAST
// that is Direct3D9 itself. What's it for?
// MSDN SAYS about the IDirect3D9 interface:
// "Applications use the methods of the IDirect3D9 interface
// to create Microsoft Direct3D objects and set up the
// environment. This interface includes methods for
// enumerating and retrieving capabilities of the device."
// IDirect3DDevice9
IDirect3DDevice9 * gpu ; // represents the GPU
// MSDN SAYS about the IDirect3DDevice9: "Applications use the
// methods of the IDirect3DDevice9 interface to perform
// DrawPrimitive-based rendering, create resources, work
// with system-level variables, adjust gamma ramp levels,
// work with palettes, and create shaders."
////////////////////////////
// The ABOVE TWO variables are
// very important to this application.
// For both of these, notice how they
// are BOTH POINTERS to an INTERFACE.
// What's an interface? Well, in a few words,
// an INTERFACE is something you "face" to
// interact with something. Thru means of
// the "INTERFACE" you get information from
// the underlying system, or send commands
// to the underlying system, without really
// having to understand the underlying system
// at all to do it. You just have to know what
// types of commands it expects to get.
// For example, your car.
// The "interface" of your car is its steering wheel,
// its dials on the dash telling you what speed
// you're going and the RPM's you're at so you
// don't blow the engine redlining, and also,
// the gas and brakes, so you can send commands
// to the car to stop and go.
// Notice how you don't have to know a THING
// about how an internal combustion engine works
// to get the car to go. Because the car's INTERFACE
// is SO abstract (simply PUSH THE PEDAL TO GO),
// working the car becomes incredibly simple.
// If the car didn't have such an abstract
// interface (like, if REALLY crummy engineers
// made a car), then to drive that crummy car,
// you might have to put your hands into
// the engine and carefully push
// vaporized gas underneath a piston, then
// push down on the piston until it goes POP!
// Then the car would be going!
// Anyway, point is, working with a system
// THROUGH ITS INTERFACE that the system defines
// makes working with the system SO easy, and
// the system itself is a black box -- its internal
// workings are hidden from you. Like, you
// don't even have to know what an internal
// combustion engine IS to be able to
// work a modern car. Heck, for all you know,
// it might not even be an internal combustion
// engine! (It might be electric). That's another
// beauty about interfaces: You can swap out
// the nitty gritty details of the implementation
// (e.g. software updates / patches, or the
// difference between Direct3D9 June 2008 release
// and March 2009 release) without affecting
// the programs that USE those interfaces, so long
// as you have not CHANGED the interface itself.
// IN the case of Direct3D9, an IDirect3DDevice9 *
// is a pointer to, let's just say the "dashboard" ON TOP
// OF the Direct3D9 RENDERING MACHINE.
// Inside, the Direct3D 3D graphics "engine" is VERY complex!
// Especially when you get to rendering textures in 3D..
// (see this book if you want to learn that stuff!)
// But with Direct3D9, you don't have to understand
// HOW 3d graphics actually gets drawn. You only have to
// understand the format that D3D expects, and pass it
// your data that you want drawn in that format.
// OK? So hopefully that made a little bit of sense.
// To me Direct3D seems slightly more "centralized"
// than OpenGL does. With OpenGL, the "interface"
// is a set of C-style functions that all begin
// with gl*. OpenGL doesn't have an "INTERFACE"
// in the OOP sense (but that set of gl* C functions
// is still an 'interface' though, its just a very
// different way of creating one!)
// Anyway, on with it.
#pragma endregion
};
///////////////////////////
// GLOBALS
// declare one struct Globals called g;
Globals g;
//
///////////////////////////
///////////////////////////
// FUNCTION PROTOTYPES
// Windows app functions. If need help
// understanding these, see MostBasicWindow
// and FastWindowsProgram
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam );
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow );
inline bool CHECK( HRESULT hr, char * msg, bool stop=true ) ; // checks for errors on the HR passed.
bool initD3D() ; // function to initialize the BEAST that is Direct3D9
void printSystemInfo() ; // function that prints some system info. can ignore if not interested
void draw() ; // drawing function containing Direct3D drawing calls
//
///////////////////////////
/// If there was an error, the ErrorString is printed out for you to see at the console.
inline bool CHECK( HRESULT hr, char * msg, bool stop )
{
if( FAILED( hr ) )
{
printf( "%s. %s: %s\n",
msg, DXGetErrorString9A( hr ), DXGetErrorDescription9A( hr ) ) ;
// Pause so we can see the error and deal with it.
if( stop ) system("pause") ;
return false ;
}
else
return true ;
}
///////////////////////////
// FUNCTION IMPLEMENTATIONS
/// Initializes Direct3D9. Returns true on success.
bool initD3D()
{
// start by nulling out both pointers:
g.d3d = 0 ;
g.gpu = 0 ;
// Create the IDirect3D9 (d3d) object now. We need the d3d object
// in order to be able to create the IDirect3DDevice9 interface.
// We can also use the d3d object to find out additional information
// about the system we are on (such as the number of displays it has, etc).
// [[ I know, I know. If you think its confusing/stupid to have
// two separate Direct3D9 things both starting with I,
// just bear with it because you'll see that these two objects
// will do VERY different things, and you'll see it does
// make a whole lot of sense to separate them out into two things.
// Granted, they could have been named a little more distinctly,
// but whaddya gonna do... ]]
// So, the IDirect3D9 object (variable 'd3d') is the "interface"
// or means by which we will access the Direct3D9
// beast
// And the IDirect3DDevice9 (called 'gpu') is THE
// variable that IS OUR PROGRAMMATIC HANDLE TO THE GPU and
// we will use it A LOT to send down triangles and stuff to the
// gpu to be drawn.
// we'll be using 'gpu' a lot more than 'd3d'.
// So now, to create our 'd3d' interface.
// Remember, FROM this interface will come
// our 'gpu' interface.
// Direct3DCreate9
g.d3d = Direct3DCreate9( D3D_SDK_VERSION ) ; // Always use D3D_SDK_VERSION
if( g.d3d == NULL )
{
// DEVICE CREATION FAILED!!!! OH NO!!!
puts( "Oh.. PHOOEY!!!!! Device creation FAILED!!! WHAT NOW???\n" ) ;
return false ;
}
// Ok, if we get here without returning, it means device creation succeeded.
puts( "Device creation SUCCESS!!!!\nWe're in business now, son..\n" ) ;
// Next we'll just print a few details about the system
// just because we can..
// Note how we use PURELY the 'd3d' object
// to do this (NOT the 'gpu' device, which hasn't
// even been created yet!)
///// printf( "I will now tell you some \nthings ABOUT your system.\n" );
///// printSystemInfo(); // BOOOOORING.
puts( "Ok, Now to CREATE the 'gpu' device!!" ) ;
// First, create the D3DPRESENT_PARAMETERS structure.
// This structure will basically "explain" to the
// IDirect3D9 interface EXACTLY WHAT we want the
// GPU rendering surface to look like once
// it has been created.
// D3DPRESENT_PARAMETERS structure
D3DPRESENT_PARAMETERS pps = { 0 } ; // Start structure all 0'd out.
// We're using a windowed mode, NOT fullscreen in this
// example. Its really annoying to program little test
// apps in fullscreen mode. Also when using windowed mode
// we don't have to (in fact, we should not) specify
// some of the other parameters, such as the refresh rate.
pps.Windowed = true ;
// How many backbuffers do we want? One.
pps.BackBufferCount = 1 ;
// This one's interesting
// Backbuffering. Imagine Leonardo Davinci doing
// a live animation for you.
// Imagine he stood in front of the canvas,
// and blazingly quickly, painted a live scene for you.
// Then to make the animation effect happen, he'd have
// to erase the canvas (paint over it in all white?) then
// paint over that white the next frame.
// Not that great for watching an "animation!" even if
// he moved blazing fast, it'd still be "flickery" having
// to "see" the canvas get all cleared out, then see each
// shape get drawn on.
// So instead, Davinci has a better idea. He will
// use TWO canvases. He will draw to the canvas
// in a HIDDEN place, where you can't see it.
// When he's done painting hte first frame, BAM,
// he slams it in your face and you can see it.
// He then takes a SECOND canvas, and paints to it
// blazing fast, what should be the next frame you see.
// Then, BAM, he slams that second canvas right in your face,
// where you can see it. He then quietly pulls away
// that first canvas that had the first frame on it
// (which you can't see anymore, because you're looking
// at the SECOND canvas he just slammed in your face),
// and quickly paints the NEXT (3rd) frame onto it.
// Then, BAM, he slams that first canvas in your face
// again, but now it has the 3rd frame on it. He then
// takes the SECOND canvas, and draws the 4th frame on it...
// Do you see what's happening here? Read it again
// if not... the whole point is to give a MUCH smoother
// and continuous presentation of image frames. If you
// didn't use a backbuffer, then the animation presented
// would look all awful and flickery and horrible because
// you'd basically be WATCHING the gpu do its drawing work,
// instead of looking at nice finished product painted scenes instead.
// Swap chains
pps.SwapEffect = D3DSWAPEFFECT_DISCARD ; // You start with
// 2 buffers, one that displays what you're currently
// looking at (the "frontbuffer") and
// one that is hidden from you (the "backbuffer").
// Basically FLIP means that
// d3d should DRAW to the BACKBUFFER first, then
// when its done doing that, it should BAM, slam
// that backbuffer in your face, so you can see it.
// The former front buffer, is then DISCARDED.
// SO, the former "backbuffer" is NOW the FRONTBUFFER.
// And the former front buffer, we will treat
// as the NEW "backbuffer".
// SO draw to the OTHER buffer now (which is considered
// as the backbuffer at the moment), when you're done,
// BAM, slam that backbuffer in the user's face
// (so again, the former "backbuffer" has flipped
// and become the frontbuffer).
// The OTHER way to do this is to DRAW TO
// the backbuffer, then to COPY OUT the backbuffer
// in its entirety to the front buffer. That's
// less efficient though, for obvious reasons
// (copying about 2,000,000 pixels has gotta take
// at least some time!). Discard is nice, but
// using it REQUIRES that you completely update
// ALL the pixels of the backbuffer before presenting it
// (because there's NO TELLING what will happen to
// the frontbuffer once you've "discarded" it!
// Microsoft says "we might do anything to it..."
// The Clear() operation is sufficient to touch every
// pixel on the buffer, so as long as you're calling
// Clear() every frame, there's nothing to worry about.
// You'll see Clear() in use a bit later in this tutorial.)
// Pixel format to use on the backbuffer.
pps.BackBufferFormat = D3DFMT_UNKNOWN ; // So, this doesn't mean
// we don't KNOW the pixel format we want.. its more
// like saying "D3DFMT_CHOOSE_IT_FOR_ME!"
// What really happens is "color conversion is done by the hardware"
// But you can think of it as D3D should picking
// the appropriate pixel format to use.
// Now, we WANT Direct3D to create and manage
// a depth buffer for this surface. That means
// if we draw two triangles and one is "closer"
// than the other, then the "closer" one should
// be drawn on top of the "further" one. That's
// achieved using the depth buffer
pps.EnableAutoDepthStencil = true ;
// Now we have to say "how deep" is the depth buffer.
// Kind of. How precise are the depth values?
// The more bits you use, the more "slots of depth"
// your depth buffer can handle. If your depth buffer
// used 2 bits, it might be able to handle 4 levels
// of depth. With 16 bits, there's a lot more levels of
// depth. Having many different levels of depth is
// really important because if two objects are
// like thought by the gpu to be at the exact same
// "depth", then there will be "z-fighting"
// z-fighting sample1
// Apparently some users experienced problems
// with z-fighting when playing GTA 4 on ATI hardware.
// We choose a fairly "deep" pixel format (16 bits)
// Could also choose 24 bits.
pps.AutoDepthStencilFormat = D3DFMT_D16 ;
// Finally, make the gpu device.
HRESULT hr = g.d3d->CreateDevice(
D3DADAPTER_DEFAULT, // primary display adapter
D3DDEVTYPE_HAL, // use HARDWARE rendering (fast!)
g.win.hwnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING,
&pps,
&g.gpu // THIS IS where you get
// the ACTUAL return value (the
// GPU device!) from this function.
// Because this function has its
// return value as an HRESULT,
// the return value is used to
// indicate success or failure.
) ;
if( !CHECK( hr, "OH NOS!! I could not initialize Direct3D! Bailing...\n" ) )
{
return false ;
}
// Successfully created direct3d9 devices
printf( "WHOO! We SUCCESSFULLY created the Direct3D9 GPU device\n" ) ;
return true ;
}
////////////////////////
// DRAWING FUNCTION
void draw()
{
HRESULT hr ;
#pragma region clear
// First, we will clear the backbuffer. This is a VERY general use
// function and has way more capabilities than we care to use
// at the moment. For example, you can choose to clear little
// sub-rectangles of the screen ONLY instead of clearing the whole
// screen with the first 2 params. we're not interested in that though,
// we just wanna clear the whole screen.
// IDirect3DDevice9::Clear()
hr = g.gpu->Clear(
0, // NUMBER of sub rectangles to clear. We set to 0 because
// we don't want to even clear any subrectangles.. we just
// want to clear the WHOLE thing!
0, // you can choose to clear only a sub-region of the
// backbuffer. But we wanna clear the WHOLE back buffer!
// so we pass 0 here and d3d will automatically clear the WHOLE THING for us.
// D3DCLEAR
D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER /* | D3DCLEAR_STENCIL */, // next is weird, but
// here we specify WHAT exactly we want cleared. Because a 3d
// buffer is actually made of several LAYERS (color layer,
// and depth layer, and stencil layer), you choose
// what exactly what you want cleared. If you weren't
// doing 3d graphics for example (you used direct3d9 to draw
// 2d graphics, which you can!) you would have no need for
// the depthbuffer. So you could save a bit of time
// (clearing a buffer is a relatively expensive operation)
// by NOT clearing the buffers you aren't going to use.
// In this example we are NOT using the stencil buffer,
// but we ARE using the color BUFFER ITSELF (like color values)
// hence specification of D3DCLEAR_TARGET,
// and we ARE using the depth buffer (D3DCLEAR_ZBUFFER)
// but we are NOT using the stencil buffer (hence me omitting
// D3DCLEAR_STENCIL). There's no sense in clearing out a
// buffer that's not in use. That's like vaccumming a room
// that's just been vaccummed squeaky clean.
// If you're not going to be picking up any extra dirt,
// cleaning it again is really just a waste of time and energy.
D3DCOLOR_ARGB( 255, 125, 25, 237 ), // The color to clear
// the backbuffer out to.
1.0f, // value to clear the depth buffer to. we clear
// it to 1.0f because 1.0f means ("furthest away possible
// before being out of range")
// So we clear every value in the depth buffer to this
// value so when something is rendered that is in view range,
// it will definitely be closer than 1.0f!
// (0.0f means right in our faces).
0 // value to clear the stencil buffer to. since we
// chose NOT to clear the stencil buffer (omitted
// D3DCLEAR_STENCIL from above), this value is
// actually going to be ignored.
) ;
CHECK( hr, "Clear FAILED!" ) ;
#pragma endregion
#pragma region set up the camera
// First we start by setting up the viewport.
// the viewport "Defines the window dimensions of
// a render-target surface onto which a 3D volume projects."
D3DVIEWPORT9 viewport ;
// Very clear explanations of each (and advice! :) )
// of these members are on msdn.
viewport.X = 0 ;
viewport.Y = 0 ;
viewport.Width = g.win.width ;
viewport.Height = g.win.height ;
viewport.MinZ = 0.0f ;
viewport.MaxZ = 1.0f ;
g.gpu->SetViewport( &viewport ) ;
// Technically we don't need to set the viewport here
// BUT you can use viewport setting to draw "picture in picture" -
// Form the
// Set projection matrix
D3DXMATRIX projx ;
// Create a perspective matrix
D3DXMatrixPerspectiveFovRH( &projx, PI/4, (float)g.win.width/g.win.height, 1.0f, 1000.0f ) ;
// set
g.gpu->SetTransform( D3DTS_PROJECTION, &projx ) ;
// Create the view matrix
D3DXMATRIX viewx ;
D3DXVECTOR3 eye( 4, 2, 4 ) ;
D3DXVECTOR3 look( 0, 0, 0 ) ;
D3DXVECTOR3 up( 0, 1, 0 ) ;
D3DXMatrixLookAtRH( &viewx, &eye, &look, &up ) ;
g.gpu->SetTransform( D3DTS_VIEW, &viewx ) ;
#pragma endregion
// Preparing to draw
// FVF. WTF is an FVF?
// MSDN: "Flexible Vertex Format Constants, or FVF codes,
// are used to describe the contents of vertices
// interleaved in a single data stream that will
// be processed by the fixed-function pipeline."
// FVF stands for "FLEXIBLE VERTEX FORMAT". If you're familiar with
// OpenGL, this is a completely (but some might say a little bit better..)
// way of allowing a person to say WHAT data each vertex has tagged along
// with it.
// So let's tell Direct3D what data exactly each VERTEX
// will have. A position? A color? A normal? A texture
// coordinate? What do you WANT TO specify for each vertex.
hr = g.gpu->SetFVF(
D3DFVF_XYZ // THe most OBVIOUS (and most important?) aspect of a VERTEX is
// that it have a POSITION, XYZ. Specifying that our vertex format
// includes an XYZ position
| D3DFVF_DIFFUSE // We also specify a diffuse color for each vertex.
) ;
CHECK( hr, "SetFVF FAILED!" ) ;
// So there we have it. We just told d3d to expect
// to get vertices that each have an XYZ coordinate
// AND a color specified for them.
// SO NOW, before DRAWING anything, we have to
// do 2 more things:
// #1) Create a D3DVERTEXELEMENT9 structure
// which will represent our vertex format.
// #2) Create an array of vertices to draw!
// #3) Just draw it!
// #1) This part is a bit confusing at first,
// but if you read through it carefully, it should
// make sense.
// What we need to do here is create a SPECIAL
// kind of structure called a "VERTEX DECLARATION"
// This VertexDeclaration will MATCH UP with
// the "FVF" format that we set up just a couple
// of lines ago. Read on!
// In the specification, we actually have to
// obey the FVF mapping
// on msdn.
/////////////
#pragma region // <position vertex element decl>
// IDirect3DVertexDeclaration9
// OK in the FVF above, we promised d3d that
// EACH AND EVERY vertex would have a POSITION
// and a COLOR.
// So we declare and set up TWO D3DVERTEXELEMENT9
// structures which explain to d3d the EXACT format
// and nature of each vertex -
// IS this a bit redundant? Kind of, yes. Getting
// on with it.
D3DVERTEXELEMENT9 pos ;
// Here is where we say that this part
// of the vertex will specify a POSITION
// in space.
pos.Usage = D3DDECLUSAGE_POSITION ;
// This part makes sense if you understand
// that sometimes, you want to send MORE THAN
// one position coordinate for each vertex.
// If that makes less sense, then think about
// sending down more than one COLOR for each
// vertex, for some type of funky color blending.
pos.UsageIndex = 0 ;
pos.Stream = 0 ; // Vertex shaders have a concept
// of "stream".
// So what's a "stream" you ask?
// This really is QUITE an advanced topic, so my honest advice
// to you is to completely ignore the below unless you
// REALLY want to know what streams are.
// <streams>
// There is a concept in GPU programming called "shader instancing".
// Shader instancing is when you specify a model's geometry once,
// then draw that same model like a million times.
//
// So, you specify the model vertex data with POSITIONS on
// channel 0, for example. Then you specify a bunch of
// positions on channel 1 (1,000,000 positions in your game world,
// or something) that describe places to draw ALL the vertices
// that are on channel 0.
// So its really quite complicated and hard to understand.
// Channels are like TV -- like the TV station sends like,
// 500 channels down to your TV in parallel (hey, pretend
// they do), the vertex data you send down to the GPU
// all goes down to the GPU in parallel, work at drawing the same thing, kind of.
// Your tv can tune into one channel at a time only, and the GPU
// will actually tune into ALL the channels when drawing..
// </streams> See http://msdn.microsoft.com/en-us/library/bb147299(VS.85).aspx for more detail.
// UH, where were we? Next, we specify the actual data type of
// the position data.
pos.Type = D3DDECLTYPE_FLOAT3 ; // "Vertices will use
// 3 floats to specify their position in space".
// If you are familiar with GPU datatypes and HLSL,
// this would correspond directly with the "float3"
// datatype in the vertex shader.
// If you don't know what a vertex shader is,
// just suffice it to say, that all this means
// is 3 floats will be used for the POSITION
// of the vertex.
// Next we set the "offset":
pos.Offset = 0 ;
// In the Vertex STRUCT that WE defined,
// this is the byte offset from the start
// of the struct where D3D SHOULD expect
// to find this data.
// Using default method.
pos.Method = D3DDECLMETHOD_DEFAULT ; // here's more info
#pragma endregion // </position vertex element decl>
/////////////
/////////////
#pragma region // <color vertex element decl>
// Next we declare the vertex element that
// will represent the DIFFUSE COLOR component.
D3DVERTEXELEMENT9 col;
col.Usage = D3DDECLUSAGE_COLOR ; // its a color
col.UsageIndex = 0 ; // COLOR0
col.Stream = 0 ;
col.Type = D3DDECLTYPE_D3DCOLOR ; // UNFORTUNATELY you MUST
// chose D3DDECLTYPE_D3DCOLOR when using COLOR0
// or COLOR1. If you write your own shader,
// then you can use a FLOAT4 color.
// If you try and do that, you will get
// [5052] Direct3D9: Decl Validator: X254: (Element Error) (Decl Element [2])
// Declaration can't map to fixed function FVF because color0/color1
// cannot have type other than D3DDECLTYPE_D3DCOLOR
// in the d3d extended debug output.
// NEXT, the OFFSET. The offset is
// 3*sizeof(float) because the 'POSITION'
// comes first and takes up 3 floats as we
// specified above.
col.Offset = 3*sizeof( float ) ;
col.Method = D3DDECLMETHOD_DEFAULT ;
#pragma endregion // </color vertex element decl>
/////////////
/////////////
#pragma region create and set vertex declaration
// Now put the two D3DVERTEXELEMENT9's into
// an array and create the VertexDeclaration:
D3DVERTEXELEMENT9 vertexElements[] =
{
pos,
col,
// VERY IMPORTANT! D3D doesn't konw
// HOW MANY elements you will be specifying
// in advance, so you TELL IT by passing
// this SPECIAL D3DVERTEXELEMENT9 object
// which is basically just like the null
// terminator at the end of C string.
D3DDECL_END()
} ;
IDirect3DVertexDeclaration9 * Vdecl ;
// Now register in the "vertexElements" array
// we just created above into the decl
hr = g.gpu->CreateVertexDeclaration( vertexElements, &Vdecl ) ;
CHECK( hr, "CreateVertexDeclaration FAILED!" ) ;
// Now SET IN that vertex declaration into the GPU
hr = g.gpu->SetVertexDeclaration( Vdecl ) ;
CHECK( hr, "SetVertexDeclaration FAILED!" ) ;
#pragma endregion
#pragma region set render states
// FINALLY, last thing before drawing, we
// have to set the renderstate to USE
// the DIFFUSE component to determine the
// color of each vertex
// Per-Vertex Color State
hr = g.gpu->SetRenderState( D3DRS_COLORVERTEX, TRUE ) ;
CHECK( hr, "SetRenderState( COLORVERTEX ) FAILED!" ) ;
// Turn LIGHTING off. TRUE to enable Direct3D lighting,
// or FALSE to disable it. The default value is TRUE.
// __Only vertices that include a vertex normal are properly lit;
// vertices that do not contain a normal ___employ a dot product of 0___
// in all lighting calculations.__!
// So if you don't disable lighting, what will actually happen is,
// since we don't specify normals for any of our vertices,
// they will all have a normal vertex of the 0 vector, so
// the result is they will all be completely black.
hr = g.gpu->SetRenderState( D3DRS_LIGHTING, FALSE ) ;
CHECK( hr, "Lighting off" ) ;
// Turn backface culling off. Its good to turn this off
// when starting out because triangles that you wind
// ccw are discarded if this is on.
// http://msdn.microsoft.com/en-us/library/bb204882(VS.85).aspx
hr = g.gpu->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ) ;
CHECK( hr, "Culling off" ) ;
#pragma endregion
#pragma region declare a vertex structure, create verts
// OK?? WHEW!! THAT was a lot of work!
// Now let's DRAW SOMETHING!! First,
// we have to create a STRUCT which
// will match up the vertex declaration
// we specified above.
// Practically you'd probably want this struct
// to be declared in global space, but its declared
// inline here just to preserve the
// flow of information for you.
struct Vertex
{
float x,y,z ;
DWORD color ;
// Ctor starts you at origin in black
// with alpha (opacity) set to 100%
Vertex()
{
x=y=z = 0.0f;
color = D3DCOLOR_XRGB( 0,0,0 ) ;
}
// Ctor.
Vertex( float ix, float iy, float iz,
unsigned char ir, unsigned char ig, unsigned char ib )
{
x=ix;y=iy;z=iz;
color = D3DCOLOR_XRGB( ir, ig, ib ) ;
}
// Ctor that lets you pick alpha
Vertex( float ix, float iy, float iz,
unsigned char ir, unsigned char ig, unsigned char ib, unsigned char ALPHA )
{
x=ix;y=iy;z=iz;
color = D3DCOLOR_ARGB( ALPHA, ir, ig, ib ) ;
}
} ;
// Now create an array full of vertices!
Vertex verts[] = {
// Red vertex @ ( -1, 0, 0 )
Vertex( -1, 0, 0, 255, 19, 0 ),
// Green vertex @ ( 0, 1, 0 )
Vertex( 0, 1, 0, 0, 255, 0 ),
// Blue vertex @ ( 1, 0, 0 )
Vertex( 1, 0, 0, 0, 0, 255 )
} ;
float axisLen = 2.0f ;
Vertex axis[] = {
// x-axis is red
Vertex( -axisLen, 0, 0, 255, 0, 0 ),
Vertex( +axisLen, 0, 0, 255, 0, 0 ),
// y-axis green
Vertex( 0, -axisLen, 0, 0, 255, 0 ),
Vertex( 0, +axisLen, 0, 0, 255, 0 ),
// z-axis blue
Vertex( 0, 0, -axisLen, 0, 0, 255 ),
Vertex( 0, 0, +axisLen, 0, 0, 255 )
} ;
#pragma endregion
#pragma region ACTUALLY __draw__
// IDirect3DDevice9::BeginScene()
// You must call BeginScene() before you start drawing anything.
hr = g.gpu->BeginScene() ;
CHECK( hr, "BeginScene FAILED!" ) ;
hr = g.gpu->DrawPrimitiveUP( D3DPT_TRIANGLELIST, 1, verts, sizeof( Vertex ) ) ;
CHECK( hr, "DrawPrimitiveUP FAILED!" ) ;
hr = g.gpu->DrawPrimitiveUP( D3DPT_LINELIST, 3, axis, sizeof( Vertex ) ) ;
CHECK( hr, "DrawPrimitiveUP FAILED!" ) ;
float pointSize = 8.0f ;
// A DWORD is 4 bytes (a WORD is 2 bytes).
// So, what we need to do is basically cast
// pointSize to (DWORD). Its a bit complicated
// about how you actually do it, so I made a macro for it.
// The general idea is you need to "trick" SetRenderState
// into taking your float value.. SetRenderState "thinks"
// its a DWORD, while its actually a float.. and D3D
// internally somehow gets it and knows to treat it as a float.
// Kind of clunky, eh, but what can you do.
g.gpu->SetRenderState( D3DRS_POINTSIZE, CAST_AS_DWORD( pointSize ) ) ;
// Draw points at end of axis.
Vertex points[] = {
Vertex( axisLen, 0, 0, 255, 0, 0 ),
Vertex( 0, axisLen, 0, 0, 255, 0 ),
Vertex( 0, 0, axisLen, 0, 0, 255 ),
} ;
hr = g.gpu->DrawPrimitiveUP( D3DPT_POINTLIST, 3, points, sizeof( Vertex ) ) ;
CHECK( hr, "DrawPrimitiveUP FAILED!" ) ;
// endscene, and present
// IDirect3DDevice9::EndScene()
// You must call EndScene() to signify to the gpu that
// you are finished drawing. Must pair up with
// a BeginScene() call that happened earlier.
hr = g.gpu->EndScene() ;
CHECK( hr, "EndScene FAILED!" ) ;
// And finally, PRESENT what we drew to the backbuffer
g.gpu->Present( 0, 0, 0, 0 ) ;
#pragma endregion
}
// function that prints some system info. can ignore this part
// if you are not interested in it.
void printSystemInfo()
{
UINT numAdapters = g.d3d->GetAdapterCount() ;
printf( "\n\n* * * * System information * * * *\n" ) ;
printf( "Owner name: Dorky Dorkinson (haha, just kidding, little easter egg there..)\n" ) ;
printf( "Ok, the rest of this information IS real!\n" ) ;
printf( "You have %d adapters\n * (this number is bigger than 1 if you have dualview)\n", numAdapters ) ;
// Scroll through all adapters and print some info about each
for( int i = 0 ; i < numAdapters ; i++ )
{
printf( "\n\n-- ADAPTER #%d --\n", i ) ;
printf( "On monitor #%d\n", g.d3d->GetAdapterMonitor( i ) ) ;
// Object into which display mode info will be saved
// by GetAdapterDisplayMode
D3DDISPLAYMODE displayMode ;
g.d3d->GetAdapterDisplayMode( i, &displayMode ) ;
printf( "Has Format=%d Height=%d Width=%d RefreshRate=%d\n", displayMode.Format, displayMode.Height, displayMode.Width, displayMode.RefreshRate ) ;
printf( " * (format refers to the D3DFMT_ pixel mode.. e.g. 22=D3DFMT_X8R8G8B8 which is 24 bit color)\n" ) ;
D3DADAPTER_IDENTIFIER9 id ; // Will hold info about the adapter
// after call to GetAdapterIdentifier
g.d3d->GetAdapterIdentifier( i, 0, &id ) ;
// At this point you can see how WEIRD the API gets.
// All I want is the adapter identifier, and here MSDN
// says the API offers to "connect to the Internet
// and download new MS Windows Hardware Quality Labs certificates."
// Holy cow. I don't want to do THAT. So leave the flag at 0.
// There's PLENTY of info here we don't care about,
// but some of it is interesting!! I've printed
// only the most interesting members, leaving parts
// like the GUID out.
printf( "<device driver info>\n" ) ;
printf( " Description: %s\n Device Id: %d\n Device name: %s\n Driver: %s\n",
id.Description, id.DeviceId, id.DeviceName, id.Driver ) ;
printf( "</device driver info>\n" ) ;
UINT modeCount ;
// I guess this next part just shows.. how FEW modes are
// actually supported on a GPU.. I have an NVIDIA 8800GTS,
// and it only supports 28 modes on D3DFMT_X8R8G8B8,
// and 28 modes on D3DFMT_R5G6B5. The rest, 0 modes are
// reported as supported!
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_R8G8B8 ) ;
printf( "D3DFMT_R8G8B8 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_A8R8G8B8 ) ;
printf( "D3DFMT_A8R8G8B8 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_X8R8G8B8 ) ;
printf( "D3DFMT_X8R8G8B8 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_R5G6B5 ) ;
printf( "D3DFMT_R5G6B5 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_X1R5G5B5 ) ;
printf( "D3DFMT_X1R5G5B5 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_A1R5G5B5 ) ;
printf( "D3DFMT_A1R5G5B5 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_A4R4G4B4 ) ;
printf( "D3DFMT_A4R4G4B4 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_R3G3B2 ) ;
printf( "D3DFMT_R3G3B2 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_A8 ) ;
printf( "D3DFMT_A8 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_A8R3G3B2 ) ;
printf( "D3DFMT_A8R3G3B2 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_X4R4G4B4 ) ;
printf( "D3DFMT_X4R4G4B4 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_A2B10G10R10 ) ;
printf( "D3DFMT_A2B10G10R10 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_A8B8G8R8 ) ;
printf( "D3DFMT_A8B8G8R8 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_X8B8G8R8 ) ;
printf( "D3DFMT_X8B8G8R8 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_G16R16 ) ;
printf( "D3DFMT_G16R16 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_A2R10G10B10 ) ;
printf( "D3DFMT_A2R10G10B10 %d modes supported\n", modeCount ) ;
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_A16B16G16R16 ) ;
printf( "D3DFMT_A16B16G16R16 %d modes supported\n", modeCount ) ;
// This mode is the MOST LIKELY TO be supported on your machine.
modeCount = g.d3d->GetAdapterModeCount( i, D3DFMT_X8R8G8B8 ) ;
for( int j = 0 ; j < modeCount; j++ )
{
g.d3d->EnumAdapterModes( i, D3DFMT_X8R8G8B8, j, &displayMode ) ;
printf( "For format=%d (D3DFMT_X8R8G8B8) Height=%d Width=%d RefreshRate=%d is SUPPORTED\n", displayMode.Format, displayMode.Height, displayMode.Width, displayMode.RefreshRate ) ;
}
// At this point you're thinking, "HEY!! But i'm SURE my gpu supports
// alpha blending! Why are all the modes like A8R8G8B8 NOT supported!?"
// My best stab at this is it makes no sense to DISPLAY something with
// an alpha component still in it. For the final image that gets displayed --
// the alphas should already have been blended -- present day
// monitors can only display RGB, they don't have the ability to
// "go transparent". Maybe one day when we work with those enormous
// glass screens that people work with in the movies -- kinda like this one:
// http://business.timesonline.co.uk/multimedia/archive/00339/screen-385_339034a.jpg
// then having an ALPHA component on the display WOULD make sense. But for
// now, all monitors are completely opaque, and they only display colors
// RGB. So an alpha component makes no sense on the display itself.
// That's why displays don't support that display mode.
// K, FINALLY we get to the device's capabilities.
// I love how DirectX lets you "reflect" on what
// the hardware is in fact capable of. Its nice.
D3DCAPS9 caps ;
g.d3d->GetDeviceCaps( i, D3DDEVTYPE_HAL, &caps ) ;
// Now we have the capabilities of the device.
// Printing all of them would be meaningless,
// but if you want to inspect some of them, just
// use the debugger.
}
}
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow )
{
//////////////////
// First we'll start by saving a copy of
// the hInstance parameter inside our
// "glob" of globals "g":
g.win.hInstance = hInstance;
// In case we need it later, we'll have it
// with firsthand easy access.
#pragma region part 0 - attach a console
// Attach a console
AllocConsole();
AttachConsole( GetCurrentProcessId() ) ;
freopen( "CON", "w", stdout ) ; // redirect stdout to console
freopen( "CON", "w", stderr ) ; // redirect stderr to console
// Move the console over to the top left
g.win.hConsole = GetConsoleWindow();
MoveWindow( g.win.hConsole, 0, 0, 400, 400, true ) ;
printf( "* * Computer Program Begin * *\n" ) ;
#pragma endregion
#pragma region part 1 - create a window
// The next few lines you should already
// be used to: create a WNDCLASSEX
// that describes the properties of
// the window we're going to soon create.
// A. Create the WNDCLASSEX
WNDCLASSEX wcx = { 0 } ;
wcx.cbSize = sizeof( WNDCLASSEX );
wcx.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH );
wcx.hCursor = LoadCursor( NULL, IDC_ARROW );
wcx.hIcon = LoadIcon( NULL, IDI_APPLICATION );
wcx.hInstance = hInstance;
wcx.lpfnWndProc = WndProc;
wcx.lpszClassName = TEXT("Philip");
wcx.lpszMenuName = 0;
wcx.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
// Register that class with the Windows O/S..
RegisterClassEx( &wcx );
/////////////////
// Ok, AT THIS POINT, we'd normally
// just go ahead and call CreateWindow().
// And we WILL call CreateWindow(), but
// there is something I must explain to
// you first. That thing is the RECT structure.
/////////////////
// RECT:
//
// A RECT is just a C struct meant to represent
// a rectangle.
//
// The RECT structure WILL DESCRIBE EXACTLY WHERE
// AND HOW WE WANT OUR WINDOW TO APPEAR WHEN WE
// CREATE IT.
//
// TOP
// --------
// | |
// LEFT | | RIGHT
// --------
// BOTTOM
//
// So, what we do is, we create the RECT
// struct for our window as follows:
RECT rect;
SetRect( &rect, 420, // left
25, // top
420 + 800, // right
25 + 600 ); // bottom
// Save width and height off.
g.win.width = rect.right - rect.left;
g.win.height = rect.bottom - rect.top;
// Adjust it.
DWORD windowStyle = WS_OVERLAPPEDWINDOW ; // typical features of a normal window
DWORD windowExStyle = WS_EX_TOPMOST ; // I want the window to be topmost
AdjustWindowRectEx( &rect, windowStyle, false, windowExStyle );
// AdjustWindowRect() expands the RECT
// so that the CLIENT AREA (drawable region)
// has EXACTLY the dimensions we specify
// in the incoming RECT.
// If you didn't just understand that, understand
// this: "you have to call AdjustWindowRect()",
// and move on. Its not THAT important, but its
// good for the performance of your app.
///////////////////
// NOW we call CreateWindow, using
// that adjusted RECT structure to
// specify the width and height of the window.
g.win.hwnd = CreateWindowEx(
windowExStyle,
TEXT("Philip"),
TEXT("TIGER-DIRECT3D WINDOW!"),
windowStyle,
rect.left, rect.top, // adjusted x, y positions
rect.right - rect.left, rect.bottom - rect.top, // adjusted width and height
NULL, NULL,
hInstance, NULL);
// check to see that the window
// was created successfully!
if( g.win.hwnd == NULL )
{
FatalAppExit( NULL, TEXT("CreateWindow() failed!") );
}
// and show.
ShowWindow( g.win.hwnd, iCmdShow );
#pragma endregion
#pragma region part 2 - initialize direct3d9
// JUMP to the initD3D() method.
if( !initD3D() )
{
FatalAppExit( 0, TEXT("SORRY!!! DEVICE CREATION FAILED!!! YOU LOSE, WITHOUT EVEN PLAYING THE GAME!!!" ) ) ;
}
#pragma endregion
#pragma region message loop
MSG msg;
while( 1 )
{
if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
if( msg.message == WM_QUIT )
{
break;
}
else
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
// 3. DRAW USING Direct3D.
// This region right here is the
// heart of our application. THE MOST
// execution time is spent just repeating
// this draw() function.
draw();
}
#pragma endregion
//////////////
// clean up
#pragma region clean up
// Release COM objects.
// What's SAFE_RELEASE()? Well, lots of people use
// if( pointer ) pointer->Release() ; to guard
// against null pointer exceptions.
// Like the MS examples do, I've defined
// SAFE_RELEASE at the top of this file and
// I'm using it here. All it does is
// make sure the pointer is not null before releasing it.
SAFE_RELEASE( g.gpu ) ;
SAFE_RELEASE( g.d3d ) ;
#pragma endregion
// and a cheesy fade exit
AnimateWindow( g.win.hwnd, 200, AW_HIDE | AW_BLEND );
printf( "* * This Computer Program Has Ended * *\n" ) ;
return msg.wParam;
}
////////////////////////
// WNDPROC
// Notice that WndProc is very very neglected.
// We hardly do anything with it! That's because
// we do all of our processing in the draw()
// function.
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam )
{
switch( message )
{
case WM_CREATE:
Beep( 50, 10 );
return 0;
break;
case WM_PAINT:
{
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint( hwnd, &ps );
// don't draw here. would be waaay too slow.
// draw in the draw() function instead.
EndPaint( hwnd, &ps );
}
return 0;
break;
case WM_KEYDOWN:
switch( wparam )
{
case VK_ESCAPE:
PostQuitMessage( 0 );
break;
default:
break;
}
return 0;
case WM_SIZE:
{
int width = LOWORD( lparam ) ;
int height = HIWORD( lparam ) ;
printf( "RESIZED TO width=%d height=%d\n", width, height ) ;
}
break;
case WM_DESTROY:
PostQuitMessage( 0 ) ;
return 0;
break;
}
return DefWindowProc( hwnd, message, wparam, lparam );
}
/*
____ __ __ __ __ ___
/ _ \ / / / / / / \ \/ /
/ _/ / / / / / / / \ /
/ _/ \ / / / /__ / /__ / /
/_____//__/ /______//______/ /__/
*/