This is a very long, very long, very long tutorial about how to get started with D3D in C++.
Check it out!
////////////////////////////////////////// // // // 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 ); } /* ____ __ __ __ __ ___ / _ \ / / / / / / \ \/ / / _/ / / / / / / / \ / / _/ \ / / / /__ / /__ / / /_____//__/ /______//______/ /__/ */